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:
parent
87df8902ac
commit
568ade58d0
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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,17 +826,11 @@ 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (IFRAME_IS_USER(frame)) {
|
||||
// enable interrupts and notify the debugger
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user