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
This commit is contained in:
Ingo Weinhold 2009-06-14 12:14:06 +00:00
parent 87df8902ac
commit 568ade58d0
10 changed files with 189 additions and 51 deletions

View File

@ -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);

View File

@ -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);

View File

@ -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,

View File

@ -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,

View File

@ -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)
{

View File

@ -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)
{

View File

@ -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

View File

@ -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)
{

View File

@ -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;
}
}
}

View File

@ -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 <arch/debug.h>
#include <arch/user_debugger.h>
#include <commpage_defs.h>
#include <cpu.h>
#include <debugger.h>
#include <kernel.h>
@ -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