* Added support for setting (hardware) break-/watchpoints in the kernel.

Can be enabled by defining KERNEL_BREAKPOINTS in arch/user_debugger.h
  and will provide the arch_{set,clear}_kernel_{break,watch}point()
  function. Hitting a break-/watchpoint will throw the thread into KDL.
* Finally added a comment, what's the point of
  i386_reinit_user_debug_after_context_switch(), since I wonder every
  time I see it. Should be optimized aways soon.



git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@22150 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Ingo Weinhold
2007-09-02 22:04:36 +00:00
parent 8a3cfa2cf3
commit 667f1eeb67
2 changed files with 377 additions and 159 deletions
@@ -11,6 +11,9 @@
extern "C" {
#endif
// Enable this to get support for kernel breakpoints.
//#define KERNEL_BREAKPOINTS 1
struct arch_team_debug_info;
struct arch_thread_debug_info;
@@ -27,6 +30,13 @@ status_t arch_clear_breakpoint(void *address);
status_t arch_set_watchpoint(void *address, uint32 type, int32 length);
status_t arch_clear_watchpoint(void *address);
#if KERNEL_BREAKPOINTS
status_t arch_set_kernel_breakpoint(void *address);
status_t arch_clear_kernel_breakpoint(void *address);
status_t arch_set_kernel_watchpoint(void *address, uint32 type, int32 length);
status_t arch_clear_kernel_watchpoint(void *address);
#endif
#ifdef __cplusplus
}
#endif
+367 -159
View File
@@ -3,13 +3,16 @@
* Distributed under the terms of the MIT License.
*/
#include <arch/user_debugger.h>
#include <string.h>
#include <debugger.h>
#include <driver_settings.h>
#include <int.h>
#include <team.h>
#include <thread.h>
#include <arch/user_debugger.h>
//#define TRACE_ARCH_USER_DEBUGGER
#ifdef TRACE_ARCH_USER_DEBUGGER
@@ -18,13 +21,13 @@
# define TRACE(x) ;
#endif
#define B_NO_MORE_BREAKPOINTS B_ERROR
#define B_NO_MORE_WATCHPOINTS B_ERROR
#define B_BAD_WATCHPOINT_ALIGNMENT B_ERROR
#define B_WATCHPOINT_TYPE_NOT_SUPPORTED B_ERROR
#define B_WATCHPOINT_LENGTH_NOT_SUPPORTED B_ERROR
#define B_BREAKPOINT_NOT_FOUND B_ERROR
#define B_WATCHPOINT_NOT_FOUND B_ERROR
#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.
// maps breakpoint slot index to LEN_i LSB number
@@ -42,6 +45,11 @@ static const uint32 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 uint32 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 uint32 sDR6B[4] = {
X86_DR6_B0, X86_DR6_B1, X86_DR6_B2, X86_DR6_B3
@@ -52,6 +60,250 @@ static const uint32 sDR6B[4] = {
static bool sQEmuSingleStepHack = false;
static inline void
install_breakpoints(const arch_team_debug_info &teamInfo)
{
// set breakpoints
asm("movl %0, %%dr0" : : "r"(teamInfo.breakpoints[0].address));
asm("movl %0, %%dr1" : : "r"(teamInfo.breakpoints[1].address));
asm("movl %0, %%dr2" : : "r"(teamInfo.breakpoints[2].address));
// asm("movl %0, %%dr3" : : "r"(teamInfo.breakpoints[3].address));
// DR3 is used to hold the current struct thread*.
// enable breakpoints
asm("movl %0, %%dr7" : : "r"(teamInfo.dr7));
}
static inline void
disable_breakpoints()
{
asm("movl %0, %%dr7" : : "r"(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, uint32 type,
uint32 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, uint32 type, uint32 length)
{
if (!address)
return B_BAD_VALUE;
struct 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;
struct 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 status_t
set_kernel_breakpoint(void *address, uint32 type, uint32 length)
{
if (!address)
return B_BAD_VALUE;
struct 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);
install_breakpoints(kernelTeam->debug_info.arch_info);
RELEASE_TEAM_DEBUG_INFO_LOCK(kernelTeam->debug_info);
restore_interrupts(state);
return error;
}
static status_t
clear_kernel_breakpoint(void *address, bool watchpoint)
{
if (!address)
return B_BAD_VALUE;
struct 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);
install_breakpoints(kernelTeam->debug_info.arch_info);
RELEASE_TEAM_DEBUG_INFO_LOCK(kernelTeam->debug_info);
restore_interrupts(state);
return error;
}
#endif // KERNEL_BREAKPOINTS
static inline status_t
check_watch_point_parameters(void* address, uint32 type, int32 length,
uint32& archType, uint32& 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 ((uint32)address & 0x1)
return B_BAD_WATCHPOINT_ALIGNMENT;
archLength = X86_BREAKPOINT_LENGTH_2;
break;
case 4:
if ((uint32)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 - in-kernel public interface
void
arch_clear_team_debug_info(struct arch_team_debug_info *info)
{
@@ -116,6 +368,7 @@ arch_set_debug_cpu_state(const struct debug_cpu_state *cpuState)
}
}
void
arch_get_debug_cpu_state(struct debug_cpu_state *cpuState)
{
@@ -148,111 +401,6 @@ arch_get_debug_cpu_state(struct debug_cpu_state *cpuState)
}
}
static status_t
set_breakpoint(void *address, uint32 type, uint32 length)
{
if (!address)
return B_BAD_VALUE;
struct thread *thread = thread_get_current_thread();
status_t error = B_OK;
cpu_status state = disable_interrupts();
GRAB_TEAM_DEBUG_INFO_LOCK(thread->team->debug_info);
arch_team_debug_info &info = thread->team->debug_info.arch_info;
// 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]);
} else {
if (type == X86_INSTRUCTION_BREAKPOINT)
error = B_NO_MORE_BREAKPOINTS;
else
error = B_NO_MORE_WATCHPOINTS;
}
}
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;
struct thread *thread = thread_get_current_thread();
status_t error = B_OK;
cpu_status state = disable_interrupts();
GRAB_TEAM_DEBUG_INFO_LOCK(thread->team->debug_info);
arch_team_debug_info &info = thread->team->debug_info.arch_info;
// 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]));
} else {
if (watchpoint)
error = B_WATCHPOINT_NOT_FOUND;
else
error = B_BREAKPOINT_NOT_FOUND;
}
RELEASE_TEAM_DEBUG_INFO_LOCK(thread->team->debug_info);
restore_interrupts(state);
return error;
}
status_t
arch_set_breakpoint(void *address)
@@ -272,40 +420,11 @@ arch_clear_breakpoint(void *address)
status_t
arch_set_watchpoint(void *address, uint32 type, int32 length)
{
// check type
uint32 archType;
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
uint32 archLength;
switch (length) {
case 1:
archLength = X86_BREAKPOINT_LENGTH_1;
break;
case 2:
if ((uint32)address & 0x1)
return B_BAD_WATCHPOINT_ALIGNMENT;
archLength = X86_BREAKPOINT_LENGTH_2;
break;
case 4:
if ((uint32)address & 0x3)
return B_BAD_WATCHPOINT_ALIGNMENT;
archLength = X86_BREAKPOINT_LENGTH_4;
break;
default:
return B_WATCHPOINT_LENGTH_NOT_SUPPORTED;
}
uint32 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);
}
@@ -318,21 +437,75 @@ arch_clear_watchpoint(void *address)
}
static inline void
install_breakpoints(const arch_team_debug_info &teamInfo)
{
// set breakpoints
asm("movl %0, %%dr0" : : "r"(teamInfo.breakpoints[0].address));
asm("movl %0, %%dr1" : : "r"(teamInfo.breakpoints[1].address));
asm("movl %0, %%dr2" : : "r"(teamInfo.breakpoints[2].address));
// asm("movl %0, %%dr3" : : "r"(teamInfo.breakpoints[3].address));
// DR3 is used to hold the current struct thread*.
#if KERNEL_BREAKPOINTS
// enable breakpoints
asm("movl %0, %%dr7" : : "r"(teamInfo.dr7));
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)
{
uint32 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 enabled.
*/
@@ -378,9 +551,16 @@ i386_exit_user_debug_at_kernel_entry()
GRAB_THREAD_LOCK();
// disable breakpoints
asm("movl %0, %%dr7" : : "r"(X86_BREAKPOINTS_DISABLED_DR7));
disable_breakpoints();
thread->debug_info.arch_info.flags &= ~X86_THREAD_DEBUG_DR7_SET;
#if KERNEL_BREAKPOINTS
struct 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);
#endif
RELEASE_THREAD_LOCK();
restore_interrupts(state);
}
@@ -392,12 +572,29 @@ i386_exit_user_debug_at_kernel_entry()
void
i386_reinit_user_debug_after_context_switch(struct thread *thread)
{
// This function deals with a race condition: We set up the debugging
// registers in i386_init_user_debug_at_kernel_exit() when a userland
// thread is going to leave the kernel. Afterwards the thread might be
// preempted, though, since interrupts are enabled.
// X86_THREAD_DEBUG_DR7_SET indicates, when this happens.
// TODO: We should fix this by disabling interrupts before
// i386_init_user_debug_at_kernel_exit() is called and keep them disabled
// until returning from the interrupt.
if (thread->debug_info.arch_info.flags & X86_THREAD_DEBUG_DR7_SET) {
GRAB_TEAM_DEBUG_INFO_LOCK(thread->team->debug_info);
install_breakpoints(thread->team->debug_info.arch_info);
RELEASE_TEAM_DEBUG_INFO_LOCK(thread->team->debug_info);
#if KERNEL_BREAKPOINTS
} else {
// we're still in the kernel
struct 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);
#endif
}
}
@@ -415,6 +612,12 @@ i386_handle_debug_exception(struct iframe *frame)
TRACE(("i386_handle_debug_exception(): DR6: %lx, DR7: %lx\n", dr6, dr7));
if (frame->cs != USER_CODE_SEG) {
panic("debug exception in kernel mode: dr6: 0x%lx, dr7: 0x%lx", dr6,
dr7);
return B_HANDLED_INTERRUPT;
}
// check, which exception condition applies
if (dr6 & X86_DR6_BREAKPOINT_MASK) {
// breakpoint
@@ -483,6 +686,11 @@ i386_handle_breakpoint_exception(struct iframe *frame)
{
TRACE(("i386_handle_breakpoint_exception()\n"));
if (frame->cs != USER_CODE_SEG) {
panic("breakpoint exception in kernel mode");
return B_HANDLED_INTERRUPT;
}
enable_interrupts();
user_debug_breakpoint_hit(true);