* New locking strategy for the team debug info. It's no longer protected

by the team lock, but by a lock in the structure. This is necessary
  since we can't acquire the team lock while thread switching, for then the
  thread lock is already held.
* Added support for single stepping and break-/watchpoints.


git-svn-id: file:///srv/svn/repos/haiku/trunk/current@11503 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Ingo Weinhold
2005-02-28 00:34:06 +00:00
parent a67d62f1db
commit a7ad39fe09
2 changed files with 294 additions and 50 deletions
+37 -3
View File
@@ -9,16 +9,39 @@
#include <debugger.h>
#include <arch/user_debugger.h>
// Team related debugging data.
//
// Locking policy:
// 1) When accessing the structure it must be made sure, that the structure,
// (i.e. the struct team it lives in) isn't deleted. Thus one either needs to
// acquire the global team lock, or one accesses the structure from a thread
// of that team.
// 2) Access to the `flags' field is atomically. Reading via atomic_get()
// requires no further locks (in addition to 1) that is). Writing requires
// `lock' being held and must be done atomically, too
// (atomic_{set,and,or}()). Reading with `lock' being held doesn't need to
// be done atomically.
// 3) Access to all other fields (read or write) requires `lock' being held.
//
struct team_debug_info {
spinlock lock;
// Guards the remaining fields. Should always be the innermost lock
// to be acquired/released.
int32 flags;
// Set atomically. So reading atomically is OK, even when the team
// lock is not held (at least if it is certain, that the team struct
// won't go).
team_id debugger_team;
port_id debugger_port;
thread_id nub_thread;
port_id nub_port;
// the port the nub thread is waiting on for commands from the debugger
struct arch_team_debug_info arch_info;
};
struct thread_debug_info {
@@ -30,6 +53,9 @@ struct thread_debug_info {
// the port the thread is waiting on for commands from the nub thread
};
#define GRAB_TEAM_DEBUG_INFO_LOCK(info) acquire_spinlock(&(info).lock)
#define RELEASE_TEAM_DEBUG_INFO_LOCK(info) release_spinlock(&(info).lock)
// team debugging flags (user-specifiable flags are in <debugger.h>)
enum {
B_TEAM_DEBUG_DEBUGGER_INSTALLED = 0x0001,
@@ -47,6 +73,7 @@ enum {
B_THREAD_DEBUG_DYING = 0x0002,
B_THREAD_DEBUG_STOP = 0x0004,
B_THREAD_DEBUG_STOPPED = 0x0008,
B_THREAD_DEBUG_SINGLE_STEP = 0x0010,
B_THREAD_DEBUG_KERNEL_FLAG_MASK = 0xffff,
@@ -57,14 +84,21 @@ enum {
typedef enum {
B_DEBUGGED_THREAD_MESSAGE_CONTINUE = 0,
B_DEBUGGED_THREAD_GET_WHY_STOPPED,
B_DEBUGGED_THREAD_SET_CPU_STATE,
} debugged_thread_message;
typedef struct {
uint32 handle_event;
} debugged_thread_run;
bool single_step;
} debugged_thread_continue;
typedef struct {
debug_cpu_state cpu_state;
} debugged_thread_set_cpu_state;
typedef union {
debugged_thread_run run;
debugged_thread_continue continue_thread;
debugged_thread_set_cpu_state set_cpu_state;
} debugged_thread_message_data;
@@ -74,7 +108,7 @@ extern "C" {
// service calls
void clear_team_debug_info(struct team_debug_info *info);
void clear_team_debug_info(struct team_debug_info *info, bool initLock);
void destroy_team_debug_info(struct team_debug_info *info);
void clear_thread_debug_info(struct thread_debug_info *info,
+257 -47
View File
@@ -53,36 +53,44 @@ kill_interruptable_write_port(port_id port, int32 code, const void *buffer,
/**
* The team lock must be held.
* For the first initialization the function must be called with \a initLock
* set to \c true. If it would be possible that another thread accesses the
* structure at the same time, `lock' must be held when calling the function.
*/
void
clear_team_debug_info(struct team_debug_info *info)
clear_team_debug_info(struct team_debug_info *info, bool initLock)
{
if (info) {
arch_clear_team_debug_info(&info->arch_info);
atomic_set(&info->flags, B_TEAM_DEBUG_DEFAULT_FLAGS);
info->debugger_team = -1;
info->debugger_port = -1;
info->nub_thread = -1;
info->nub_port = -1;
if (initLock)
info->lock = 0;
}
}
/**
* The team lock must not be held. \a info must not be a member of a
* team struct (or the team struct not longer be accessible, i.e. the team
* should already be removed).
* `lock' must not be held nor may interrupts be disabled.
* \a info must not be a member of a team struct (or the team struct must no
* longer be accessible, i.e. the team should already be removed).
*
* In case the team is still accessible, the procedure is:
* 1. get the team lock
* 1. get `lock'
* 2. copy the team debug info on stack
* 3. call clear_team_debug_info() on the team debug info
* 4. release the team lock
* 4. release `lock'
* 5. call destroy_team_debug_info() on the copied team debug info
*/
void
destroy_team_debug_info(struct team_debug_info *info)
{
if (info) {
arch_destroy_team_debug_info(&info->arch_info);
// delete the nub port
if (info->nub_port >= 0) {
set_port_owner(info->nub_port, B_CURRENT_TEAM);
@@ -129,17 +137,17 @@ destroy_thread_debug_info(struct thread_debug_info *info)
}
void
static void
get_team_debug_info(team_debug_info &teamDebugInfo)
{
struct thread *thread = thread_get_current_thread();
cpu_status state = disable_interrupts();
GRAB_TEAM_LOCK();
GRAB_TEAM_DEBUG_INFO_LOCK(thread->team->debug_info);
memcpy(&teamDebugInfo, &thread->team->debug_info, sizeof(team_debug_info));
RELEASE_TEAM_LOCK();
RELEASE_TEAM_DEBUG_INFO_LOCK(thread->team->debug_info);
restore_interrupts(state);
}
@@ -195,8 +203,8 @@ thread_hit_debug_event(uint32 event, const void *message, int32 size,
port_id debuggerPort = -1;
status_t error = B_OK;
cpu_status state = disable_interrupts();
GRAB_TEAM_LOCK();
GRAB_THREAD_LOCK();
GRAB_TEAM_DEBUG_INFO_LOCK(thread->team->debug_info);
uint32 threadFlags = thread->debug_info.flags;
threadFlags &= ~B_THREAD_DEBUG_STOP;
@@ -229,8 +237,8 @@ thread_hit_debug_event(uint32 event, const void *message, int32 size,
threadFlags |= B_THREAD_DEBUG_STOPPED;
atomic_set(&thread->debug_info.flags, threadFlags);
RELEASE_TEAM_DEBUG_INFO_LOCK(thread->team->debug_info);
RELEASE_THREAD_LOCK();
RELEASE_TEAM_LOCK();
restore_interrupts(state);
// delete the superfluous port
@@ -262,17 +270,34 @@ thread_hit_debug_event(uint32 event, const void *message, int32 size,
&command, &commandMessage, sizeof(commandMessage));
if (commandMessageSize < 0) {
error = commandMessageSize;
TRACE(("thread_hit_debug_event(): thread: %ld, failed to "
"receive message from port %ld: %lx\n", thread->id, port,
error));
TRACE(("thread_hit_debug_event(): thread: %ld, failed "
"to receive message from port %ld: %lx\n",
thread->id, port, error));
break;
}
switch (command) {
case B_DEBUGGED_THREAD_MESSAGE_CONTINUE:
TRACE(("thread_hit_debug_event(): thread: %ld: "
"B_DEBUGGED_THREAD_MESSAGE_CONTINUE\n", thread->id));
result = commandMessage.run.handle_event;
"B_DEBUGGED_THREAD_MESSAGE_CONTINUE\n",
thread->id));
result = commandMessage.continue_thread.handle_event;
// update the single-step flag
state = disable_interrupts();
GRAB_THREAD_LOCK();
if (commandMessage.continue_thread.single_step) {
atomic_or(&thread->debug_info.flags,
B_THREAD_DEBUG_SINGLE_STEP);
} else {
atomic_and(&thread->debug_info.flags,
~B_THREAD_DEBUG_SINGLE_STEP);
}
RELEASE_THREAD_LOCK();
restore_interrupts(state);
done = true;
break;
@@ -299,11 +324,23 @@ thread_hit_debug_event(uint32 event, const void *message, int32 size,
break;
}
case B_DEBUGGED_THREAD_SET_CPU_STATE:
{
TRACE(("thread_hit_debug_event(): thread: %ld: "
"B_DEBUGGED_THREAD_SET_CPU_STATE\n",
thread->id));
arch_set_debug_cpu_state(
&commandMessage.set_cpu_state.cpu_state);
break;
}
}
}
} else {
TRACE(("thread_hit_debug_event(): thread: %ld, failed to send message "
"to debugger port %ld: %lx\n", thread->id, debuggerPort, error));
TRACE(("thread_hit_debug_event(): thread: %ld, failed to send "
"message to debugger port %ld: %lx\n", thread->id,
debuggerPort, error));
}
// unset the "stopped" state
@@ -537,8 +574,12 @@ user_debug_thread_deleted(team_id teamID, thread_id threadID)
int32 teamDebugFlags = 0;
port_id debuggerPort = -1;
if (team) {
GRAB_TEAM_DEBUG_INFO_LOCK(team->debug_info);
teamDebugFlags = atomic_get(&team->debug_info.flags);
debuggerPort = team->debug_info.debugger_port;
RELEASE_TEAM_DEBUG_INFO_LOCK(team->debug_info);
}
RELEASE_TEAM_LOCK();
@@ -617,15 +658,17 @@ nub_thread_cleanup(struct thread *nubThread)
cpu_status state = disable_interrupts();
GRAB_TEAM_LOCK();
GRAB_TEAM_DEBUG_INFO_LOCK(nubThread->team->debug_info);
team_debug_info &info = nubThread->team->debug_info;
if (info.flags & B_TEAM_DEBUG_DEBUGGER_INSTALLED
&& info.nub_thread == nubThread->id) {
teamDebugInfo = info;
clear_team_debug_info(&info);
clear_team_debug_info(&info, false);
destroyDebugInfo = true;
}
RELEASE_TEAM_DEBUG_INFO_LOCK(nubThread->team->debug_info);
RELEASE_TEAM_LOCK();
restore_interrupts(state);
@@ -637,17 +680,19 @@ nub_thread_cleanup(struct thread *nubThread)
static status_t
debug_nub_thread(void *)
{
struct thread *nubThread = thread_get_current_thread();
// check, if we're still the current nub thread and get our port
cpu_status state = disable_interrupts();
GRAB_TEAM_LOCK();
struct thread *nubThread = thread_get_current_thread();
GRAB_TEAM_DEBUG_INFO_LOCK(nubThread->team->debug_info);
if (nubThread->team->debug_info.nub_thread != nubThread->id)
return 0;
port_id port = nubThread->team->debug_info.nub_port;
RELEASE_TEAM_LOCK();
RELEASE_TEAM_DEBUG_INFO_LOCK(nubThread->team->debug_info);
restore_interrupts(state);
TRACE(("debug_nub_thread() thread: %ld, team %ld, nub port: %ld\n",
@@ -676,6 +721,8 @@ debug_nub_thread(void *)
union {
debug_nub_read_memory_reply read_memory;
debug_nub_write_memory_reply write_memory;
debug_nub_set_breakpoint_reply set_breakpoint;
debug_nub_set_watchpoint_reply set_watchpoint;
} reply;
int32 replySize = 0;
port_id replyPort = -1;
@@ -755,12 +802,12 @@ debug_nub_thread(void *)
// set the flags
cpu_status state = disable_interrupts();
GRAB_TEAM_LOCK();
GRAB_TEAM_DEBUG_INFO_LOCK(team->debug_info);
flags |= team->debug_info.flags & B_TEAM_DEBUG_KERNEL_FLAG_MASK;
atomic_set(&team->debug_info.flags, flags);
RELEASE_TEAM_LOCK();
RELEASE_TEAM_DEBUG_INFO_LOCK(team->debug_info);
restore_interrupts(state);
break;
@@ -796,14 +843,27 @@ debug_nub_thread(void *)
}
case B_DEBUG_MESSAGE_RUN_THREAD:
case B_DEBUG_MESSAGE_STEP_THREAD:
{
// get the parameters
thread_id threadID = message.run_thread.thread;
uint32 handleEvent = message.run_thread.handle_event;
thread_id threadID;
uint32 handleEvent;
bool singleStep;
TRACE(("nub thread %ld: B_DEBUG_MESSAGE_RUN_THREAD: "
if (command == B_DEBUG_MESSAGE_RUN_THREAD) {
threadID = message.run_thread.thread;
handleEvent = message.run_thread.handle_event;
singleStep = false;
} else {
threadID = message.step_thread.thread;
handleEvent = message.step_thread.handle_event;
singleStep = true;
}
TRACE(("nub thread %ld: B_DEBUG_MESSAGE_%s_THREAD: "
"thread: %ld, handle event: %lu\n", nubThread->id,
threadID, handleEvent));
(singleStep ? "STEP" : "RUN"), threadID,
handleEvent));
// find the thread and get its debug port
state = disable_interrupts();
@@ -822,12 +882,13 @@ debug_nub_thread(void *)
// send a message to the debugged thread
if (threadDebugPort >= 0) {
debugged_thread_run commandMessage;
debugged_thread_continue commandMessage;
commandMessage.handle_event = handleEvent;
commandMessage.single_step = singleStep;
write_port(threadDebugPort,
B_DEBUGGED_THREAD_MESSAGE_CONTINUE, &commandMessage,
sizeof(commandMessage));
B_DEBUGGED_THREAD_MESSAGE_CONTINUE,
&commandMessage, sizeof(commandMessage));
}
break;
@@ -864,6 +925,141 @@ debug_nub_thread(void *)
break;
}
case B_DEBUG_MESSAGE_SET_CPU_STATE:
{
// get the parameters
thread_id threadID = message.set_cpu_state.thread;
const debug_cpu_state &cpuState
= message.set_cpu_state.cpu_state;
TRACE(("nub thread %ld: B_DEBUG_MESSAGE_SET_CPU_STATE: "
"thread: %ld\n", nubThread->id, threadID));
// find the thread and get its debug port
state = disable_interrupts();
GRAB_THREAD_LOCK();
port_id threadDebugPort = -1;
struct thread *thread
= thread_get_thread_struct_locked(threadID);
if (thread && thread->team == nubThread->team
&& thread->debug_info.flags & B_THREAD_DEBUG_STOPPED) {
threadDebugPort = thread->debug_info.debug_port;
}
RELEASE_THREAD_LOCK();
restore_interrupts(state);
// send a message to the debugged thread
if (threadDebugPort >= 0) {
debugged_thread_set_cpu_state commandMessage;
memcpy(&commandMessage.cpu_state, &cpuState,
sizeof(debug_cpu_state));
write_port(threadDebugPort,
B_DEBUGGED_THREAD_SET_CPU_STATE,
&commandMessage, sizeof(commandMessage));
}
break;
}
case B_DEBUG_MESSAGE_SET_BREAKPOINT:
{
// get the parameters
replyPort = message.set_breakpoint.reply_port;
void *address = message.set_breakpoint.address;
TRACE(("nub thread %ld: B_DEBUG_MESSAGE_SET_BREAKPOINT: "
"address: %p\n", nubThread->id, address));
// check the address
status_t result = B_OK;
if (address == NULL || !IS_USER_ADDRESS(address))
result = B_BAD_ADDRESS;
// set the breakpoint
if (result == B_OK)
result = arch_set_breakpoint(address);
// prepare the reply
reply.set_breakpoint.error = result;
replySize = sizeof(reply.set_breakpoint);
sendReply = true;
break;
}
case B_DEBUG_MESSAGE_CLEAR_BREAKPOINT:
{
// get the parameters
void *address = message.clear_breakpoint.address;
TRACE(("nub thread %ld: B_DEBUG_MESSAGE_CLEAR_BREAKPOINT: "
"address: %p\n", nubThread->id, address));
// check the address
status_t result = B_OK;
if (address == NULL || !IS_USER_ADDRESS(address))
result = B_BAD_ADDRESS;
// clear the breakpoint
if (result == B_OK)
result = arch_clear_breakpoint(address);
break;
}
case B_DEBUG_MESSAGE_SET_WATCHPOINT:
{
// get the parameters
replyPort = message.set_watchpoint.reply_port;
void *address = message.set_watchpoint.address;
uint32 type = message.set_watchpoint.type;
int32 length = message.set_watchpoint.length;
TRACE(("nub thread %ld: B_DEBUG_MESSAGE_SET_WATCHPOINT: "
"address: %p, type: %lu, length: %ld\n", nubThread->id,
address, type, length));
// check the address and size
status_t result = B_OK;
if (address == NULL || !IS_USER_ADDRESS(address))
result = B_BAD_ADDRESS;
if (length < 0)
result = B_BAD_VALUE;
// set the watchpoint
if (result == B_OK)
result = arch_set_watchpoint(address, type, length);
// prepare the reply
reply.set_watchpoint.error = result;
replySize = sizeof(reply.set_watchpoint);
sendReply = true;
break;
}
case B_DEBUG_MESSAGE_CLEAR_WATCHPOINT:
{
// get the parameters
void *address = message.clear_watchpoint.address;
TRACE(("nub thread %ld: B_DEBUG_MESSAGE_CLEAR_WATCHPOINT: "
"address: %p\n", nubThread->id, address));
// check the address
status_t result = B_OK;
if (address == NULL || !IS_USER_ADDRESS(address))
result = B_BAD_ADDRESS;
// clear the watchpoint
if (result == B_OK)
result = arch_clear_watchpoint(address);
break;
}
}
// send the reply, if necessary
@@ -901,13 +1097,16 @@ TRACE(("install_team_debugger(team: %ld, port: %ld, default: %d, "
cpu_status state = disable_interrupts();
GRAB_TEAM_LOCK();
struct team *team = (teamID == B_CURRENT_TEAM
? thread_get_current_thread()->team
: team_get_team_struct_locked(teamID));
// get a real team ID
// We look up the team by ID, even in case of the current team, so we can be
// sure, that the team is not already dying.
if (teamID == B_CURRENT_TEAM)
teamID = thread_get_current_thread()->team->id;
struct team *team = team_get_team_struct_locked(teamID);
if (team) {
// get a real team ID
teamID = team->id;
GRAB_TEAM_DEBUG_INFO_LOCK(team->debug_info);
if (team == team_get_kernel_team()) {
// don't allow to debug the kernel
@@ -918,6 +1117,8 @@ TRACE(("install_team_debugger(team: %ld, port: %ld, default: %d, "
done = true;
result = team->debug_info.nub_port;
}
RELEASE_TEAM_DEBUG_INFO_LOCK(team->debug_info);
} else
error = B_BAD_TEAM_ID;
@@ -977,16 +1178,19 @@ TRACE(("install_team_debugger(team: %ld, port: %ld, default: %d, "
state = disable_interrupts();
GRAB_TEAM_LOCK();
team = (teamID == B_CURRENT_TEAM
? thread_get_current_thread()->team
: team_get_team_struct_locked(teamID));
// look up again, to make sure the team isn't dying
team = team_get_team_struct_locked(teamID);
if (team) {
GRAB_TEAM_DEBUG_INFO_LOCK(team->debug_info);
if (team->debug_info.flags & B_TEAM_DEBUG_DEBUGGER_INSTALLED) {
// there's already a debugger installed
error = (dontFail ? B_OK : B_BAD_VALUE);
done = true;
result = team->debug_info.nub_port;
RELEASE_TEAM_DEBUG_INFO_LOCK(team->debug_info);
} else {
atomic_set(&team->debug_info.flags,
B_TEAM_DEBUG_DEFAULT_FLAGS
@@ -996,6 +1200,8 @@ TRACE(("install_team_debugger(team: %ld, port: %ld, default: %d, "
team->debug_info.debugger_team = debuggerTeam;
team->debug_info.debugger_port = debuggerPort;
RELEASE_TEAM_DEBUG_INFO_LOCK(team->debug_info);
// set the user debug flags of all threads to the default
GRAB_THREAD_LOCK();
@@ -1076,18 +1282,18 @@ _user_debugger(const char *message)
int
_user_disable_debugger(int state)
{
cpu_status cpuState = disable_interrupts();
GRAB_TEAM_LOCK();
struct team *team = thread_get_current_thread()->team;
team_debug_info &info = thread_get_current_thread()->team->debug_info;
cpu_status cpuState = disable_interrupts();
GRAB_TEAM_DEBUG_INFO_LOCK(team->debug_info);
int32 oldFlags;
if (state)
oldFlags = atomic_and(&info.flags, ~B_TEAM_DEBUG_SIGNALS);
oldFlags = atomic_and(&team->debug_info.flags, ~B_TEAM_DEBUG_SIGNALS);
else
oldFlags = atomic_or(&info.flags, B_TEAM_DEBUG_SIGNALS);
oldFlags = atomic_or(&team->debug_info.flags, B_TEAM_DEBUG_SIGNALS);
RELEASE_TEAM_LOCK();
RELEASE_TEAM_DEBUG_INFO_LOCK(team->debug_info);
restore_interrupts(cpuState);
// TODO: Check, if the return value is really the old state.
@@ -1138,14 +1344,18 @@ _user_remove_team_debugger(team_id teamID)
: team_get_team_struct_locked(teamID));
if (team) {
GRAB_TEAM_DEBUG_INFO_LOCK(team->debug_info);
if (team->debug_info.flags & B_TEAM_DEBUG_DEBUGGER_INSTALLED) {
// there's a debugger installed
info = team->debug_info;
clear_team_debug_info(&team->debug_info);
clear_team_debug_info(&team->debug_info, false);
} else {
// no debugger installed
error = B_BAD_VALUE;
}
RELEASE_TEAM_DEBUG_INFO_LOCK(team->debug_info);
} else
error = B_BAD_TEAM_ID;