/* * Copyright 2005-2016, Ingo Weinhold, ingo_weinhold@gmx.de. * Distributed under the terms of the MIT License. */ #include #include #include #include #include #include #include #include //#define TRACE_ARCH_USER_DEBUGGER #ifdef TRACE_ARCH_USER_DEBUGGER # define TRACE(x) dprintf x #else # define TRACE(x) ; #endif #define B_NO_MORE_BREAKPOINTS B_BUSY #define B_NO_MORE_WATCHPOINTS B_BUSY #define B_BAD_WATCHPOINT_ALIGNMENT B_BAD_VALUE #define B_WATCHPOINT_TYPE_NOT_SUPPORTED B_NOT_SUPPORTED #define B_WATCHPOINT_LENGTH_NOT_SUPPORTED B_NOT_SUPPORTED #define B_BREAKPOINT_NOT_FOUND B_NAME_NOT_FOUND #define B_WATCHPOINT_NOT_FOUND B_NAME_NOT_FOUND // TODO: Make those real error codes. #ifdef __x86_64__ extern bool gHasXsave; #else extern bool gHasSSE; #endif // The software breakpoint instruction (int3). const uint8 kX86SoftwareBreakpoint[1] = { 0xcc }; // maps breakpoint slot index to LEN_i LSB number static const size_t sDR7Len[4] = { X86_DR7_LEN0_LSB, X86_DR7_LEN1_LSB, X86_DR7_LEN2_LSB, X86_DR7_LEN3_LSB }; // maps breakpoint slot index to R/W_i LSB number static const size_t sDR7RW[4] = { X86_DR7_RW0_LSB, X86_DR7_RW1_LSB, X86_DR7_RW2_LSB, X86_DR7_RW3_LSB }; // maps breakpoint slot index to L_i bit number static const size_t sDR7L[4] = { X86_DR7_L0, X86_DR7_L1, X86_DR7_L2, X86_DR7_L3 }; // maps breakpoint slot index to G_i bit number static const size_t sDR7G[4] = { X86_DR7_G0, X86_DR7_G1, X86_DR7_G2, X86_DR7_G3 }; // maps breakpoint slot index to B_i bit number static const size_t sDR6B[4] = { X86_DR6_B0, X86_DR6_B1, X86_DR6_B2, X86_DR6_B3 }; // Enables a hack to make single stepping work under qemu. Set via kernel // driver settings. static bool sQEmuSingleStepHack = false; #ifdef __x86_64__ static void get_iframe_registers(const iframe* frame, debug_cpu_state* cpuState) { // Get general purpose registers. cpuState->r15 = frame->r15; cpuState->r14 = frame->r14; cpuState->r13 = frame->r13; cpuState->r12 = frame->r12; cpuState->r11 = frame->r11; cpuState->r10 = frame->r10; cpuState->r9 = frame->r9; cpuState->r8 = frame->r8; cpuState->rbp = frame->bp; cpuState->rsi = frame->si; cpuState->rdi = frame->di; cpuState->rdx = frame->dx; cpuState->rcx = frame->cx; cpuState->rbx = frame->bx; cpuState->rax = frame->ax; cpuState->vector = frame->vector; cpuState->error_code = frame->error_code; cpuState->rip = frame->ip; cpuState->cs = frame->cs; cpuState->rflags = frame->flags; cpuState->rsp = frame->sp; cpuState->ss = frame->ss; // Other segment registers are not saved or changed on interrupts, so // get their value here. uint16 seg; __asm__ volatile ("movw %%ds, %0" : "=r" (seg)); cpuState->ds = seg; __asm__ volatile ("movw %%es, %0" : "=r" (seg)); cpuState->es = seg; __asm__ volatile ("movw %%fs, %0" : "=r" (seg)); cpuState->fs = seg; __asm__ volatile ("movw %%gs, %0" : "=r" (seg)); cpuState->gs = seg; } static void set_iframe_registers(iframe* frame, const debug_cpu_state* cpuState) { frame->r15 = cpuState->r15; frame->r14 = cpuState->r14; frame->r13 = cpuState->r13; frame->r12 = cpuState->r12; frame->r11 = cpuState->r11; frame->r10 = cpuState->r10; frame->r9 = cpuState->r9; frame->r8 = cpuState->r8; frame->bp = cpuState->rbp; frame->si = cpuState->rsi; frame->di = cpuState->rdi; frame->dx = cpuState->rdx; frame->cx = cpuState->rcx; frame->bx = cpuState->rbx; frame->ax = cpuState->rax; frame->ip = cpuState->rip; frame->flags = (frame->flags & ~X86_EFLAGS_USER_SETTABLE_FLAGS) | (cpuState->rflags & X86_EFLAGS_USER_SETTABLE_FLAGS); frame->sp = cpuState->rsp; } #else // __x86_64__ static void get_iframe_registers(const iframe* frame, debug_cpu_state* cpuState) { cpuState->gs = frame->gs; cpuState->fs = frame->fs; cpuState->es = frame->es; cpuState->ds = frame->ds; cpuState->edi = frame->di; cpuState->esi = frame->si; cpuState->ebp = frame->bp; cpuState->esp = frame->sp; cpuState->ebx = frame->bx; cpuState->edx = frame->orig_edx; cpuState->ecx = frame->cx; cpuState->eax = frame->orig_eax; cpuState->vector = frame->vector; cpuState->error_code = frame->error_code; cpuState->eip = frame->ip; cpuState->cs = frame->cs; cpuState->eflags = frame->flags; cpuState->user_esp = frame->user_sp; cpuState->user_ss = frame->user_ss; } static void set_iframe_registers(iframe* frame, const debug_cpu_state* cpuState) { // frame->gs = cpuState->gs; // frame->fs = cpuState->fs; // frame->es = cpuState->es; // frame->ds = cpuState->ds; frame->di = cpuState->edi; frame->si = cpuState->esi; frame->bp = cpuState->ebp; // frame->esp = cpuState->esp; frame->bx = cpuState->ebx; frame->dx = cpuState->edx; frame->cx = cpuState->ecx; frame->ax = cpuState->eax; // frame->vector = cpuState->vector; // frame->error_code = cpuState->error_code; frame->ip = cpuState->eip; // frame->cs = cpuState->cs; frame->flags = (frame->flags & ~X86_EFLAGS_USER_SETTABLE_FLAGS) | (cpuState->eflags & X86_EFLAGS_USER_SETTABLE_FLAGS); frame->user_sp = cpuState->user_esp; // frame->user_ss = cpuState->user_ss; } #endif // __x86_64__ static void get_cpu_state(Thread* thread, iframe* frame, debug_cpu_state* cpuState) { // For the floating point state to be correct the calling function must // not use these registers (not even indirectly). #ifdef __x86_64__ memset(&cpuState->extended_registers, 0, sizeof(cpuState->extended_registers)); if (frame->fpu != nullptr) { if (gHasXsave) { // TODO check the xsave header to know the actual size of the // register context depending on what is saved. For now we assume // there is only the YMM AVX registers memcpy(&cpuState->extended_registers, frame->fpu, sizeof(cpuState->extended_registers)); } else { // Only the "legacy area" saved by fxsave is available memcpy(&cpuState->extended_registers, frame->fpu, sizeof(cpuState->extended_registers.fp_fxsave)); } } #else Thread* thisThread = thread_get_current_thread(); if (gHasSSE) { if (thread == thisThread) { // Since fxsave requires 16-byte alignment and this isn't guaranteed // for the passed buffer, we use our thread's fpu_state field as // temporary buffer. We need to disable interrupts to make use of // it. Thread* thread = thread_get_current_thread(); InterruptsLocker locker; x86_fxsave(thread->arch_info.fpu_state); // unlike fnsave, fxsave doesn't reinit the FPU state } memcpy(&cpuState->extended_registers, thread->arch_info.fpu_state, sizeof(cpuState->extended_registers)); } else { if (thread == thisThread) { x86_fnsave(&cpuState->extended_registers); // fnsave reinits the FPU state after saving, so we need to // load it again x86_frstor(&cpuState->extended_registers); } else { memcpy(&cpuState->extended_registers, thread->arch_info.fpu_state, sizeof(cpuState->extended_registers)); } // TODO: Convert to fxsave format! } #endif get_iframe_registers(frame, cpuState); } static inline void install_breakpoints(const arch_team_debug_info& teamInfo) { // set breakpoints asm("mov %0, %%dr0" : : "r"(teamInfo.breakpoints[0].address)); asm("mov %0, %%dr1" : : "r"(teamInfo.breakpoints[1].address)); asm("mov %0, %%dr2" : : "r"(teamInfo.breakpoints[2].address)); asm("mov %0, %%dr3" : : "r"(teamInfo.breakpoints[3].address)); // enable breakpoints asm("mov %0, %%dr7" : : "r"(teamInfo.dr7)); } static inline void disable_breakpoints() { asm("mov %0, %%dr7" : : "r"((size_t)X86_BREAKPOINTS_DISABLED_DR7)); } /*! Sets a break-/watchpoint in the given team info. Interrupts must be disabled and the team debug info lock be held. */ static inline status_t set_breakpoint(arch_team_debug_info& info, void* address, size_t type, size_t length, bool setGlobalFlag) { // check, if there is already a breakpoint at that address bool alreadySet = false; for (int32 i = 0; i < X86_BREAKPOINT_COUNT; i++) { if (info.breakpoints[i].address == address && info.breakpoints[i].type == type) { alreadySet = true; break; } } if (!alreadySet) { // find a free slot int32 slot = -1; for (int32 i = 0; i < X86_BREAKPOINT_COUNT; i++) { if (!info.breakpoints[i].address) { slot = i; break; } } // init the breakpoint if (slot >= 0) { info.breakpoints[slot].address = address; info.breakpoints[slot].type = type; info.breakpoints[slot].length = length; info.dr7 |= (length << sDR7Len[slot]) | (type << sDR7RW[slot]) | (1 << sDR7L[slot]); if (setGlobalFlag) info.dr7 |= (1 << sDR7G[slot]); } else { if (type == X86_INSTRUCTION_BREAKPOINT) return B_NO_MORE_BREAKPOINTS; else return B_NO_MORE_WATCHPOINTS; } } return B_OK; } /*! Clears a break-/watchpoint in the given team info. Interrupts must be disabled and the team debug info lock be held. */ static inline status_t clear_breakpoint(arch_team_debug_info& info, void* address, bool watchpoint) { // find the breakpoint int32 slot = -1; for (int32 i = 0; i < X86_BREAKPOINT_COUNT; i++) { if (info.breakpoints[i].address == address && (watchpoint != (info.breakpoints[i].type == X86_INSTRUCTION_BREAKPOINT))) { slot = i; break; } } // clear the breakpoint if (slot >= 0) { info.breakpoints[slot].address = NULL; info.dr7 &= ~((0x3 << sDR7Len[slot]) | (0x3 << sDR7RW[slot]) | (1 << sDR7L[slot]) | (1 << sDR7G[slot])); } else { if (watchpoint) return B_WATCHPOINT_NOT_FOUND; else return B_BREAKPOINT_NOT_FOUND; } return B_OK; } static status_t set_breakpoint(void* address, size_t type, size_t length) { if (!address) return B_BAD_VALUE; Thread* thread = thread_get_current_thread(); cpu_status state = disable_interrupts(); GRAB_TEAM_DEBUG_INFO_LOCK(thread->team->debug_info); status_t error = set_breakpoint(thread->team->debug_info.arch_info, address, type, length, false); RELEASE_TEAM_DEBUG_INFO_LOCK(thread->team->debug_info); restore_interrupts(state); return error; } static status_t clear_breakpoint(void* address, bool watchpoint) { if (!address) return B_BAD_VALUE; Thread* thread = thread_get_current_thread(); cpu_status state = disable_interrupts(); GRAB_TEAM_DEBUG_INFO_LOCK(thread->team->debug_info); status_t error = clear_breakpoint(thread->team->debug_info.arch_info, address, watchpoint); RELEASE_TEAM_DEBUG_INFO_LOCK(thread->team->debug_info); restore_interrupts(state); return error; } #if KERNEL_BREAKPOINTS static void install_breakpoints_per_cpu(void* /*cookie*/, int cpu) { Team* kernelTeam = team_get_kernel_team(); GRAB_TEAM_DEBUG_INFO_LOCK(kernelTeam->debug_info); install_breakpoints(kernelTeam->debug_info.arch_info); RELEASE_TEAM_DEBUG_INFO_LOCK(kernelTeam->debug_info); } static status_t set_kernel_breakpoint(void* address, size_t type, size_t length) { if (!address) return B_BAD_VALUE; Team* kernelTeam = team_get_kernel_team(); cpu_status state = disable_interrupts(); GRAB_TEAM_DEBUG_INFO_LOCK(kernelTeam->debug_info); status_t error = set_breakpoint(kernelTeam->debug_info.arch_info, address, type, length, true); RELEASE_TEAM_DEBUG_INFO_LOCK(kernelTeam->debug_info); call_all_cpus(install_breakpoints_per_cpu, NULL); restore_interrupts(state); return error; } static status_t clear_kernel_breakpoint(void* address, bool watchpoint) { if (!address) return B_BAD_VALUE; Team* kernelTeam = team_get_kernel_team(); cpu_status state = disable_interrupts(); GRAB_TEAM_DEBUG_INFO_LOCK(kernelTeam->debug_info); status_t error = clear_breakpoint(kernelTeam->debug_info.arch_info, address, watchpoint); RELEASE_TEAM_DEBUG_INFO_LOCK(kernelTeam->debug_info); call_all_cpus(install_breakpoints_per_cpu, NULL); restore_interrupts(state); return error; } #endif // KERNEL_BREAKPOINTS static inline status_t check_watch_point_parameters(void* address, uint32 type, int32 length, size_t& archType, size_t& archLength) { // check type switch (type) { case B_DATA_WRITE_WATCHPOINT: archType = X86_DATA_WRITE_BREAKPOINT; break; case B_DATA_READ_WRITE_WATCHPOINT: archType = X86_DATA_READ_WRITE_BREAKPOINT; break; case B_DATA_READ_WATCHPOINT: default: return B_WATCHPOINT_TYPE_NOT_SUPPORTED; break; } // check length and alignment switch (length) { case 1: archLength = X86_BREAKPOINT_LENGTH_1; break; case 2: if ((addr_t)address & 0x1) return B_BAD_WATCHPOINT_ALIGNMENT; archLength = X86_BREAKPOINT_LENGTH_2; break; case 4: if ((addr_t)address & 0x3) return B_BAD_WATCHPOINT_ALIGNMENT; archLength = X86_BREAKPOINT_LENGTH_4; break; default: return B_WATCHPOINT_LENGTH_NOT_SUPPORTED; } return B_OK; } // #pragma mark - kernel debugger commands #if KERNEL_BREAKPOINTS static int debugger_breakpoints(int argc, char** argv) { Team* kernelTeam = team_get_kernel_team(); arch_team_debug_info& info = kernelTeam->debug_info.arch_info; for (int32 i = 0; i < X86_BREAKPOINT_COUNT; i++) { kprintf("breakpoint[%" B_PRId32 "] ", i); if (info.breakpoints[i].address != NULL) { kprintf("%p ", info.breakpoints[i].address); switch (info.breakpoints[i].type) { case X86_INSTRUCTION_BREAKPOINT: kprintf("instruction"); break; case X86_IO_READ_WRITE_BREAKPOINT: kprintf("io read/write"); break; case X86_DATA_WRITE_BREAKPOINT: kprintf("data write"); break; case X86_DATA_READ_WRITE_BREAKPOINT: kprintf("data read/write"); break; } int length = 1; switch (info.breakpoints[i].length) { case X86_BREAKPOINT_LENGTH_1: length = 1; break; case X86_BREAKPOINT_LENGTH_2: length = 2; break; case X86_BREAKPOINT_LENGTH_4: length = 4; break; } if (info.breakpoints[i].type != X86_INSTRUCTION_BREAKPOINT) kprintf(" %d byte%s", length, (length > 1 ? "s" : "")); } else kprintf("unused"); kprintf("\n"); } return 0; } static int debugger_breakpoint(int argc, char** argv) { // get arguments if (argc < 2 || argc > 3) return print_debugger_command_usage(argv[0]); addr_t address = strtoul(argv[1], NULL, 0); if (address == 0) return print_debugger_command_usage(argv[0]); bool clear = false; if (argc == 3) { if (strcmp(argv[2], "clear") == 0) clear = true; else return print_debugger_command_usage(argv[0]); } // set/clear breakpoint arch_team_debug_info& info = team_get_kernel_team()->debug_info.arch_info; status_t error; if (clear) { error = clear_breakpoint(info, (void*)address, false); } else { error = set_breakpoint(info, (void*)address, X86_INSTRUCTION_BREAKPOINT, X86_BREAKPOINT_LENGTH_1, true); } if (error == B_OK) call_all_cpus_sync(install_breakpoints_per_cpu, NULL); else kprintf("Failed to install breakpoint: %s\n", strerror(error)); return 0; } static int debugger_watchpoint(int argc, char** argv) { // get arguments if (argc < 2 || argc > 4) return print_debugger_command_usage(argv[0]); addr_t address = strtoul(argv[1], NULL, 0); if (address == 0) return print_debugger_command_usage(argv[0]); bool clear = false; bool readWrite = false; int argi = 2; int length = 1; if (argc >= 3) { if (strcmp(argv[argi], "clear") == 0) { clear = true; argi++; } else if (strcmp(argv[argi], "rw") == 0) { readWrite = true; argi++; } if (!clear && argi < argc) length = strtoul(argv[argi++], NULL, 0); if (length == 0 || argi < argc) return print_debugger_command_usage(argv[0]); } // set/clear breakpoint arch_team_debug_info& info = team_get_kernel_team()->debug_info.arch_info; status_t error; if (clear) { error = clear_breakpoint(info, (void*)address, true); } else { uint32 type = readWrite ? B_DATA_READ_WRITE_WATCHPOINT : B_DATA_WRITE_WATCHPOINT; size_t archType, archLength; error = check_watch_point_parameters((void*)address, type, length, archType, archLength); if (error == B_OK) { error = set_breakpoint(info, (void*)address, archType, archLength, true); } } if (error == B_OK) call_all_cpus_sync(install_breakpoints_per_cpu, NULL); else kprintf("Failed to install breakpoint: %s\n", strerror(error)); return 0; } static int debugger_single_step(int argc, char** argv) { // TODO: Since we need an iframe, this doesn't work when KDL wasn't entered // via an exception. iframe* frame = x86_get_current_iframe(); if (frame == NULL) { kprintf("Failed to get the current iframe!\n"); return 0; } frame->flags |= (1 << X86_EFLAGS_TF); return B_KDEBUG_QUIT; } #endif // KERNEL_BREAKPOINTS // #pragma mark - in-kernel public interface void arch_clear_team_debug_info(arch_team_debug_info* info) { for (int32 i = 0; i < X86_BREAKPOINT_COUNT; i++) info->breakpoints[i].address = NULL; info->dr7 = X86_BREAKPOINTS_DISABLED_DR7; } void arch_destroy_team_debug_info(arch_team_debug_info* info) { arch_clear_team_debug_info(info); } void arch_clear_thread_debug_info(arch_thread_debug_info* info) { info->flags = 0; } void arch_destroy_thread_debug_info(arch_thread_debug_info* info) { arch_clear_thread_debug_info(info); } void arch_update_thread_single_step() { if (iframe* frame = x86_get_user_iframe()) { Thread* thread = thread_get_current_thread(); // set/clear TF in EFLAGS depending on whether single stepping is // desired if (thread->debug_info.flags & B_THREAD_DEBUG_SINGLE_STEP) frame->flags |= (1 << X86_EFLAGS_TF); else frame->flags &= ~(1 << X86_EFLAGS_TF); } } void arch_set_debug_cpu_state(const debug_cpu_state* cpuState) { if (iframe* frame = x86_get_user_iframe()) { // For the floating point state to be correct the calling function must // not use these registers (not even indirectly). #ifdef __x86_64__ Thread* thread = thread_get_current_thread(); memcpy(thread->arch_info.fpu_state, &cpuState->extended_registers, sizeof(cpuState->extended_registers)); frame->fpu = &thread->arch_info.fpu_state; #else if (gHasSSE) { // Since fxrstor requires 16-byte alignment and this isn't // guaranteed passed buffer, we use our thread's fpu_state field as // temporary buffer. We need to disable interrupts to make use of // it. Thread* thread = thread_get_current_thread(); InterruptsLocker locker; memcpy(thread->arch_info.fpu_state, &cpuState->extended_registers, sizeof(cpuState->extended_registers)); x86_fxrstor(thread->arch_info.fpu_state); } else { // TODO: Implement! We need to convert the format first. // x86_frstor(&cpuState->extended_registers); } #endif set_iframe_registers(frame, cpuState); } } void arch_get_debug_cpu_state(debug_cpu_state* cpuState) { if (iframe* frame = x86_get_user_iframe()) get_cpu_state(thread_get_current_thread(), frame, cpuState); } /*! \brief Retrieves the CPU state for the given thread. The thread must not be running and the thread's scheduler spinlock must be held. \param thread The thread whose CPU state to retrieve. \param cpuState Pointer to pre-allocated storage for the CPU state. \return \c B_OK, if everything goes fine, another error code, if the CPU state could not be retrieved. */ status_t arch_get_thread_debug_cpu_state(Thread* thread, debug_cpu_state* cpuState) { iframe* frame = x86_get_thread_user_iframe(thread); if (frame == NULL) return B_BAD_VALUE; get_cpu_state(thread, frame, cpuState); return B_OK; } status_t arch_set_breakpoint(void* address) { return set_breakpoint(address, X86_INSTRUCTION_BREAKPOINT, X86_BREAKPOINT_LENGTH_1); } status_t arch_clear_breakpoint(void* address) { return clear_breakpoint(address, false); } status_t arch_set_watchpoint(void* address, uint32 type, int32 length) { size_t archType, archLength; status_t error = check_watch_point_parameters(address, type, length, archType, archLength); if (error != B_OK) return error; return set_breakpoint(address, archType, archLength); } status_t arch_clear_watchpoint(void* address) { return clear_breakpoint(address, true); } bool arch_has_breakpoints(arch_team_debug_info* info) { // Reading info->dr7 is atomically, so we don't need to lock. The caller // has to ensure, that the info doesn't go away. return (info->dr7 != X86_BREAKPOINTS_DISABLED_DR7); } #if KERNEL_BREAKPOINTS status_t arch_set_kernel_breakpoint(void* address) { status_t error = set_kernel_breakpoint(address, X86_INSTRUCTION_BREAKPOINT, X86_BREAKPOINT_LENGTH_1); if (error != B_OK) { panic("arch_set_kernel_breakpoint() failed to set breakpoint: %s", strerror(error)); } return error; } status_t arch_clear_kernel_breakpoint(void* address) { status_t error = clear_kernel_breakpoint(address, false); if (error != B_OK) { panic("arch_clear_kernel_breakpoint() failed to clear breakpoint: %s", strerror(error)); } return error; } status_t arch_set_kernel_watchpoint(void* address, uint32 type, int32 length) { size_t archType, archLength; status_t error = check_watch_point_parameters(address, type, length, archType, archLength); if (error == B_OK) error = set_kernel_breakpoint(address, archType, archLength); if (error != B_OK) { panic("arch_set_kernel_watchpoint() failed to set watchpoint: %s", strerror(error)); } return error; } status_t arch_clear_kernel_watchpoint(void* address) { status_t error = clear_kernel_breakpoint(address, true); if (error != B_OK) { panic("arch_clear_kernel_watchpoint() failed to clear watchpoint: %s", strerror(error)); } return error; } #endif // KERNEL_BREAKPOINTS // #pragma mark - x86 implementation interface /** * Interrupts are disabled. \a frame is unused, i.e. can be \c NULL. */ void x86_init_user_debug_at_kernel_exit(iframe* frame) { Thread* thread = thread_get_current_thread(); if (!(thread->flags & THREAD_FLAGS_BREAKPOINTS_DEFINED)) return; // disable kernel breakpoints disable_breakpoints(); // install the user breakpoints GRAB_TEAM_DEBUG_INFO_LOCK(thread->team->debug_info); arch_team_debug_info &teamInfo = thread->team->debug_info.arch_info; install_breakpoints(teamInfo); atomic_or(&thread->flags, THREAD_FLAGS_BREAKPOINTS_INSTALLED); RELEASE_TEAM_DEBUG_INFO_LOCK(thread->team->debug_info); } /** * Interrupts are disabled. */ void x86_exit_user_debug_at_kernel_entry() { Thread* thread = thread_get_current_thread(); // We need to save the current values of dr6 and dr7 in the CPU structure, // since in case of a debug exception we might overwrite them before // x86_handle_debug_exception() is called. Debug exceptions occur when // hitting a hardware break/watchpoint or when single-stepping. asm("mov %%dr6, %0" : "=r"(thread->cpu->arch.dr6)); asm("mov %%dr7, %0" : "=r"(thread->cpu->arch.dr7)); // The remainder needs only be done, when user breakpoints are installed. if (!(thread->flags & THREAD_FLAGS_BREAKPOINTS_INSTALLED)) return; // disable user breakpoints disable_breakpoints(); // install kernel breakpoints Team* kernelTeam = team_get_kernel_team(); GRAB_TEAM_DEBUG_INFO_LOCK(kernelTeam->debug_info); install_breakpoints(kernelTeam->debug_info.arch_info); atomic_and(&thread->flags, ~THREAD_FLAGS_BREAKPOINTS_INSTALLED); RELEASE_TEAM_DEBUG_INFO_LOCK(kernelTeam->debug_info); } /** * Interrupts are disabled and will possibly be enabled by the function. */ void x86_handle_debug_exception(iframe* frame) { Thread* thread = thread_get_current_thread(); // Get dr6 and dr7. If the given iframe is a userland frame, the exception // obviously occurred in userland. In that case // x86_exit_user_debug_at_kernel_entry() has already been invoked and dr6 // and dr7 are stored in the cpu info. Otherwise we need to fetch the // current values from the registers. size_t dr6; size_t dr7; if (IFRAME_IS_USER(frame)) { dr6 = thread->cpu->arch.dr6; dr7 = thread->cpu->arch.dr7; } else { asm("mov %%dr6, %0" : "=r"(dr6)); asm("mov %%dr7, %0" : "=r"(dr7)); } TRACE(("x86_handle_debug_exception(): DR6: %lx, DR7: %lx\n", dr6, dr7)); // check, which exception condition applies if (dr6 & X86_DR6_BREAKPOINT_MASK) { // breakpoint // check which breakpoint was taken bool watchpoint = true; for (int32 i = 0; i < X86_BREAKPOINT_COUNT; i++) { if (dr6 & (1 << sDR6B[i])) { size_t type = (dr7 >> sDR7RW[i]) & 0x3; if (type == X86_INSTRUCTION_BREAKPOINT) watchpoint = false; } } if (IFRAME_IS_USER(frame)) { // enable interrupts and notify the debugger enable_interrupts(); if (watchpoint) user_debug_watchpoint_hit(); else user_debug_breakpoint_hit(false); } else { panic("hit kernel %spoint: dr6: 0x%lx, dr7: 0x%lx", watchpoint ? "watch" : "break", dr6, dr7); } } else if (dr6 & (1 << X86_DR6_BD)) { // general detect exception // Occurs only, if GD in DR7 is set (which we don't do) and someone // tries to write to the debug registers. if (IFRAME_IS_USER(frame)) { dprintf("x86_handle_debug_exception(): ignoring spurious general " "detect exception\n"); enable_interrupts(); } else panic("spurious general detect exception in kernel mode"); } else if ((dr6 & (1 << X86_DR6_BS)) || sQEmuSingleStepHack) { // single step if (IFRAME_IS_USER(frame)) { // enable interrupts and notify the debugger enable_interrupts(); user_debug_single_stepped(); } else { // Disable single-stepping -- the next "step" command will re-enable // it, but we don't want it when continuing otherwise. frame->flags &= ~(1 << X86_EFLAGS_TF); // Determine whether the exception occurred at a syscall/trap // kernel entry or whether this is genuine kernel single-stepping. bool inKernel = true; if (thread->team != team_get_kernel_team() && x86_get_user_iframe() == NULL) { // TODO: This is not yet fully correct, since a newly created // thread that hasn't entered userland yet also has this // property. inKernel = false; } if (inKernel) { panic("kernel single step"); } else { // The thread is a userland thread and it just entered the // kernel when the single-step exception occurred. This happens // e.g. when sysenter is called with single-stepping enabled. // We need to ignore the exception now and send a single-step // notification later, when the thread wants to return from the // kernel. InterruptsSpinLocker threadDebugInfoLocker( thread->debug_info.lock); // Check whether the team is still being debugged and set // the B_THREAD_DEBUG_NOTIFY_SINGLE_STEP and // B_THREAD_DEBUG_STOP flags, so that the thread will be // stopped when it is going to leave the kernel and notify the // debugger about the single-step event. int32 teamDebugFlags = atomic_get(&thread->team->debug_info.flags); if (teamDebugFlags & B_TEAM_DEBUG_DEBUGGER_INSTALLED) { atomic_or(&thread->debug_info.flags, B_THREAD_DEBUG_NOTIFY_SINGLE_STEP | B_THREAD_DEBUG_STOP); // also set the respective thread flag atomic_or(&thread->flags, THREAD_FLAGS_DEBUG_THREAD); } } } } else if (dr6 & (1 << X86_DR6_BT)) { // task switch // Occurs only, if T in EFLAGS is set (which we don't do). if (IFRAME_IS_USER(frame)) { dprintf("x86_handle_debug_exception(): ignoring spurious task switch " "exception\n"); enable_interrupts(); } else panic("spurious task switch exception in kernel mode"); } else { if (IFRAME_IS_USER(frame)) { TRACE(("x86_handle_debug_exception(): ignoring spurious debug " "exception (no condition recognized)\n")); enable_interrupts(); } else { panic("spurious debug exception in kernel mode (no condition " "recognized)"); } } } /** * Interrupts are disabled and will possibly be enabled by the function. */ void x86_handle_breakpoint_exception(iframe* frame) { TRACE(("x86_handle_breakpoint_exception()\n")); // reset eip to the int3 instruction frame->ip--; if (!IFRAME_IS_USER(frame)) { panic("breakpoint exception in kernel mode"); return; } enable_interrupts(); user_debug_breakpoint_hit(true); } void x86_init_user_debug() { // get debug settings if (void* handle = load_driver_settings("kernel")) { sQEmuSingleStepHack = get_driver_boolean_parameter(handle, "qemu_single_step_hack", false, false);; unload_driver_settings(handle); } #if KERNEL_BREAKPOINTS // install debugger commands add_debugger_command_etc("breakpoints", &debugger_breakpoints, "Lists current break-/watchpoints", "\n" "Lists the current kernel break-/watchpoints.\n", 0); add_debugger_command_alias("watchpoints", "breakpoints", NULL); add_debugger_command_etc("breakpoint", &debugger_breakpoint, "Set/clears a breakpoint", "
[ clear ]\n" "Sets respectively clears the breakpoint at address
.\n", 0); add_debugger_command_etc("watchpoint", &debugger_watchpoint, "Set/clears a watchpoint", "
( [ rw ] [ ] | clear )\n" "Sets respectively clears the watchpoint at address
.\n" "If \"rw\" is given the new watchpoint is a read/write watchpoint\n" "otherwise a write watchpoint only.\n", 0); add_debugger_command_etc("step", &debugger_single_step, "Single-steps to the next instruction", "\n" "Single-steps to the next instruction.\n", 0); #endif }