From 568ade58d054e27ce4cd9da0d4e73ecb79563b96 Mon Sep 17 00:00:00 2001 From: Ingo Weinhold Date: Sun, 14 Jun 2009 12:14:06 +0000 Subject: [PATCH] User debugger support: * Generalized address checks. The debugger can now also read the commpage. * Added new syscall _kern_get_thread_cpu_state() to get the CPU state of a not running thread. Introduced arch_get_thread_debug_cpu_state() for that purpose, which is only implemented for x86 ATM (uses the new i386_get_thread_user_iframe()). * Don't allow a debugger to change a thread's "esp" anymore. That's the esp register in the kernel. "user_esp" can still be changed. * Generally set RF (resume flag) in eflags in interrupt handlers, not only after a instruction breakpoint debug exception. This should prevent breakpoints from being triggered more than once (e.g. when the breakpoint is on an instruction that can cause a page fault). I still saw those with bdb in VMware, but that might be a VMware bug. git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@31045 a95241bf-73f2-0310-859d-f6bbb57e9c96 --- headers/private/kernel/arch/user_debugger.h | 3 + headers/private/kernel/arch/x86/arch_thread.h | 4 +- headers/private/kernel/user_debugger.h | 2 + headers/private/system/syscalls.h | 3 + .../kernel/arch/m68k/arch_user_debugger.cpp | 12 ++- .../kernel/arch/ppc/arch_user_debugger.cpp | 8 ++ src/system/kernel/arch/x86/arch_interrupts.S | 5 + src/system/kernel/arch/x86/arch_thread.cpp | 34 ++++++- .../kernel/arch/x86/arch_user_debugger.cpp | 77 ++++++++++------ src/system/kernel/debug/user_debugger.cpp | 92 ++++++++++++++++--- 10 files changed, 189 insertions(+), 51 deletions(-) diff --git a/headers/private/kernel/arch/user_debugger.h b/headers/private/kernel/arch/user_debugger.h index 08537da7f4..2cdd0eec00 100644 --- a/headers/private/kernel/arch/user_debugger.h +++ b/headers/private/kernel/arch/user_debugger.h @@ -17,6 +17,7 @@ extern "C" { struct arch_team_debug_info; struct arch_thread_debug_info; +struct thread; void arch_clear_team_debug_info(struct arch_team_debug_info *info); void arch_destroy_team_debug_info(struct arch_team_debug_info *info); @@ -27,6 +28,8 @@ void arch_update_thread_single_step(); void arch_set_debug_cpu_state(const struct debug_cpu_state *cpuState); void arch_get_debug_cpu_state(struct debug_cpu_state *cpuState); +status_t arch_get_thread_debug_cpu_state(struct thread *thread, + struct debug_cpu_state *cpuState); status_t arch_set_breakpoint(void *address); status_t arch_clear_breakpoint(void *address); diff --git a/headers/private/kernel/arch/x86/arch_thread.h b/headers/private/kernel/arch/x86/arch_thread.h index a7ae51c47c..8c72fec3d6 100644 --- a/headers/private/kernel/arch/x86/arch_thread.h +++ b/headers/private/kernel/arch/x86/arch_thread.h @@ -1,5 +1,5 @@ /* - * Copyright 2002-2005, The Haiku Team. All rights reserved. + * Copyright 2002-2009, The Haiku Team. All rights reserved. * Distributed under the terms of the MIT License. * * Copyright 2002, Travis Geiselbrecht. All rights reserved. @@ -18,6 +18,8 @@ extern "C" { struct iframe *i386_get_user_iframe(void); struct iframe *i386_get_current_iframe(void); +struct iframe *i386_get_thread_user_iframe(struct thread *thread); + void *x86_next_page_directory(struct thread *from, struct thread *to); void x86_restart_syscall(struct iframe* frame); diff --git a/headers/private/kernel/user_debugger.h b/headers/private/kernel/user_debugger.h index 903d9323d4..e1b12adb26 100644 --- a/headers/private/kernel/user_debugger.h +++ b/headers/private/kernel/user_debugger.h @@ -240,6 +240,8 @@ status_t _user_install_default_debugger(port_id debuggerPort); port_id _user_install_team_debugger(team_id team, port_id debuggerPort); status_t _user_remove_team_debugger(team_id team); status_t _user_debug_thread(thread_id thread); +status_t _user_get_thread_cpu_state(thread_id thread, + struct debug_cpu_state *cpuState); void _user_wait_for_debugger(void); status_t _user_set_debugger_breakpoint(void *address, uint32 type, diff --git a/headers/private/system/syscalls.h b/headers/private/system/syscalls.h index 77d16b7c45..bf63f8bb8c 100644 --- a/headers/private/system/syscalls.h +++ b/headers/private/system/syscalls.h @@ -19,6 +19,7 @@ extern "C" { #endif +struct debug_cpu_state; struct dirent; struct Elf32_Sym; struct fd_info; @@ -384,6 +385,8 @@ extern port_id _kern_install_team_debugger(team_id team, port_id debuggerPort); extern status_t _kern_remove_team_debugger(team_id team); extern status_t _kern_debug_thread(thread_id thread); +extern status_t _kern_get_thread_cpu_state(thread_id threadID, + struct debug_cpu_state *userCPUState); extern void _kern_wait_for_debugger(void); extern status_t _kern_set_debugger_breakpoint(void *address, uint32 type, diff --git a/src/system/kernel/arch/m68k/arch_user_debugger.cpp b/src/system/kernel/arch/m68k/arch_user_debugger.cpp index 312396674a..e1909e4a3f 100644 --- a/src/system/kernel/arch/m68k/arch_user_debugger.cpp +++ b/src/system/kernel/arch/m68k/arch_user_debugger.cpp @@ -45,14 +45,14 @@ arch_update_thread_single_step() { if (struct iframe* frame = m68k_get_user_iframe()) { struct thread* thread = thread_get_current_thread(); - + // set/clear T1 in SR depending on if single stepping is desired // T1 T0 // 0 0 no tracing // 0 1 trace on flow // 1 0 single step // 1 1 undef - // note 060 and 020(?) only have T1 bit, + // note 060 and 020(?) only have T1 bit, // but this should be compatible as well. if (thread->debug_info.flags & B_THREAD_DEBUG_SINGLE_STEP) { frame->cpu.sr &= ~(M68K_SR_T_MASK); @@ -76,6 +76,14 @@ arch_get_debug_cpu_state(struct debug_cpu_state *cpuState) } +status_t +arch_get_thread_debug_cpu_state(struct thread *thread, + struct debug_cpu_state *cpuState) +{ + return B_UNSUPPORTED; +} + + status_t arch_set_breakpoint(void *address) { diff --git a/src/system/kernel/arch/ppc/arch_user_debugger.cpp b/src/system/kernel/arch/ppc/arch_user_debugger.cpp index a724bbf1e0..b4710494a3 100644 --- a/src/system/kernel/arch/ppc/arch_user_debugger.cpp +++ b/src/system/kernel/arch/ppc/arch_user_debugger.cpp @@ -54,6 +54,14 @@ arch_get_debug_cpu_state(struct debug_cpu_state *cpuState) } +status_t +arch_get_thread_debug_cpu_state(struct thread *thread, + struct debug_cpu_state *cpuState) +{ + return B_UNSUPPORTED; +} + + status_t arch_set_breakpoint(void *address) { diff --git a/src/system/kernel/arch/x86/arch_interrupts.S b/src/system/kernel/arch/x86/arch_interrupts.S index d6e376dfb0..09ccc88ff5 100644 --- a/src/system/kernel/arch/x86/arch_interrupts.S +++ b/src/system/kernel/arch/x86/arch_interrupts.S @@ -253,6 +253,11 @@ STATIC_FUNCTION(int_bottom): movl %esp, %ebp // frame pointer is the iframe + // Set the RF (resume flag) in EFLAGS. This prevents an instruction + // breakpoint on the instruction we're returning to to trigger a debug + // exception. + orl $0x10000, IFRAME_flags(%ebp); + testl $0x20000, IFRAME_flags(%ebp) // VM86 mode jnz int_bottom_vm86 cmp $USER_CODE_SEG, IFRAME_cs(%ebp) // user mode diff --git a/src/system/kernel/arch/x86/arch_thread.cpp b/src/system/kernel/arch/x86/arch_thread.cpp index fe9efb9b58..979a82a1ff 100644 --- a/src/system/kernel/arch/x86/arch_thread.cpp +++ b/src/system/kernel/arch/x86/arch_thread.cpp @@ -90,10 +90,8 @@ arch_thread_init(struct kernel_args *args) static struct iframe * -find_previous_iframe(addr_t frame) +find_previous_iframe(struct thread *thread, addr_t frame) { - struct thread *thread = thread_get_current_thread(); - // iterate backwards through the stack frames, until we hit an iframe while (frame >= thread->kernel_stack_base && frame < thread->kernel_stack_top) { @@ -117,7 +115,7 @@ get_previous_iframe(struct iframe* frame) if (frame == NULL) return NULL; - return find_previous_iframe(frame->ebp); + return find_previous_iframe(thread_get_current_thread(), frame->ebp); } @@ -130,7 +128,7 @@ get_previous_iframe(struct iframe* frame) static struct iframe* get_current_iframe(void) { - return find_previous_iframe(x86_read_ebp()); + return find_previous_iframe(thread_get_current_thread(), x86_read_ebp()); } @@ -156,12 +154,38 @@ i386_get_user_iframe(void) } +/*! \brief Like i386_get_user_iframe(), just for the given thread. + The thread must not be running and the threads spinlock must be held. +*/ +struct iframe * +i386_get_thread_user_iframe(struct thread *thread) +{ + if (thread->state == B_THREAD_RUNNING) + return NULL; + + // read %ebp from the thread's stack stored by a pushad + addr_t ebp = thread->arch_info.current_stack.esp[2]; + + // find the user iframe + struct iframe *frame = find_previous_iframe(thread, ebp); + + while (frame != NULL) { + if (IFRAME_IS_USER(frame)) + return frame; + frame = get_previous_iframe(frame); + } + + return NULL; +} + + struct iframe * i386_get_current_iframe(void) { return get_current_iframe(); } + void * x86_next_page_directory(struct thread *from, struct thread *to) { diff --git a/src/system/kernel/arch/x86/arch_user_debugger.cpp b/src/system/kernel/arch/x86/arch_user_debugger.cpp index 2299613e28..145949e3b4 100644 --- a/src/system/kernel/arch/x86/arch_user_debugger.cpp +++ b/src/system/kernel/arch/x86/arch_user_debugger.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2005, Ingo Weinhold, bonefish@users.sf.net. + * Copyright 2005-2009, Ingo Weinhold, ingo_weinhold@gmx.de. * Distributed under the terms of the MIT License. */ @@ -60,6 +60,31 @@ static const uint32 sDR6B[4] = { static bool sQEmuSingleStepHack = false; +static void +get_iframe_registers(struct iframe *frame, struct debug_cpu_state *cpuState) +{ + cpuState->gs = frame->gs; + cpuState->fs = frame->fs; + cpuState->es = frame->es; + cpuState->ds = frame->ds; + cpuState->edi = frame->edi; + cpuState->esi = frame->esi; + cpuState->ebp = frame->ebp; + cpuState->esp = frame->esp; + cpuState->ebx = frame->ebx; + cpuState->edx = frame->orig_edx; + cpuState->ecx = frame->ecx; + cpuState->eax = frame->orig_eax; + cpuState->vector = frame->vector; + cpuState->error_code = frame->error_code; + cpuState->eip = frame->eip; + cpuState->cs = frame->cs; + cpuState->eflags = frame->flags; + cpuState->user_esp = frame->user_esp; + cpuState->user_ss = frame->user_ss; +} + + static inline void install_breakpoints(const arch_team_debug_info &teamInfo) { @@ -562,7 +587,7 @@ arch_set_debug_cpu_state(const struct debug_cpu_state *cpuState) frame->edi = cpuState->edi; frame->esi = cpuState->esi; frame->ebp = cpuState->ebp; - frame->esp = cpuState->esp; +// frame->esp = cpuState->esp; frame->ebx = cpuState->ebx; frame->edx = cpuState->edx; frame->ecx = cpuState->ecx; @@ -586,30 +611,30 @@ arch_get_debug_cpu_state(struct debug_cpu_state *cpuState) i386_fnsave(cpuState->extended_regs); // For this to be correct the calling function must not use these // registers (not even indirectly). - - cpuState->gs = frame->gs; - cpuState->fs = frame->fs; - cpuState->es = frame->es; - cpuState->ds = frame->ds; - cpuState->edi = frame->edi; - cpuState->esi = frame->esi; - cpuState->ebp = frame->ebp; - cpuState->esp = frame->esp; - cpuState->ebx = frame->ebx; - cpuState->edx = frame->orig_edx; - cpuState->ecx = frame->ecx; - cpuState->eax = frame->orig_eax; - cpuState->vector = frame->vector; - cpuState->error_code = frame->error_code; - cpuState->eip = frame->eip; - cpuState->cs = frame->cs; - cpuState->eflags = frame->flags; - cpuState->user_esp = frame->user_esp; - cpuState->user_ss = frame->user_ss; + get_iframe_registers(frame, cpuState); } } +/*! \brief Returns the CPU state for the given thread. + The thread must not be running and the threads spinlock must be held. +*/ +status_t +arch_get_thread_debug_cpu_state(struct thread *thread, + struct debug_cpu_state *cpuState) +{ + struct iframe *frame = i386_get_thread_user_iframe(thread); + if (frame == NULL) + return B_BAD_VALUE; + + get_iframe_registers(frame, cpuState); + memcpy(cpuState->extended_regs, thread->arch_info.fpu_state, + sizeof(cpuState->extended_regs)); + + return B_OK; +} + + status_t arch_set_breakpoint(void *address) { @@ -801,15 +826,9 @@ x86_handle_debug_exception(struct iframe *frame) bool watchpoint = true; for (int32 i = 0; i < X86_BREAKPOINT_COUNT; i++) { if (dr6 & (1 << sDR6B[i])) { - // If it is an instruction breakpoint, we need to set RF in - // EFLAGS to prevent triggering the same exception - // again (breakpoint instructions are triggered *before* - // executing the instruction). uint32 type = (dr7 >> sDR7RW[i]) & 0x3; - if (type == X86_INSTRUCTION_BREAKPOINT) { - frame->flags |= (1 << X86_EFLAGS_RF); + if (type == X86_INSTRUCTION_BREAKPOINT) watchpoint = false; - } } } diff --git a/src/system/kernel/debug/user_debugger.cpp b/src/system/kernel/debug/user_debugger.cpp index 9f1baa8423..084127ec74 100644 --- a/src/system/kernel/debug/user_debugger.cpp +++ b/src/system/kernel/debug/user_debugger.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2005-2008, Ingo Weinhold, ingo_weinhold@gmx.de. + * Copyright 2005-2009, Ingo Weinhold, ingo_weinhold@gmx.de. * Distributed under the terms of the MIT License. */ @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -1412,6 +1413,26 @@ nub_thread_cleanup(struct thread *nubThread) } +/*! \brief Returns whether the given address can be accessed in principle. + No check whether there's an actually accessible area is performed, though. +*/ +static bool +can_access_address(const void* address, bool write) +{ + // user addresses are always fine + if (IS_USER_ADDRESS(address)) + return true; + + // a commpage address can at least be read + if ((addr_t)address >= USER_COMMPAGE_ADDR + && (addr_t)address < USER_COMMPAGE_ADDR + COMMPAGE_SIZE) { + return !write; + } + + return false; +} + + /** \brief Reads data from user memory. * * Tries to read \a size bytes of data from user memory address \a address @@ -1435,7 +1456,7 @@ read_user_memory(const void *_address, void *_buffer, int32 size, char *buffer = (char*)_buffer; // check the parameters - if (!IS_USER_ADDRESS(address)) + if (!can_access_address(address, false)) return B_BAD_ADDRESS; if (size <= 0) return B_BAD_VALUE; @@ -1446,7 +1467,7 @@ read_user_memory(const void *_address, void *_buffer, int32 size, bytesRead = 0; while (size > 0) { // check whether we're still in user address space - if (!IS_USER_ADDRESS(address)) { + if (!can_access_address(address, false)) { error = B_BAD_ADDRESS; break; } @@ -1486,7 +1507,7 @@ write_user_memory(void *_address, const void *_buffer, int32 size, const char *buffer = (const char*)_buffer; // check the parameters - if (!IS_USER_ADDRESS(address)) + if (!can_access_address(address, true)) return B_BAD_ADDRESS; if (size <= 0) return B_BAD_VALUE; @@ -1497,7 +1518,7 @@ write_user_memory(void *_address, const void *_buffer, int32 size, bytesWritten = 0; while (size > 0) { // check whether we're still in user address space - if (!IS_USER_ADDRESS(address)) { + if (!can_access_address(address, true)) { error = B_BAD_ADDRESS; break; } @@ -1589,7 +1610,7 @@ debug_nub_thread_get_thread_debug_port(struct thread *nubThread, else if (thread->debug_info.flags & B_THREAD_DEBUG_STOPPED) threadDebugPort = thread->debug_info.debug_port; else - result = B_BAD_VALUE; + result = B_BAD_THREAD_STATE; } else result = B_BAD_THREAD_ID; @@ -1677,7 +1698,7 @@ debug_nub_thread(void *) status_t result = B_OK; // check the parameters - if (!IS_USER_ADDRESS(address)) + if (!can_access_address(address, false)) result = B_BAD_ADDRESS; else if (size <= 0 || size > B_MAX_READ_WRITE_MEMORY_SIZE) result = B_BAD_VALUE; @@ -1713,7 +1734,7 @@ debug_nub_thread(void *) status_t result = B_OK; // check the parameters - if (!IS_USER_ADDRESS(address)) + if (!can_access_address(address, true)) result = B_BAD_ADDRESS; else if (size <= 0 || size > realSize) result = B_BAD_VALUE; @@ -1897,7 +1918,7 @@ debug_nub_thread(void *) // check the address status_t result = B_OK; - if (address == NULL || !IS_USER_ADDRESS(address)) + if (address == NULL || !can_access_address(address, false)) result = B_BAD_ADDRESS; // set the breakpoint @@ -1925,7 +1946,7 @@ debug_nub_thread(void *) // check the address status_t result = B_OK; - if (address == NULL || !IS_USER_ADDRESS(address)) + if (address == NULL || !can_access_address(address, false)) result = B_BAD_ADDRESS; // clear the breakpoint @@ -1952,7 +1973,7 @@ debug_nub_thread(void *) // check the address and size status_t result = B_OK; - if (address == NULL || !IS_USER_ADDRESS(address)) + if (address == NULL || !can_access_address(address, false)) result = B_BAD_ADDRESS; if (length < 0) result = B_BAD_VALUE; @@ -1982,7 +2003,7 @@ debug_nub_thread(void *) // check the address status_t result = B_OK; - if (address == NULL || !IS_USER_ADDRESS(address)) + if (address == NULL || !can_access_address(address, false)) result = B_BAD_ADDRESS; // clear the watchpoint @@ -2928,6 +2949,49 @@ _user_debug_thread(thread_id threadID) } +status_t +_user_get_thread_cpu_state(thread_id threadID, + struct debug_cpu_state *userCPUState) +{ + TRACE(("[%ld] _user_get_thread_cpu_state(%ld, %p)\n", find_thread(NULL), + threadID, userCPUState)); + + if (userCPUState == NULL || !IS_USER_ADDRESS(userCPUState)) + return B_BAD_ADDRESS; + + InterruptsSpinLocker locker(gThreadSpinlock); + + // get and check the thread + struct thread *thread = thread_get_thread_struct_locked(threadID); + if (thread == NULL) { + // thread doesn't exist any longer + return B_BAD_THREAD_ID; + } else if (thread->team == team_get_kernel_team()) { + // we can't debug the kernel team + return B_NOT_ALLOWED; + } else if (thread->debug_info.flags & B_THREAD_DEBUG_DYING) { + // the thread is already dying + return B_BAD_THREAD_ID; + } else if (thread->debug_info.flags & B_THREAD_DEBUG_NUB_THREAD) { + // don't play with the nub thread + return B_NOT_ALLOWED; + } else if (thread->state == B_THREAD_RUNNING) { + // thread is running -- no way to get its CPU state + return B_BAD_THREAD_STATE; + } + + // get the CPU state + debug_cpu_state cpuState; + status_t error = arch_get_thread_debug_cpu_state(thread, &cpuState); + if (error != B_OK) + return error; + + locker.Unlock(); + + return user_memcpy(userCPUState, &cpuState, sizeof(cpuState)); +} + + void _user_wait_for_debugger(void) { @@ -2942,7 +3006,7 @@ _user_set_debugger_breakpoint(void *address, uint32 type, int32 length, bool watchpoint) { // check the address and size - if (address == NULL || !IS_USER_ADDRESS(address)) + if (address == NULL || !can_access_address(address, false)) return B_BAD_ADDRESS; if (watchpoint && length < 0) return B_BAD_VALUE; @@ -2975,7 +3039,7 @@ status_t _user_clear_debugger_breakpoint(void *address, bool watchpoint) { // check the address - if (address == NULL || !IS_USER_ADDRESS(address)) + if (address == NULL || !can_access_address(address, false)) return B_BAD_ADDRESS; // check whether a debugger is installed already