diff --git a/headers/private/kernel/user_debugger.h b/headers/private/kernel/user_debugger.h index 3aa4118578..b1c8b2bc6a 100644 --- a/headers/private/kernel/user_debugger.h +++ b/headers/private/kernel/user_debugger.h @@ -9,16 +9,39 @@ #include +#include + +// 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 ) 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, diff --git a/src/kernel/core/user_debugger.cpp b/src/kernel/core/user_debugger.cpp index e6308591c1..9067fbadbc 100644 --- a/src/kernel/core/user_debugger.cpp +++ b/src/kernel/core/user_debugger.cpp @@ -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;