* Copyright 2005-2016, Ingo Weinhold, ingo_weinhold@gmx.de.
* Copyright 2015, Rene Gollent, rene@gollent.com.
* Distributed under the terms of the MIT License.
*/
#include <errno.h>
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <arch/debug.h>
#include <arch/user_debugger.h>
#include <core_dump.h>
#include <cpu.h>
#include <debugger.h>
#include <kernel.h>
#include <KernelExport.h>
#include <kscheduler.h>
#include <ksignal.h>
#include <ksyscalls.h>
#include <port.h>
#include <sem.h>
#include <team.h>
#include <thread.h>
#include <thread_types.h>
#include <user_debugger.h>
#include <vm/vm.h>
#include <vm/vm_types.h>
#include <AutoDeleter.h>
#include <util/AutoLock.h>
#include <util/ThreadAutoLock.h>
#include "BreakpointManager.h"
#ifdef TRACE_USER_DEBUGGER
# define TRACE(x) dprintf x
#else
# define TRACE(x) ;
#endif
static port_id sDefaultDebuggerPort = -1;
static timer sProfilingTimers[SMP_MAX_CPUS];
static void schedule_profiling_timer(Thread* thread, bigtime_t interval);
static int32 profiling_event(timer* unused);
static void profiling_flush(void*);
static status_t ensure_debugger_installed();
static void get_team_debug_info(team_debug_info &teamDebugInfo);
static inline status_t
kill_interruptable_write_port(port_id port, int32 code, const void *buffer,
size_t bufferSize)
{
return write_port_etc(port, code, buffer, bufferSize, B_KILL_CAN_INTERRUPT,
0);
}
static status_t
debugger_write(port_id port, int32 code, const void *buffer, size_t bufferSize,
bool dontWait)
{
TRACE(("debugger_write(): thread: %" B_PRId32 ", team %" B_PRId32 ", "
"port: %" B_PRId32 ", code: %" B_PRIx32 ", message: %p, size: %lu, "
"dontWait: %d\n", thread_get_current_thread()->id,
thread_get_current_thread()->team->id, port, code, buffer, bufferSize,
dontWait));
status_t error = B_OK;
team_debug_info teamDebugInfo;
get_team_debug_info(teamDebugInfo);
sem_id writeLock = teamDebugInfo.debugger_write_lock;
TRACE(("debugger_write(): acquiring write lock...\n"));
error = acquire_sem_etc(writeLock, 1,
dontWait ? (uint32)B_RELATIVE_TIMEOUT : (uint32)B_KILL_CAN_INTERRUPT, 0);
if (error != B_OK) {
TRACE(("debugger_write() done1: %" B_PRIx32 "\n", error));
return error;
}
get_team_debug_info(teamDebugInfo);
if (teamDebugInfo.debugger_port != port
|| (teamDebugInfo.flags & B_TEAM_DEBUG_DEBUGGER_HANDOVER)) {
TRACE(("debugger_write(): %s\n",
(teamDebugInfo.debugger_port != port ? "debugger port changed"
: "handover flag set")));
} else {
TRACE(("debugger_write(): writing to port...\n"));
error = write_port_etc(port, code, buffer, bufferSize,
dontWait ? (uint32)B_RELATIVE_TIMEOUT : (uint32)B_KILL_CAN_INTERRUPT, 0);
}
release_sem(writeLock);
TRACE(("debugger_write() done: %" B_PRIx32 "\n", error));
return error;
}
set for the thread.
Interrupts must be disabled and the thread's debug info lock must be held.
*/
static void
update_thread_user_debug_flag(Thread* thread)
{
if ((atomic_get(&thread->debug_info.flags) & B_THREAD_DEBUG_STOP) != 0)
atomic_or(&thread->flags, THREAD_FLAGS_DEBUG_THREAD);
else
atomic_and(&thread->flags, ~THREAD_FLAGS_DEBUG_THREAD);
}
given thread.
Interrupts must be disabled and the thread debug info lock must be held.
*/
static void
update_thread_breakpoints_flag(Thread* thread)
{
Team* team = thread->team;
if (arch_has_breakpoints(&team->debug_info.arch_info))
atomic_or(&thread->flags, THREAD_FLAGS_BREAKPOINTS_DEFINED);
else
atomic_and(&thread->flags, ~THREAD_FLAGS_BREAKPOINTS_DEFINED);
}
threads of the current team.
*/
static void
update_threads_breakpoints_flag()
{
Team* team = thread_get_current_thread()->team;
TeamLocker teamLocker(team);
if (arch_has_breakpoints(&team->debug_info.arch_info)) {
for (Thread* thread = team->thread_list.First(); thread != NULL;
thread = team->thread_list.GetNext(thread)) {
atomic_or(&thread->flags, THREAD_FLAGS_BREAKPOINTS_DEFINED);
}
} else {
for (Thread* thread = team->thread_list.First(); thread != NULL;
thread = team->thread_list.GetNext(thread)) {
atomic_and(&thread->flags, ~THREAD_FLAGS_BREAKPOINTS_DEFINED);
}
}
}
given thread, which must be the current thread.
*/
static void
update_thread_debugger_installed_flag(Thread* thread)
{
Team* team = thread->team;
if (atomic_get(&team->debug_info.flags) & B_TEAM_DEBUG_DEBUGGER_INSTALLED)
atomic_or(&thread->flags, THREAD_FLAGS_DEBUGGER_INSTALLED);
else
atomic_and(&thread->flags, ~THREAD_FLAGS_DEBUGGER_INSTALLED);
}
threads of the given team.
The team's lock must be held.
*/
static void
update_threads_debugger_installed_flag(Team* team)
{
if (atomic_get(&team->debug_info.flags) & B_TEAM_DEBUG_DEBUGGER_INSTALLED) {
for (Thread* thread = team->thread_list.First(); thread != NULL;
thread = team->thread_list.GetNext(thread)) {
atomic_or(&thread->flags, THREAD_FLAGS_DEBUGGER_INSTALLED);
}
} else {
for (Thread* thread = team->thread_list.First(); thread != NULL;
thread = team->thread_list.GetNext(thread)) {
atomic_and(&thread->flags, ~THREAD_FLAGS_DEBUGGER_INSTALLED);
}
}
}
* 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, 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;
info->debugger_write_lock = -1;
info->causing_thread = -1;
info->image_event = 0;
info->breakpoint_manager = NULL;
if (initLock) {
B_INITIALIZE_SPINLOCK(&info->lock);
info->debugger_changed_condition = NULL;
}
}
}
* `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 `lock'
* 2. copy the team debug info on stack
* 3. call clear_team_debug_info() on the team debug info
* 4. release `lock'
* 5. call destroy_team_debug_info() on the copied team debug info
*/
static void
destroy_team_debug_info(struct team_debug_info *info)
{
if (info) {
arch_destroy_team_debug_info(&info->arch_info);
delete info->breakpoint_manager ;
info->breakpoint_manager = NULL;
if (info->debugger_write_lock >= 0) {
delete_sem(info->debugger_write_lock);
info->debugger_write_lock = -1;
}
if (info->nub_port >= 0) {
set_port_owner(info->nub_port, B_CURRENT_TEAM);
delete_port(info->nub_port);
info->nub_port = -1;
}
if (info->nub_thread >= 0) {
if (info->nub_thread != thread_get_current_thread()->id) {
int32 result;
wait_for_thread(info->nub_thread, &result);
}
info->nub_thread = -1;
}
atomic_set(&info->flags, 0);
info->debugger_team = -1;
info->debugger_port = -1;
info->causing_thread = -1;
info->image_event = -1;
}
}
void
init_thread_debug_info(struct thread_debug_info *info)
{
if (info) {
B_INITIALIZE_SPINLOCK(&info->lock);
arch_clear_thread_debug_info(&info->arch_info);
info->flags = B_THREAD_DEBUG_DEFAULT_FLAGS;
info->debug_port = -1;
info->ignore_signals = 0;
info->ignore_signals_once = 0;
info->profile.sample_area = -1;
info->profile.interval = 0;
info->profile.samples = NULL;
info->profile.flush_needed = false;
info->profile.installed_timer = NULL;
}
}
Invoked with thread debug info lock being held.
*/
void
clear_thread_debug_info(struct thread_debug_info *info, bool dying)
{
if (info) {
if (info->profile.installed_timer != NULL) {
cancel_timer(info->profile.installed_timer);
info->profile.installed_timer->hook = NULL;
info->profile.installed_timer = NULL;
}
arch_clear_thread_debug_info(&info->arch_info);
atomic_set(&info->flags,
B_THREAD_DEBUG_DEFAULT_FLAGS | (dying ? B_THREAD_DEBUG_DYING : 0));
info->debug_port = -1;
info->ignore_signals = 0;
info->ignore_signals_once = 0;
info->profile.sample_area = -1;
info->profile.interval = 0;
info->profile.samples = NULL;
info->profile.flush_needed = false;
}
}
void
destroy_thread_debug_info(struct thread_debug_info *info)
{
if (info) {
area_id sampleArea = info->profile.sample_area;
if (sampleArea >= 0) {
area_info areaInfo;
if (get_area_info(sampleArea, &areaInfo) == B_OK) {
unlock_memory(areaInfo.address, areaInfo.size, B_READ_DEVICE);
delete_area(sampleArea);
}
}
arch_destroy_thread_debug_info(&info->arch_info);
if (info->debug_port >= 0) {
delete_port(info->debug_port);
info->debug_port = -1;
}
info->ignore_signals = 0;
info->ignore_signals_once = 0;
atomic_set(&info->flags, 0);
}
}
static status_t
prepare_debugger_change(team_id teamID, ConditionVariable& condition,
Team*& team)
{
if (teamID == B_CURRENT_TEAM)
teamID = thread_get_current_thread()->team->id;
while (true) {
team = Team::GetAndLock(teamID);
if (team == NULL)
return B_BAD_TEAM_ID;
BReference<Team> teamReference(team, true);
TeamLocker teamLocker(team, true);
if (team == team_get_kernel_team())
return B_NOT_ALLOWED;
InterruptsSpinLocker debugInfoLocker(team->debug_info.lock);
if (team->debug_info.debugger_changed_condition == NULL) {
team->debug_info.debugger_changed_condition = &condition;
return B_OK;
}
ConditionVariableEntry entry;
team->debug_info.debugger_changed_condition->Add(&entry);
debugInfoLocker.Unlock();
teamLocker.Unlock();
entry.Wait();
}
}
static void
prepare_debugger_change(Team* team, ConditionVariable& condition)
{
while (true) {
InterruptsSpinLocker debugInfoLocker(team->debug_info.lock);
if (team->debug_info.debugger_changed_condition == NULL) {
team->debug_info.debugger_changed_condition = &condition;
return;
}
ConditionVariableEntry entry;
team->debug_info.debugger_changed_condition->Add(&entry);
debugInfoLocker.Unlock();
entry.Wait();
}
}
static void
finish_debugger_change(Team* team)
{
InterruptsSpinLocker debugInfoLocker(team->debug_info.lock);
ConditionVariable* condition = team->debug_info.debugger_changed_condition;
team->debug_info.debugger_changed_condition = NULL;
condition->NotifyAll();
}
void
user_debug_prepare_for_exec()
{
Thread *thread = thread_get_current_thread();
Team *team = thread->team;
if (atomic_get(&team->debug_info.flags) & B_TEAM_DEBUG_DEBUGGER_INSTALLED) {
port_id debugPort = -1;
InterruptsSpinLocker threadDebugInfoLocker(thread->debug_info.lock);
if ((thread->debug_info.flags & B_THREAD_DEBUG_INITIALIZED) != 0)
debugPort = thread->debug_info.debug_port;
threadDebugInfoLocker.Unlock();
if (debugPort >= 0)
set_port_owner(debugPort, team_get_kernel_team_id());
}
}
void
user_debug_finish_after_exec()
{
Thread *thread = thread_get_current_thread();
Team *team = thread->team;
if (atomic_get(&team->debug_info.flags) & B_TEAM_DEBUG_DEBUGGER_INSTALLED) {
port_id debugPort = -1;
InterruptsSpinLocker threadDebugInfoLocker(thread->debug_info.lock);
if (thread->debug_info.flags & B_THREAD_DEBUG_INITIALIZED)
debugPort = thread->debug_info.debug_port;
threadDebugInfoLocker.Unlock();
if (debugPort >= 0)
set_port_owner(debugPort, team->id);
}
}
void
init_user_debug()
{
#ifdef ARCH_INIT_USER_DEBUG
ARCH_INIT_USER_DEBUG();
#endif
}
static void
get_team_debug_info(team_debug_info &teamDebugInfo)
{
Thread *thread = thread_get_current_thread();
cpu_status state = disable_interrupts();
GRAB_TEAM_DEBUG_INFO_LOCK(thread->team->debug_info);
memcpy(&teamDebugInfo, &thread->team->debug_info, sizeof(team_debug_info));
RELEASE_TEAM_DEBUG_INFO_LOCK(thread->team->debug_info);
restore_interrupts(state);
}
static status_t
thread_hit_debug_event_internal(debug_debugger_message event,
const void *message, int32 size, bool requireDebugger, bool &restart)
{
restart = false;
Thread *thread = thread_get_current_thread();
TRACE(("thread_hit_debug_event(): thread: %" B_PRId32 ", event: %" B_PRIu32
", message: %p, size: %" B_PRId32 "\n", thread->id, (uint32)event,
message, size));
bool setPort = !(atomic_get(&thread->debug_info.flags)
& B_THREAD_DEBUG_INITIALIZED);
port_id port = -1;
if (setPort) {
char nameBuffer[128];
snprintf(nameBuffer, sizeof(nameBuffer), "nub to thread %" B_PRId32,
thread->id);
port = create_port(1, nameBuffer);
if (port < 0) {
dprintf("thread_hit_debug_event(): Failed to create debug port: "
"%s\n", strerror(port));
return port;
}
}
port_id deletePort = port;
port_id debuggerPort = -1;
port_id nubPort = -1;
status_t error = B_OK;
cpu_status state = disable_interrupts();
GRAB_TEAM_DEBUG_INFO_LOCK(thread->team->debug_info);
SpinLocker threadDebugInfoLocker(thread->debug_info.lock);
uint32 threadFlags = thread->debug_info.flags;
threadFlags &= ~B_THREAD_DEBUG_STOP;
bool debuggerInstalled
= (thread->team->debug_info.flags & B_TEAM_DEBUG_DEBUGGER_INSTALLED);
if (thread->id == thread->team->debug_info.nub_thread) {
TRACE(("thread_hit_debug_event(): Misdirected nub thread: %" B_PRId32
"\n", thread->id));
error = B_ERROR;
} else if (debuggerInstalled || !requireDebugger) {
if (debuggerInstalled) {
debuggerPort = thread->team->debug_info.debugger_port;
nubPort = thread->team->debug_info.nub_port;
}
if (setPort) {
if (threadFlags & B_THREAD_DEBUG_INITIALIZED) {
port = thread->debug_info.debug_port;
} else {
thread->debug_info.debug_port = port;
deletePort = -1;
threadFlags |= B_THREAD_DEBUG_INITIALIZED;
}
} else {
if (threadFlags & B_THREAD_DEBUG_INITIALIZED) {
port = thread->debug_info.debug_port;
} else {
error = B_ERROR;
}
}
} else
error = B_ERROR;
if (error == B_OK)
threadFlags |= B_THREAD_DEBUG_STOPPED;
atomic_set(&thread->debug_info.flags, threadFlags);
update_thread_user_debug_flag(thread);
threadDebugInfoLocker.Unlock();
RELEASE_TEAM_DEBUG_INFO_LOCK(thread->team->debug_info);
restore_interrupts(state);
if (deletePort >= 0)
delete_port(deletePort);
if (error != B_OK) {
TRACE(("thread_hit_debug_event() error: thread: %" B_PRId32 ", error: "
"%" B_PRIx32 "\n", thread->id, error));
return error;
}
if (debuggerInstalled) {
debug_origin *origin = (debug_origin *)message;
origin->thread = thread->id;
origin->team = thread->team->id;
origin->nub_port = nubPort;
TRACE(("thread_hit_debug_event(): thread: %" B_PRId32 ", sending "
"message to debugger port %" B_PRId32 "\n", thread->id,
debuggerPort));
error = debugger_write(debuggerPort, event, message, size, false);
}
status_t result = B_THREAD_DEBUG_HANDLE_EVENT;
bool singleStep = false;
if (error == B_OK) {
bool done = false;
while (!done) {
int32 command;
debugged_thread_message_data commandMessage;
ssize_t commandMessageSize = read_port_etc(port, &command,
&commandMessage, sizeof(commandMessage), B_KILL_CAN_INTERRUPT,
0);
if (commandMessageSize < 0) {
error = commandMessageSize;
TRACE(("thread_hit_debug_event(): thread: %" B_PRId32 ", failed "
"to receive message from port %" B_PRId32 ": %" B_PRIx32 "\n",
thread->id, port, error));
break;
}
switch (command) {
case B_DEBUGGED_THREAD_MESSAGE_CONTINUE:
TRACE(("thread_hit_debug_event(): thread: %" B_PRId32 ": "
"B_DEBUGGED_THREAD_MESSAGE_CONTINUE\n",
thread->id));
result = commandMessage.continue_thread.handle_event;
singleStep = commandMessage.continue_thread.single_step;
done = true;
break;
case B_DEBUGGED_THREAD_SET_CPU_STATE:
{
TRACE(("thread_hit_debug_event(): thread: %" B_PRId32 ": "
"B_DEBUGGED_THREAD_SET_CPU_STATE\n",
thread->id));
arch_set_debug_cpu_state(
&commandMessage.set_cpu_state.cpu_state);
break;
}
case B_DEBUGGED_THREAD_GET_CPU_STATE:
{
port_id replyPort = commandMessage.get_cpu_state.reply_port;
debug_nub_get_cpu_state_reply replyMessage;
replyMessage.error = B_OK;
replyMessage.message = event;
arch_get_debug_cpu_state(&replyMessage.cpu_state);
error = kill_interruptable_write_port(replyPort, event,
&replyMessage, sizeof(replyMessage));
break;
}
case B_DEBUGGED_THREAD_DEBUGGER_CHANGED:
{
team_debug_info teamDebugInfo;
get_team_debug_info(teamDebugInfo);
if (teamDebugInfo.flags & B_TEAM_DEBUG_DEBUGGER_INSTALLED) {
if (!debuggerInstalled
|| teamDebugInfo.debugger_port != debuggerPort) {
restart = true;
done = true;
}
} else {
if (debuggerInstalled) {
done = true;
}
}
break;
}
}
}
} else {
TRACE(("thread_hit_debug_event(): thread: %" B_PRId32 ", failed to send "
"message to debugger port %" B_PRId32 ": %" B_PRIx32 "\n",
thread->id, debuggerPort, error));
}
bool destroyThreadInfo = false;
thread_debug_info threadDebugInfo;
state = disable_interrupts();
threadDebugInfoLocker.Lock();
int32 teamDebugFlags = atomic_get(&thread->team->debug_info.flags);
if (teamDebugFlags & B_TEAM_DEBUG_DEBUGGER_INSTALLED) {
if (singleStep) {
atomic_or(&thread->debug_info.flags,
B_THREAD_DEBUG_SINGLE_STEP);
atomic_or(&thread->flags, THREAD_FLAGS_SINGLE_STEP);
} else {
atomic_and(&thread->debug_info.flags,
~(int32)B_THREAD_DEBUG_SINGLE_STEP);
}
atomic_and(&thread->debug_info.flags, ~B_THREAD_DEBUG_STOPPED);
update_thread_user_debug_flag(thread);
} else {
threadDebugInfo = thread->debug_info;
clear_thread_debug_info(&thread->debug_info, false);
destroyThreadInfo = true;
}
threadDebugInfoLocker.Unlock();
restore_interrupts(state);
arch_update_thread_single_step();
if (destroyThreadInfo)
destroy_thread_debug_info(&threadDebugInfo);
return (error == B_OK ? result : error);
}
static status_t
thread_hit_debug_event(debug_debugger_message event, const void *message,
int32 size, bool requireDebugger)
{
status_t result;
bool restart;
do {
restart = false;
result = thread_hit_debug_event_internal(event, message, size,
requireDebugger, restart);
} while (result >= 0 && restart);
Team* team = thread_get_current_thread()->team;
ConditionVariable debugChangeCondition;
debugChangeCondition.Init(team, "debug change condition");
prepare_debugger_change(team, debugChangeCondition);
if (team->debug_info.breakpoint_manager != NULL) {
bool isSyscall;
void* pc = arch_debug_get_interrupt_pc(&isSyscall);
if (pc != NULL && !isSyscall)
team->debug_info.breakpoint_manager->PrepareToContinue(pc);
}
finish_debugger_change(team);
return result;
}
static status_t
thread_hit_serious_debug_event(debug_debugger_message event,
const void *message, int32 messageSize)
{
status_t error = ensure_debugger_installed();
if (error != B_OK) {
Thread *thread = thread_get_current_thread();
dprintf("thread_hit_serious_debug_event(): Failed to install debugger: "
"thread: %" B_PRId32 " (%s): %s\n", thread->id, thread->name,
strerror(error));
return error;
}
return thread_hit_debug_event(event, message, messageSize, true);
}
void
user_debug_pre_syscall(uint32 syscall, void *args)
{
Thread *thread = thread_get_current_thread();
int32 teamDebugFlags = atomic_get(&thread->team->debug_info.flags);
if (!(teamDebugFlags & B_TEAM_DEBUG_DEBUGGER_INSTALLED))
return;
int32 threadDebugFlags = atomic_get(&thread->debug_info.flags);
if ((teamDebugFlags & B_TEAM_DEBUG_PRE_SYSCALL)
|| (threadDebugFlags & B_THREAD_DEBUG_PRE_SYSCALL)) {
debug_pre_syscall message;
message.syscall = syscall;
if (syscall < (uint32)kSyscallCount) {
if (kSyscallInfos[syscall].parameter_size > 0)
memcpy(message.args, args, kSyscallInfos[syscall].parameter_size);
}
thread_hit_debug_event(B_DEBUGGER_MESSAGE_PRE_SYSCALL, &message,
sizeof(message), true);
}
if ((teamDebugFlags & B_TEAM_DEBUG_POST_SYSCALL)
|| (threadDebugFlags & B_THREAD_DEBUG_POST_SYSCALL)) {
if (thread->debug_info.profile.samples == NULL)
thread->debug_info.profile.syscall_start_time = system_time();
}
}
void
user_debug_post_syscall(uint32 syscall, void *args, uint64 returnValue)
{
Thread *thread = thread_get_current_thread();
int32 teamDebugFlags = atomic_get(&thread->team->debug_info.flags);
if (!(teamDebugFlags & B_TEAM_DEBUG_DEBUGGER_INSTALLED))
return;
if (thread->debug_info.profile.flush_needed)
profiling_flush(NULL);
int32 threadDebugFlags = atomic_get(&thread->debug_info.flags);
if (!(teamDebugFlags & B_TEAM_DEBUG_POST_SYSCALL)
&& !(threadDebugFlags & B_THREAD_DEBUG_POST_SYSCALL)) {
return;
}
bigtime_t startTime = 0;
if (thread->debug_info.profile.samples == NULL) {
startTime = thread->debug_info.profile.syscall_start_time;
thread->debug_info.profile.syscall_start_time = 0;
}
debug_post_syscall message;
message.start_time = startTime;
message.end_time = system_time();
message.return_value = returnValue;
message.syscall = syscall;
if (syscall < (uint32)kSyscallCount) {
if (kSyscallInfos[syscall].parameter_size > 0)
memcpy(message.args, args, kSyscallInfos[syscall].parameter_size);
}
thread_hit_debug_event(B_DEBUGGER_MESSAGE_POST_SYSCALL, &message,
sizeof(message), true);
}
* occurred.
* \param exception The debug_why_stopped value identifying the kind of fault.
* \param signal The signal corresponding to the exception.
* \return \c true, if the caller shall continue normally, i.e. usually send
* a deadly signal. \c false, if the debugger insists to continue the
* program (e.g. because it has solved the removed the cause of the
* problem).
*/
bool
user_debug_exception_occurred(debug_exception_type exception, int signal)
{
struct sigaction signalAction;
if (sigaction(signal, NULL, &signalAction) == 0
&& signalAction.sa_handler != SIG_DFL) {
return true;
}
debug_exception_occurred message;
message.exception = exception;
message.signal = signal;
status_t result = thread_hit_serious_debug_event(
B_DEBUGGER_MESSAGE_EXCEPTION_OCCURRED, &message, sizeof(message));
return (result != B_THREAD_DEBUG_IGNORE_EVENT);
}
bool
user_debug_handle_signal(int signal, struct sigaction *handler, siginfo_t *info,
bool deadly)
{
Thread *thread = thread_get_current_thread();
int32 teamDebugFlags = atomic_get(&thread->team->debug_info.flags);
if (~teamDebugFlags
& (B_TEAM_DEBUG_DEBUGGER_INSTALLED | B_TEAM_DEBUG_SIGNALS)) {
return true;
}
debug_signal_received message;
message.signal = signal;
message.handler = *handler;
message.info = *info;
message.deadly = deadly;
status_t result = thread_hit_debug_event(B_DEBUGGER_MESSAGE_SIGNAL_RECEIVED,
&message, sizeof(message), true);
return (result != B_THREAD_DEBUG_IGNORE_EVENT);
}
void
user_debug_stop_thread()
{
Thread* thread = thread_get_current_thread();
InterruptsSpinLocker threadDebugInfoLocker(thread->debug_info.lock);
bool singleStepped = false;
if ((atomic_and(&thread->debug_info.flags,
~B_THREAD_DEBUG_NOTIFY_SINGLE_STEP)
& B_THREAD_DEBUG_NOTIFY_SINGLE_STEP) != 0) {
singleStepped = true;
}
threadDebugInfoLocker.Unlock();
if (singleStepped) {
user_debug_single_stepped();
} else {
debug_thread_debugged message;
thread_hit_serious_debug_event(B_DEBUGGER_MESSAGE_THREAD_DEBUGGED,
&message, sizeof(message));
}
}
void
user_debug_team_created(team_id teamID)
{
Thread *thread = thread_get_current_thread();
int32 teamDebugFlags = atomic_get(&thread->team->debug_info.flags);
if (~teamDebugFlags
& (B_TEAM_DEBUG_DEBUGGER_INSTALLED | B_TEAM_DEBUG_TEAM_CREATION)) {
return;
}
debug_team_created message;
message.new_team = teamID;
thread_hit_debug_event(B_DEBUGGER_MESSAGE_TEAM_CREATED, &message,
sizeof(message), true);
}
void
user_debug_team_deleted(team_id teamID, port_id debuggerPort, status_t status, int signal,
team_usage_info* usageInfo)
{
if (debuggerPort >= 0) {
TRACE(("user_debug_team_deleted(team: %" B_PRId32 ", debugger port: "
"%" B_PRId32 ")\n", teamID, debuggerPort));
debug_team_deleted message;
message.origin.thread = -1;
message.origin.team = teamID;
message.origin.nub_port = -1;
message.status = status;
message.signal = signal;
message.usage = *usageInfo;
write_port_etc(debuggerPort, B_DEBUGGER_MESSAGE_TEAM_DELETED, &message,
sizeof(message), B_RELATIVE_TIMEOUT, 0);
}
}
void
user_debug_team_exec()
{
Thread *thread = thread_get_current_thread();
int32 teamDebugFlags = atomic_get(&thread->team->debug_info.flags);
if (~teamDebugFlags
& (B_TEAM_DEBUG_DEBUGGER_INSTALLED | B_TEAM_DEBUG_TEAM_CREATION)) {
return;
}
debug_team_exec message;
message.image_event = atomic_add(&thread->team->debug_info.image_event, 1)
+ 1;
thread_hit_debug_event(B_DEBUGGER_MESSAGE_TEAM_EXEC, &message,
sizeof(message), true);
}
\c Thread::flags before the thread first enters userland.
\param thread The calling thread.
*/
void
user_debug_update_new_thread_flags(Thread* thread)
{
InterruptsSpinLocker threadDebugInfoLocker(thread->debug_info.lock);
update_thread_user_debug_flag(thread);
update_thread_breakpoints_flag(thread);
update_thread_debugger_installed_flag(thread);
}
void
user_debug_thread_created(thread_id threadID)
{
Thread *thread = thread_get_current_thread();
int32 teamDebugFlags = atomic_get(&thread->team->debug_info.flags);
if (~teamDebugFlags
& (B_TEAM_DEBUG_DEBUGGER_INSTALLED | B_TEAM_DEBUG_THREADS)) {
return;
}
debug_thread_created message;
message.new_thread = threadID;
thread_hit_debug_event(B_DEBUGGER_MESSAGE_THREAD_CREATED, &message,
sizeof(message), true);
}
void
user_debug_thread_deleted(team_id teamID, thread_id threadID, status_t status)
{
Team* team = Team::Get(teamID);
if (team == NULL)
return;
BReference<Team> teamReference(team, true);
InterruptsSpinLocker debugInfoLocker(team->debug_info.lock);
int32 teamDebugFlags = atomic_get(&team->debug_info.flags);
port_id debuggerPort = team->debug_info.debugger_port;
sem_id writeLock = team->debug_info.debugger_write_lock;
debugInfoLocker.Unlock();
if (~teamDebugFlags
& (B_TEAM_DEBUG_DEBUGGER_INSTALLED | B_TEAM_DEBUG_THREADS)) {
return;
}
status_t error = acquire_sem_etc(writeLock, 1, B_KILL_CAN_INTERRUPT, 0);
if (error != B_OK)
return;
debugInfoLocker.Lock();
teamDebugFlags = atomic_get(&team->debug_info.flags);
port_id newDebuggerPort = team->debug_info.debugger_port;
debugInfoLocker.Unlock();
if (newDebuggerPort == debuggerPort
|| (teamDebugFlags & B_TEAM_DEBUG_DEBUGGER_HANDOVER) == 0) {
debug_thread_deleted message;
message.origin.thread = threadID;
message.origin.team = teamID;
message.origin.nub_port = -1;
message.status = status;
write_port_etc(debuggerPort, B_DEBUGGER_MESSAGE_THREAD_DELETED,
&message, sizeof(message), B_KILL_CAN_INTERRUPT, 0);
}
release_sem(writeLock);
}
facilities installed for the thread.
\param thread The current thread, the one that is going to die.
*/
void
user_debug_thread_exiting(Thread* thread)
{
Team* team = thread->team;
InterruptsLocker interruptsLocker;
GRAB_TEAM_DEBUG_INFO_LOCK(team->debug_info);
int32 teamDebugFlags = atomic_get(&team->debug_info.flags);
port_id debuggerPort = team->debug_info.debugger_port;
RELEASE_TEAM_DEBUG_INFO_LOCK(team->debug_info);
if ((teamDebugFlags & B_TEAM_DEBUG_DEBUGGER_INSTALLED) == 0
|| debuggerPort < 0) {
return;
}
SpinLocker threadDebugInfoLocker(thread->debug_info.lock);
thread_debug_info& threadDebugInfo = thread->debug_info;
if (threadDebugInfo.profile.samples == NULL)
return;
area_id sampleArea = threadDebugInfo.profile.sample_area;
int32 sampleCount = threadDebugInfo.profile.sample_count;
int32 droppedTicks = threadDebugInfo.profile.dropped_ticks;
int32 stackDepth = threadDebugInfo.profile.stack_depth;
bool variableStackDepth = threadDebugInfo.profile.variable_stack_depth;
int32 imageEvent = threadDebugInfo.profile.image_event;
threadDebugInfo.profile.sample_area = -1;
threadDebugInfo.profile.samples = NULL;
threadDebugInfo.profile.flush_needed = false;
bigtime_t lastCPUTime; {
SpinLocker threadTimeLocker(thread->time_lock);
lastCPUTime = thread->CPUTime(false);
}
atomic_or(&threadDebugInfo.flags, B_THREAD_DEBUG_DYING);
threadDebugInfoLocker.Unlock();
interruptsLocker.Unlock();
debug_profiler_update message;
message.origin.thread = thread->id;
message.origin.team = thread->team->id;
message.origin.nub_port = -1;
message.sample_count = sampleCount;
message.dropped_ticks = droppedTicks;
message.stack_depth = stackDepth;
message.variable_stack_depth = variableStackDepth;
message.image_event = imageEvent;
message.stopped = true;
message.last_cpu_time = lastCPUTime;
debugger_write(debuggerPort, B_DEBUGGER_MESSAGE_PROFILER_UPDATE,
&message, sizeof(message), false);
if (sampleArea >= 0) {
area_info areaInfo;
if (get_area_info(sampleArea, &areaInfo) == B_OK) {
unlock_memory(areaInfo.address, areaInfo.size, B_READ_DEVICE);
delete_area(sampleArea);
}
}
}
void
user_debug_image_created(const image_info *imageInfo)
{
Thread *thread = thread_get_current_thread();
int32 teamDebugFlags = atomic_get(&thread->team->debug_info.flags);
if (~teamDebugFlags
& (B_TEAM_DEBUG_DEBUGGER_INSTALLED | B_TEAM_DEBUG_IMAGES)) {
return;
}
debug_image_created message;
memcpy(&message.info, imageInfo, sizeof(image_info));
message.image_event = atomic_add(&thread->team->debug_info.image_event, 1)
+ 1;
thread_hit_debug_event(B_DEBUGGER_MESSAGE_IMAGE_CREATED, &message,
sizeof(message), true);
}
void
user_debug_image_deleted(const image_info *imageInfo)
{
Thread *thread = thread_get_current_thread();
int32 teamDebugFlags = atomic_get(&thread->team->debug_info.flags);
if (~teamDebugFlags
& (B_TEAM_DEBUG_DEBUGGER_INSTALLED | B_TEAM_DEBUG_IMAGES)) {
return;
}
debug_image_deleted message;
memcpy(&message.info, imageInfo, sizeof(image_info));
message.image_event = atomic_add(&thread->team->debug_info.image_event, 1)
+ 1;
thread_hit_debug_event(B_DEBUGGER_MESSAGE_IMAGE_DELETED, &message,
sizeof(message), true);
}
void
user_debug_breakpoint_hit(bool software)
{
debug_breakpoint_hit message;
arch_get_debug_cpu_state(&message.cpu_state);
thread_hit_serious_debug_event(B_DEBUGGER_MESSAGE_BREAKPOINT_HIT, &message,
sizeof(message));
}
void
user_debug_watchpoint_hit()
{
debug_watchpoint_hit message;
arch_get_debug_cpu_state(&message.cpu_state);
thread_hit_serious_debug_event(B_DEBUGGER_MESSAGE_WATCHPOINT_HIT, &message,
sizeof(message));
}
void
user_debug_single_stepped()
{
Thread* thread = thread_get_current_thread();
atomic_and(&thread->flags, ~(int32)THREAD_FLAGS_SINGLE_STEP);
debug_single_step message;
arch_get_debug_cpu_state(&message.cpu_state);
thread_hit_serious_debug_event(B_DEBUGGER_MESSAGE_SINGLE_STEP, &message,
sizeof(message));
}
The caller must hold the thread's debug info lock.
\param thread The current thread.
\param interval The time after which the timer should fire.
*/
static void
schedule_profiling_timer(Thread* thread, bigtime_t interval)
{
struct timer* timer = &sProfilingTimers[thread->cpu->cpu_num];
ASSERT(timer->hook == NULL);
thread->debug_info.profile.installed_timer = timer;
thread->debug_info.profile.timer_end = system_time() + interval;
add_timer(timer, &profiling_event, interval, B_ONE_SHOT_RELATIVE_TIMER);
}
The caller must hold the thread's debug info lock.
\param thread The current thread.
*/
static bigtime_t
profiling_timer_left(Thread* thread)
{
return thread->debug_info.profile.timer_end - system_time();
}
The caller must hold the current thread's debug info lock.
\returns Whether the profiling timer should be rescheduled.
*/
static bool
profiling_do_sample()
{
Thread* thread = thread_get_current_thread();
thread_debug_info& debugInfo = thread->debug_info;
if (debugInfo.profile.samples == NULL)
return false;
int32 maxSamples = debugInfo.profile.max_samples;
int32 sampleCount = debugInfo.profile.sample_count;
int32 stackDepth = debugInfo.profile.stack_depth;
int32 imageEvent = thread->team->debug_info.image_event;
if (debugInfo.profile.sample_count > 0) {
if (debugInfo.profile.last_image_event < imageEvent
&& debugInfo.profile.variable_stack_depth
&& sampleCount + 2 <= maxSamples) {
addr_t* event = debugInfo.profile.samples + sampleCount;
event[0] = B_DEBUG_PROFILE_IMAGE_EVENT;
event[1] = imageEvent;
sampleCount += 2;
debugInfo.profile.sample_count = sampleCount;
debugInfo.profile.last_image_event = imageEvent;
}
if (debugInfo.profile.last_image_event < imageEvent
|| debugInfo.profile.flush_threshold - sampleCount < stackDepth) {
debugInfo.profile.flush_needed = true;
if (maxSamples - sampleCount < stackDepth) {
debugInfo.profile.dropped_ticks++;
return true;
}
}
} else {
debugInfo.profile.image_event = imageEvent;
debugInfo.profile.last_image_event = imageEvent;
}
uint32 flags = STACK_TRACE_USER;
int32 skipIFrames = 0;
if (debugInfo.profile.profile_kernel) {
flags |= STACK_TRACE_KERNEL;
skipIFrames = 1;
}
addr_t* returnAddresses = debugInfo.profile.samples
+ debugInfo.profile.sample_count;
if (debugInfo.profile.variable_stack_depth) {
*returnAddresses = arch_debug_get_stack_trace(returnAddresses + 1,
stackDepth - 1, skipIFrames, 0, flags);
debugInfo.profile.sample_count += *returnAddresses + 1;
} else {
if (stackDepth > 1 || !debugInfo.profile.profile_kernel) {
int32 count = arch_debug_get_stack_trace(returnAddresses,
stackDepth, skipIFrames, 0, flags);
for (int32 i = count; i < stackDepth; i++)
returnAddresses[i] = 0;
} else
*returnAddresses = (addr_t)arch_debug_get_interrupt_pc(NULL);
debugInfo.profile.sample_count += stackDepth;
}
return true;
}
static void
profiling_flush(void*)
{
disable_interrupts();
Thread* thread = thread_get_current_thread();
thread_debug_info& debugInfo = thread->debug_info;
SpinLocker threadDebugInfoLocker(debugInfo.lock);
if (debugInfo.profile.samples != NULL && debugInfo.profile.flush_needed) {
int32 sampleCount = debugInfo.profile.sample_count;
int32 droppedTicks = debugInfo.profile.dropped_ticks;
int32 stackDepth = debugInfo.profile.stack_depth;
bool variableStackDepth = debugInfo.profile.variable_stack_depth;
int32 imageEvent = debugInfo.profile.image_event;
bigtime_t interval = debugInfo.profile.interval;
if (debugInfo.profile.installed_timer != NULL) {
interval = max_c(profiling_timer_left(thread), 0);
cancel_timer(debugInfo.profile.installed_timer);
debugInfo.profile.installed_timer->hook = NULL;
debugInfo.profile.installed_timer = NULL;
}
debugInfo.profile.interval_left = -1;
debugInfo.profile.sample_count = 0;
debugInfo.profile.dropped_ticks = 0;
debugInfo.profile.flush_needed = false;
threadDebugInfoLocker.Unlock();
enable_interrupts();
debug_profiler_update message;
message.sample_count = sampleCount;
message.dropped_ticks = droppedTicks;
message.stack_depth = stackDepth;
message.variable_stack_depth = variableStackDepth;
message.image_event = imageEvent;
message.stopped = false;
thread_hit_debug_event(B_DEBUGGER_MESSAGE_PROFILER_UPDATE, &message,
sizeof(message), false);
disable_interrupts();
threadDebugInfoLocker.Lock();
if (debugInfo.profile.samples != NULL)
schedule_profiling_timer(thread, interval);
}
threadDebugInfoLocker.Unlock();
enable_interrupts();
}
Called with interrupts disabled.
*/
static int32
profiling_event(timer* )
{
Thread* thread = thread_get_current_thread();
thread_debug_info& debugInfo = thread->debug_info;
SpinLocker threadDebugInfoLocker(debugInfo.lock);
debugInfo.profile.installed_timer->hook = NULL;
debugInfo.profile.installed_timer = NULL;
if (profiling_do_sample()) {
if (debugInfo.profile.flush_needed
&& !IS_KERNEL_ADDRESS(arch_debug_get_interrupt_pc(NULL))) {
thread->post_interrupt_callback = profiling_flush;
debugInfo.profile.interval_left = -1;
} else
schedule_profiling_timer(thread, debugInfo.profile.interval);
}
return B_HANDLED_INTERRUPT;
}
The scheduler lock is being held.
*/
void
user_debug_thread_unscheduled(Thread* thread)
{
SpinLocker threadDebugInfoLocker(thread->debug_info.lock);
struct timer* timer = thread->debug_info.profile.installed_timer;
if (timer != NULL) {
bigtime_t left = profiling_timer_left(thread);
thread->debug_info.profile.interval_left = max_c(left, 0);
thread->debug_info.profile.installed_timer->hook = NULL;
thread->debug_info.profile.installed_timer = NULL;
threadDebugInfoLocker.Unlock();
cancel_timer(timer);
}
}
The scheduler lock is being held.
*/
void
user_debug_thread_scheduled(Thread* thread)
{
SpinLocker threadDebugInfoLocker(thread->debug_info.lock);
if (thread->debug_info.profile.samples != NULL
&& thread->debug_info.profile.interval_left >= 0) {
schedule_profiling_timer(thread,
thread->debug_info.profile.interval_left);
}
}
all threads of the team that are initialized for debugging (and
thus have a debug port).
*/
static void
broadcast_debugged_thread_message(Thread *nubThread, int32 code,
const void *message, int32 size)
{
thread_info threadInfo;
int32 cookie = 0;
while (get_next_thread_info(nubThread->team->id, &cookie, &threadInfo)
== B_OK) {
Thread* thread = Thread::GetAndLock(threadInfo.thread);
if (thread == NULL)
continue;
BReference<Thread> threadReference(thread, true);
ThreadLocker threadLocker(thread, true);
InterruptsSpinLocker threadDebugInfoLocker(thread->debug_info.lock);
port_id threadDebugPort = -1;
if (thread && thread != nubThread && thread->team == nubThread->team
&& (thread->debug_info.flags & B_THREAD_DEBUG_INITIALIZED) != 0
&& (thread->debug_info.flags & B_THREAD_DEBUG_STOPPED) != 0) {
threadDebugPort = thread->debug_info.debug_port;
}
threadDebugInfoLocker.Unlock();
threadLocker.Unlock();
if (threadDebugPort >= 0) {
status_t error = kill_interruptable_write_port(threadDebugPort,
code, message, size);
if (error != B_OK) {
TRACE(("broadcast_debugged_thread_message(): Failed to send "
"message to thread %" B_PRId32 ": %" B_PRIx32 "\n",
thread->id, error));
}
}
}
}
static void
nub_thread_cleanup(Thread *nubThread)
{
TRACE(("nub_thread_cleanup(%" B_PRId32 "): debugger port: %" B_PRId32 "\n",
nubThread->id, nubThread->team->debug_info.debugger_port));
ConditionVariable debugChangeCondition;
debugChangeCondition.Init(nubThread->team, "debug change condition");
prepare_debugger_change(nubThread->team, debugChangeCondition);
team_debug_info teamDebugInfo;
bool destroyDebugInfo = false;
TeamLocker teamLocker(nubThread->team);
cpu_status state = disable_interrupts();
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, false);
destroyDebugInfo = true;
}
update_threads_debugger_installed_flag(nubThread->team);
RELEASE_TEAM_DEBUG_INFO_LOCK(nubThread->team->debug_info);
restore_interrupts(state);
teamLocker.Unlock();
if (destroyDebugInfo)
teamDebugInfo.breakpoint_manager->RemoveAllBreakpoints();
finish_debugger_change(nubThread->team);
if (destroyDebugInfo)
destroy_team_debug_info(&teamDebugInfo);
broadcast_debugged_thread_message(nubThread,
B_DEBUGGED_THREAD_DEBUGGER_CHANGED, NULL, 0);
}
* a thread of the same team.
*/
static status_t
debug_nub_thread_get_thread_debug_port(Thread *nubThread,
thread_id threadID, port_id &threadDebugPort)
{
threadDebugPort = -1;
Thread* thread = Thread::GetAndLock(threadID);
if (thread == NULL)
return B_BAD_THREAD_ID;
BReference<Thread> threadReference(thread, true);
ThreadLocker threadLocker(thread, true);
InterruptsSpinLocker threadDebugInfoLocker(thread->debug_info.lock);
if (thread->team != nubThread->team)
return B_BAD_VALUE;
if ((thread->debug_info.flags & B_THREAD_DEBUG_STOPPED) == 0)
return B_BAD_THREAD_STATE;
threadDebugPort = thread->debug_info.debug_port;
threadDebugInfoLocker.Unlock();
if (threadDebugPort < 0)
return B_ERROR;
return B_OK;
}
static status_t
debug_nub_thread(void *)
{
Thread *nubThread = thread_get_current_thread();
cpu_status state = disable_interrupts();
GRAB_TEAM_DEBUG_INFO_LOCK(nubThread->team->debug_info);
if (nubThread->team->debug_info.nub_thread != nubThread->id) {
RELEASE_TEAM_DEBUG_INFO_LOCK(nubThread->team->debug_info);
restore_interrupts(state);
return 0;
}
port_id port = nubThread->team->debug_info.nub_port;
sem_id writeLock = nubThread->team->debug_info.debugger_write_lock;
BreakpointManager* breakpointManager
= nubThread->team->debug_info.breakpoint_manager;
RELEASE_TEAM_DEBUG_INFO_LOCK(nubThread->team->debug_info);
restore_interrupts(state);
TRACE(("debug_nub_thread() thread: %" B_PRId32 ", team %" B_PRId32 ", nub "
"port: %" B_PRId32 "\n", nubThread->id, nubThread->team->id, port));
broadcast_debugged_thread_message(nubThread,
B_DEBUGGED_THREAD_DEBUGGER_CHANGED, NULL, 0);
while (true) {
int32 command;
debug_nub_message_data message;
ssize_t messageSize = read_port_etc(port, &command, &message,
sizeof(message), B_KILL_CAN_INTERRUPT, 0);
if (messageSize < 0) {
nub_thread_cleanup(nubThread);
TRACE(("nub thread %" B_PRId32 ": terminating: %lx\n",
nubThread->id, messageSize));
return messageSize;
}
bool sendReply = false;
union {
debug_nub_read_memory_reply read_memory;
debug_nub_write_memory_reply write_memory;
debug_nub_clone_area_reply clone_area;
debug_nub_get_cpu_state_reply get_cpu_state;
debug_nub_set_breakpoint_reply set_breakpoint;
debug_nub_set_watchpoint_reply set_watchpoint;
debug_nub_get_signal_masks_reply get_signal_masks;
debug_nub_get_signal_handler_reply get_signal_handler;
debug_nub_start_profiler_reply start_profiler;
debug_profiler_update profiler_update;
debug_nub_write_core_file_reply write_core_file;
} reply;
int32 replySize = 0;
port_id replyPort = -1;
switch (command) {
case B_DEBUG_MESSAGE_READ_MEMORY:
{
replyPort = message.read_memory.reply_port;
void *address = message.read_memory.address;
int32 size = message.read_memory.size;
status_t result = B_OK;
if (!BreakpointManager::CanAccessAddress(address, false))
result = B_BAD_ADDRESS;
else if (size <= 0 || size > B_MAX_READ_WRITE_MEMORY_SIZE)
result = B_BAD_VALUE;
size_t bytesRead = 0;
if (result == B_OK) {
result = breakpointManager->ReadMemory(address,
reply.read_memory.data, size, bytesRead);
}
reply.read_memory.error = result;
TRACE(("nub thread %" B_PRId32 ": B_DEBUG_MESSAGE_READ_MEMORY: "
"reply port: %" B_PRId32 ", address: %p, size: %" B_PRId32
", result: %" B_PRIx32 ", read: %ld\n", nubThread->id,
replyPort, address, size, result, bytesRead));
reply.read_memory.size = bytesRead;
replySize = reply.read_memory.data + bytesRead - (char*)&reply;
sendReply = true;
break;
}
case B_DEBUG_MESSAGE_WRITE_MEMORY:
{
replyPort = message.write_memory.reply_port;
void *address = message.write_memory.address;
int32 size = message.write_memory.size;
const char *data = message.write_memory.data;
int32 realSize = (char*)&message + messageSize - data;
status_t result = B_OK;
if (!BreakpointManager::CanAccessAddress(address, true))
result = B_BAD_ADDRESS;
else if (size <= 0 || size > realSize)
result = B_BAD_VALUE;
size_t bytesWritten = 0;
if (result == B_OK) {
result = breakpointManager->WriteMemory(address, data, size,
bytesWritten);
}
reply.write_memory.error = result;
TRACE(("nub thread %" B_PRId32 ": B_DEBUG_MESSAGE_WRITE_MEMORY: "
"reply port: %" B_PRId32 ", address: %p, size: %" B_PRId32
", result: %" B_PRIx32 ", written: %ld\n", nubThread->id,
replyPort, address, size, result, bytesWritten));
reply.write_memory.size = bytesWritten;
sendReply = true;
replySize = sizeof(debug_nub_write_memory_reply);
break;
}
case B_DEBUG_MESSAGE_CLONE_AREA:
{
replyPort = message.clone_area.reply_port;
const void *address = message.clone_area.address;
area_id result = 0;
if (!IS_USER_ADDRESS(address))
result = B_NOT_ALLOWED;
area_id sourceArea;
addr_t addressOffset = 0;
if (result == B_OK) {
sourceArea = _user_area_for((void*)address);
if (sourceArea < 0) {
result = sourceArea;
} else {
area_info info;
result = get_area_info(sourceArea, &info);
addressOffset = (addr_t)address - (addr_t)info.address;
}
}
if (result == B_OK) {
void* newAddress = NULL;
result = vm_clone_area(nubThread->team->debug_info.debugger_team,
"debugger-cloned area", &newAddress, B_ANY_ADDRESS, B_READ_AREA,
REGION_NO_PRIVATE_MAP, sourceArea, true);
reply.clone_area.address = (void*)((addr_t)newAddress + addressOffset);
}
reply.clone_area.area = result;
TRACE(("nub thread %" B_PRId32 ": B_DEBUG_MESSAGE_CLONE_AREA: "
"reply port: %" B_PRId32 ", address: %p, result: %" B_PRIx32 "\n",
nubThread->id, replyPort, address, result));
sendReply = true;
replySize = sizeof(debug_nub_clone_area_reply);
break;
}
case B_DEBUG_MESSAGE_SET_TEAM_FLAGS:
{
int32 flags = message.set_team_flags.flags
& B_TEAM_DEBUG_USER_FLAG_MASK;
TRACE(("nub thread %" B_PRId32 ": B_DEBUG_MESSAGE_SET_TEAM_FLAGS"
": flags: %" B_PRIx32 "\n", nubThread->id, flags));
Team *team = thread_get_current_thread()->team;
cpu_status state = disable_interrupts();
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_DEBUG_INFO_LOCK(team->debug_info);
restore_interrupts(state);
break;
}
case B_DEBUG_MESSAGE_SET_THREAD_FLAGS:
{
thread_id threadID = message.set_thread_flags.thread;
int32 flags = message.set_thread_flags.flags
& B_THREAD_DEBUG_USER_FLAG_MASK;
TRACE(("nub thread %" B_PRId32 ": B_DEBUG_MESSAGE_SET_THREAD_FLAGS"
": thread: %" B_PRId32 ", flags: %" B_PRIx32 "\n",
nubThread->id, threadID, flags));
Thread* thread = Thread::GetAndLock(threadID);
if (thread == NULL)
break;
BReference<Thread> threadReference(thread, true);
ThreadLocker threadLocker(thread, true);
InterruptsSpinLocker threadDebugInfoLocker(
thread->debug_info.lock);
if (thread->team == thread_get_current_thread()->team) {
flags |= thread->debug_info.flags
& B_THREAD_DEBUG_KERNEL_FLAG_MASK;
atomic_set(&thread->debug_info.flags, flags);
}
break;
}
case B_DEBUG_MESSAGE_CONTINUE_THREAD:
{
thread_id threadID;
uint32 handleEvent;
bool singleStep;
threadID = message.continue_thread.thread;
handleEvent = message.continue_thread.handle_event;
singleStep = message.continue_thread.single_step;
TRACE(("nub thread %" B_PRId32 ": B_DEBUG_MESSAGE_CONTINUE_THREAD"
": thread: %" B_PRId32 ", handle event: %" B_PRIu32 ", "
"single step: %d\n", nubThread->id, threadID, handleEvent,
singleStep));
port_id threadDebugPort = -1;
status_t result = debug_nub_thread_get_thread_debug_port(
nubThread, threadID, threadDebugPort);
if (result == B_OK) {
debugged_thread_continue commandMessage;
commandMessage.handle_event = handleEvent;
commandMessage.single_step = singleStep;
result = write_port(threadDebugPort,
B_DEBUGGED_THREAD_MESSAGE_CONTINUE,
&commandMessage, sizeof(commandMessage));
} else if (result == B_BAD_THREAD_STATE) {
Thread* thread = Thread::GetAndLock(threadID);
if (thread == NULL)
break;
BReference<Thread> threadReference(thread, true);
ThreadLocker threadLocker(thread, true);
if (thread->state == B_THREAD_SUSPENDED) {
threadLocker.Unlock();
resume_thread(threadID);
break;
}
}
break;
}
case B_DEBUG_MESSAGE_SET_CPU_STATE:
{
thread_id threadID = message.set_cpu_state.thread;
const debug_cpu_state &cpuState
= message.set_cpu_state.cpu_state;
TRACE(("nub thread %" B_PRId32 ": B_DEBUG_MESSAGE_SET_CPU_STATE"
": thread: %" B_PRId32 "\n", nubThread->id, threadID));
port_id threadDebugPort = -1;
status_t result = debug_nub_thread_get_thread_debug_port(
nubThread, threadID, threadDebugPort);
if (result == B_OK) {
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_GET_CPU_STATE:
{
thread_id threadID = message.get_cpu_state.thread;
replyPort = message.get_cpu_state.reply_port;
TRACE(("nub thread %" B_PRId32 ": B_DEBUG_MESSAGE_GET_CPU_STATE"
": thread: %" B_PRId32 "\n", nubThread->id, threadID));
port_id threadDebugPort = -1;
status_t result = debug_nub_thread_get_thread_debug_port(
nubThread, threadID, threadDebugPort);
if (threadDebugPort >= 0) {
debugged_thread_get_cpu_state commandMessage;
commandMessage.reply_port = replyPort;
result = write_port(threadDebugPort,
B_DEBUGGED_THREAD_GET_CPU_STATE, &commandMessage,
sizeof(commandMessage));
}
if (result != B_OK) {
reply.get_cpu_state.error = result;
sendReply = true;
replySize = sizeof(reply.get_cpu_state);
}
break;
}
case B_DEBUG_MESSAGE_SET_BREAKPOINT:
{
replyPort = message.set_breakpoint.reply_port;
void *address = message.set_breakpoint.address;
TRACE(("nub thread %" B_PRId32 ": B_DEBUG_MESSAGE_SET_BREAKPOINT"
": address: %p\n", nubThread->id, address));
status_t result = B_OK;
if (address == NULL
|| !BreakpointManager::CanAccessAddress(address, false)) {
result = B_BAD_ADDRESS;
}
if (result == B_OK)
result = breakpointManager->InstallBreakpoint(address);
if (result == B_OK)
update_threads_breakpoints_flag();
reply.set_breakpoint.error = result;
replySize = sizeof(reply.set_breakpoint);
sendReply = true;
break;
}
case B_DEBUG_MESSAGE_CLEAR_BREAKPOINT:
{
void *address = message.clear_breakpoint.address;
TRACE(("nub thread %" B_PRId32 ": B_DEBUG_MESSAGE_CLEAR_BREAKPOINT"
": address: %p\n", nubThread->id, address));
status_t result = B_OK;
if (address == NULL
|| !BreakpointManager::CanAccessAddress(address, false)) {
result = B_BAD_ADDRESS;
}
if (result == B_OK)
result = breakpointManager->UninstallBreakpoint(address);
if (result == B_OK)
update_threads_breakpoints_flag();
break;
}
case B_DEBUG_MESSAGE_SET_WATCHPOINT:
{
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 %" B_PRId32 ": B_DEBUG_MESSAGE_SET_WATCHPOINT"
": address: %p, type: %" B_PRIu32 ", length: %" B_PRId32 "\n",
nubThread->id, address, type, length));
status_t result = B_OK;
if (address == NULL
|| !BreakpointManager::CanAccessAddress(address, false)) {
result = B_BAD_ADDRESS;
}
if (length < 0)
result = B_BAD_VALUE;
if (result == B_OK) {
result = breakpointManager->InstallWatchpoint(address, type,
length);
}
if (result == B_OK)
update_threads_breakpoints_flag();
reply.set_watchpoint.error = result;
replySize = sizeof(reply.set_watchpoint);
sendReply = true;
break;
}
case B_DEBUG_MESSAGE_CLEAR_WATCHPOINT:
{
void *address = message.clear_watchpoint.address;
TRACE(("nub thread %" B_PRId32 ": B_DEBUG_MESSAGE_CLEAR_WATCHPOINT"
": address: %p\n", nubThread->id, address));
status_t result = B_OK;
if (address == NULL
|| !BreakpointManager::CanAccessAddress(address, false)) {
result = B_BAD_ADDRESS;
}
if (result == B_OK)
result = breakpointManager->UninstallWatchpoint(address);
if (result == B_OK)
update_threads_breakpoints_flag();
break;
}
case B_DEBUG_MESSAGE_SET_SIGNAL_MASKS:
{
thread_id threadID = message.set_signal_masks.thread;
uint64 ignore = message.set_signal_masks.ignore_mask;
uint64 ignoreOnce = message.set_signal_masks.ignore_once_mask;
uint32 ignoreOp = message.set_signal_masks.ignore_op;
uint32 ignoreOnceOp = message.set_signal_masks.ignore_once_op;
TRACE(("nub thread %" B_PRId32 ": B_DEBUG_MESSAGE_SET_SIGNAL_MASKS"
": thread: %" B_PRId32 ", ignore: %" B_PRIx64 " (op: %"
B_PRIu32 "), ignore once: %" B_PRIx64 " (op: %" B_PRIu32
")\n", nubThread->id, threadID, ignore, ignoreOp,
ignoreOnce, ignoreOnceOp));
Thread* thread = Thread::GetAndLock(threadID);
if (thread == NULL)
break;
BReference<Thread> threadReference(thread, true);
ThreadLocker threadLocker(thread, true);
InterruptsSpinLocker threadDebugInfoLocker(
thread->debug_info.lock);
if (thread->team == thread_get_current_thread()->team) {
thread_debug_info &threadDebugInfo = thread->debug_info;
switch (ignoreOp) {
case B_DEBUG_SIGNAL_MASK_AND:
threadDebugInfo.ignore_signals &= ignore;
break;
case B_DEBUG_SIGNAL_MASK_OR:
threadDebugInfo.ignore_signals |= ignore;
break;
case B_DEBUG_SIGNAL_MASK_SET:
threadDebugInfo.ignore_signals = ignore;
break;
}
switch (ignoreOnceOp) {
case B_DEBUG_SIGNAL_MASK_AND:
threadDebugInfo.ignore_signals_once &= ignoreOnce;
break;
case B_DEBUG_SIGNAL_MASK_OR:
threadDebugInfo.ignore_signals_once |= ignoreOnce;
break;
case B_DEBUG_SIGNAL_MASK_SET:
threadDebugInfo.ignore_signals_once = ignoreOnce;
break;
}
}
break;
}
case B_DEBUG_MESSAGE_GET_SIGNAL_MASKS:
{
replyPort = message.get_signal_masks.reply_port;
thread_id threadID = message.get_signal_masks.thread;
status_t result = B_OK;
uint64 ignore = 0;
uint64 ignoreOnce = 0;
Thread* thread = Thread::GetAndLock(threadID);
if (thread != NULL) {
BReference<Thread> threadReference(thread, true);
ThreadLocker threadLocker(thread, true);
InterruptsSpinLocker threadDebugInfoLocker(
thread->debug_info.lock);
ignore = thread->debug_info.ignore_signals;
ignoreOnce = thread->debug_info.ignore_signals_once;
} else
result = B_BAD_THREAD_ID;
TRACE(("nub thread %" B_PRId32 ": B_DEBUG_MESSAGE_GET_SIGNAL_MASKS"
": reply port: %" B_PRId32 ", thread: %" B_PRId32 ", "
"ignore: %" B_PRIx64 ", ignore once: %" B_PRIx64 ", result: "
"%" B_PRIx32 "\n", nubThread->id, replyPort, threadID,
ignore, ignoreOnce, result));
reply.get_signal_masks.error = result;
reply.get_signal_masks.ignore_mask = ignore;
reply.get_signal_masks.ignore_once_mask = ignoreOnce;
replySize = sizeof(reply.get_signal_masks);
sendReply = true;
break;
}
case B_DEBUG_MESSAGE_SET_SIGNAL_HANDLER:
{
int signal = message.set_signal_handler.signal;
struct sigaction &handler = message.set_signal_handler.handler;
TRACE(("nub thread %" B_PRId32 ": B_DEBUG_MESSAGE_SET_SIGNAL_HANDLER"
": signal: %d, handler: %p\n", nubThread->id, signal,
handler.sa_handler));
sigaction(signal, &handler, NULL);
break;
}
case B_DEBUG_MESSAGE_GET_SIGNAL_HANDLER:
{
replyPort = message.get_signal_handler.reply_port;
int signal = message.get_signal_handler.signal;
status_t result = B_OK;
if (sigaction(signal, NULL, &reply.get_signal_handler.handler)
!= 0) {
result = errno;
}
TRACE(("nub thread %" B_PRId32 ": B_DEBUG_MESSAGE_GET_SIGNAL_HANDLER"
": reply port: %" B_PRId32 ", signal: %d, handler: %p\n",
nubThread->id, replyPort, signal,
reply.get_signal_handler.handler.sa_handler));
reply.get_signal_handler.error = result;
replySize = sizeof(reply.get_signal_handler);
sendReply = true;
break;
}
case B_DEBUG_MESSAGE_PREPARE_HANDOVER:
{
TRACE(("nub thread %" B_PRId32 ": B_DEBUG_MESSAGE_PREPARE_HANDOVER"
"\n", nubThread->id));
Team *team = nubThread->team;
status_t result = acquire_sem_etc(writeLock, 1,
B_KILL_CAN_INTERRUPT, 0);
if (result == B_OK) {
cpu_status state = disable_interrupts();
GRAB_TEAM_DEBUG_INFO_LOCK(team->debug_info);
atomic_or(&team->debug_info.flags,
B_TEAM_DEBUG_DEBUGGER_HANDOVER);
BreakpointManager* breakpointManager
= team->debug_info.breakpoint_manager;
RELEASE_TEAM_DEBUG_INFO_LOCK(team->debug_info);
restore_interrupts(state);
breakpointManager->RemoveAllBreakpoints();
release_sem(writeLock);
} else {
}
break;
}
case B_DEBUG_MESSAGE_HANDED_OVER:
{
broadcast_debugged_thread_message(nubThread,
B_DEBUGGED_THREAD_DEBUGGER_CHANGED, NULL, 0);
break;
}
case B_DEBUG_MESSAGE_START_PROFILER:
{
thread_id threadID = message.start_profiler.thread;
replyPort = message.start_profiler.reply_port;
area_id sampleArea = message.start_profiler.sample_area;
int32 stackDepth = message.start_profiler.stack_depth;
bool variableStackDepth
= message.start_profiler.variable_stack_depth;
bool profileKernel = message.start_profiler.profile_kernel;
bigtime_t interval = max_c(message.start_profiler.interval,
B_DEBUG_MIN_PROFILE_INTERVAL);
status_t result = B_OK;
TRACE(("nub thread %" B_PRId32 ": B_DEBUG_START_PROFILER: "
"thread: %" B_PRId32 ", sample area: %" B_PRId32 "\n",
nubThread->id, threadID, sampleArea));
if (stackDepth < 1)
stackDepth = 1;
else if (stackDepth > B_DEBUG_STACK_TRACE_DEPTH)
stackDepth = B_DEBUG_STACK_TRACE_DEPTH;
if (variableStackDepth)
stackDepth++;
area_info areaInfo;
if (result == B_OK)
result = get_area_info(sampleArea, &areaInfo);
area_id clonedSampleArea = -1;
void* samples = NULL;
if (result == B_OK) {
clonedSampleArea = clone_area("profiling samples", &samples,
B_ANY_KERNEL_ADDRESS,
B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA,
sampleArea);
if (clonedSampleArea >= 0) {
result = lock_memory(samples, areaInfo.size,
B_READ_DEVICE);
if (result != B_OK) {
delete_area(clonedSampleArea);
clonedSampleArea = -1;
}
} else
result = clonedSampleArea;
}
int32 imageEvent = nubThread->team->debug_info.image_event;
if (result == B_OK) {
Thread* thread = Thread::GetAndLock(threadID);
BReference<Thread> threadReference(thread, true);
ThreadLocker threadLocker(thread, true);
if (thread != NULL && thread->team == nubThread->team) {
thread_debug_info &threadDebugInfo = thread->debug_info;
InterruptsSpinLocker threadDebugInfoLocker(
threadDebugInfo.lock);
if (threadDebugInfo.profile.samples == NULL) {
threadDebugInfo.profile.interval = interval;
threadDebugInfo.profile.sample_area
= clonedSampleArea;
threadDebugInfo.profile.samples = (addr_t*)samples;
threadDebugInfo.profile.max_samples
= areaInfo.size / sizeof(addr_t);
threadDebugInfo.profile.flush_threshold
= threadDebugInfo.profile.max_samples
* B_DEBUG_PROFILE_BUFFER_FLUSH_THRESHOLD
/ 100;
threadDebugInfo.profile.sample_count = 0;
threadDebugInfo.profile.dropped_ticks = 0;
threadDebugInfo.profile.stack_depth = stackDepth;
threadDebugInfo.profile.variable_stack_depth
= variableStackDepth;
threadDebugInfo.profile.profile_kernel = profileKernel;
threadDebugInfo.profile.flush_needed = false;
threadDebugInfo.profile.interval_left = interval;
threadDebugInfo.profile.installed_timer = NULL;
threadDebugInfo.profile.image_event = imageEvent;
threadDebugInfo.profile.last_image_event
= imageEvent;
} else
result = B_BAD_VALUE;
} else
result = B_BAD_THREAD_ID;
}
if (result != B_OK) {
if (clonedSampleArea >= 0) {
unlock_memory(samples, areaInfo.size, B_READ_DEVICE);
delete_area(clonedSampleArea);
}
}
reply.start_profiler.error = result;
reply.start_profiler.interval = interval;
reply.start_profiler.image_event = imageEvent;
sendReply = true;
replySize = sizeof(reply.start_profiler);
break;
}
case B_DEBUG_MESSAGE_STOP_PROFILER:
{
thread_id threadID = message.stop_profiler.thread;
replyPort = message.stop_profiler.reply_port;
status_t result = B_OK;
TRACE(("nub thread %" B_PRId32 ": B_DEBUG_STOP_PROFILER: "
"thread: %" B_PRId32 "\n", nubThread->id, threadID));
area_id sampleArea = -1;
addr_t* samples = NULL;
int32 sampleCount = 0;
int32 stackDepth = 0;
bool variableStackDepth = false;
int32 imageEvent = 0;
int32 droppedTicks = 0;
bigtime_t lastCPUTime = 0;
Thread* thread = Thread::GetAndLock(threadID);
BReference<Thread> threadReference(thread, true);
ThreadLocker threadLocker(thread, true);
if (thread && thread->team == nubThread->team) {
thread_debug_info &threadDebugInfo = thread->debug_info;
InterruptsSpinLocker threadDebugInfoLocker(
threadDebugInfo.lock);
if (threadDebugInfo.profile.samples != NULL) {
sampleArea = threadDebugInfo.profile.sample_area;
samples = threadDebugInfo.profile.samples;
sampleCount = threadDebugInfo.profile.sample_count;
droppedTicks = threadDebugInfo.profile.dropped_ticks;
stackDepth = threadDebugInfo.profile.stack_depth;
variableStackDepth
= threadDebugInfo.profile.variable_stack_depth;
imageEvent = threadDebugInfo.profile.image_event;
threadDebugInfo.profile.sample_area = -1;
threadDebugInfo.profile.samples = NULL;
threadDebugInfo.profile.flush_needed = false;
threadDebugInfo.profile.dropped_ticks = 0;
{
SpinLocker threadTimeLocker(thread->time_lock);
lastCPUTime = thread->CPUTime(false);
}
} else
result = B_BAD_VALUE;
} else
result = B_BAD_THREAD_ID;
threadLocker.Unlock();
if (result == B_OK) {
reply.profiler_update.origin.thread = threadID;
reply.profiler_update.image_event = imageEvent;
reply.profiler_update.stack_depth = stackDepth;
reply.profiler_update.variable_stack_depth
= variableStackDepth;
reply.profiler_update.sample_count = sampleCount;
reply.profiler_update.dropped_ticks = droppedTicks;
reply.profiler_update.stopped = true;
reply.profiler_update.last_cpu_time = lastCPUTime;
} else
reply.profiler_update.origin.thread = result;
replySize = sizeof(debug_profiler_update);
sendReply = true;
if (sampleArea >= 0) {
area_info areaInfo;
if (get_area_info(sampleArea, &areaInfo) == B_OK) {
unlock_memory(samples, areaInfo.size, B_READ_DEVICE);
delete_area(sampleArea);
}
}
break;
}
case B_DEBUG_MESSAGE_WRITE_CORE_FILE:
{
replyPort = message.write_core_file.reply_port;
char* path = message.write_core_file.path;
path[sizeof(message.write_core_file.path) - 1] = '\0';
TRACE(("nub thread %" B_PRId32 ": B_DEBUG_WRITE_CORE_FILE"
": path: %s\n", nubThread->id, path));
status_t result = core_dump_write_core_file(path, false);
reply.write_core_file.error = result;
replySize = sizeof(reply.write_core_file);
sendReply = true;
break;
}
}
if (sendReply) {
status_t error = kill_interruptable_write_port(replyPort, command,
&reply, replySize);
if (error != B_OK) {
TRACE(("nub thread %" B_PRId32 ": failed to send reply to port "
"%" B_PRId32 ": %s\n", nubThread->id, replyPort,
strerror(error)));
nub_thread_cleanup(nubThread);
return error;
}
}
}
}
and thread debug infos.
The caller must hold the team's lock as well as the team debug info lock.
The function also clears the arch specific team and thread debug infos
(including among other things formerly set break/watchpoints).
*/
static void
install_team_debugger_init_debug_infos(Team *team, team_id debuggerTeam,
port_id debuggerPort, port_id nubPort, thread_id nubThread,
sem_id debuggerPortWriteLock, thread_id causingThread)
{
atomic_set(&team->debug_info.flags,
B_TEAM_DEBUG_DEFAULT_FLAGS | B_TEAM_DEBUG_DEBUGGER_INSTALLED);
team->debug_info.nub_port = nubPort;
team->debug_info.nub_thread = nubThread;
team->debug_info.debugger_team = debuggerTeam;
team->debug_info.debugger_port = debuggerPort;
team->debug_info.debugger_write_lock = debuggerPortWriteLock;
team->debug_info.causing_thread = causingThread;
arch_clear_team_debug_info(&team->debug_info.arch_info);
for (Thread *thread = team->thread_list.First(); thread != NULL;
thread = team->thread_list.GetNext(thread)) {
SpinLocker threadDebugInfoLocker(thread->debug_info.lock);
if (thread->id == nubThread) {
atomic_set(&thread->debug_info.flags, B_THREAD_DEBUG_NUB_THREAD);
} else {
int32 flags = thread->debug_info.flags
& ~B_THREAD_DEBUG_USER_FLAG_MASK;
atomic_set(&thread->debug_info.flags,
flags | B_THREAD_DEBUG_DEFAULT_FLAGS);
thread->debug_info.ignore_signals = 0;
thread->debug_info.ignore_signals_once = 0;
arch_clear_thread_debug_info(&thread->debug_info.arch_info);
}
}
update_threads_debugger_installed_flag(team);
}
static port_id
install_team_debugger(team_id teamID, port_id debuggerPort,
thread_id causingThread, bool useDefault, bool dontReplace)
{
TRACE(("install_team_debugger(team: %" B_PRId32 ", port: %" B_PRId32 ", "
"default: %d, dontReplace: %d)\n", teamID, debuggerPort, useDefault,
dontReplace));
if (useDefault)
debuggerPort = atomic_get(&sDefaultDebuggerPort);
port_info debuggerPortInfo;
status_t error = get_port_info(debuggerPort, &debuggerPortInfo);
if (error != B_OK) {
TRACE(("install_team_debugger(): Failed to get debugger port info: "
"%" B_PRIx32 "\n", error));
return error;
}
team_id debuggerTeam = debuggerPortInfo.team;
if (teamID == B_CURRENT_TEAM)
teamID = team_get_current_team_id();
if (debuggerTeam == team_get_kernel_team_id() || debuggerTeam == teamID) {
TRACE(("install_team_debugger(): Can't debug kernel or debugger team. "
"debugger: %" B_PRId32 ", debugged: %" B_PRId32 "\n", debuggerTeam,
teamID));
return B_NOT_ALLOWED;
}
Team* team;
ConditionVariable debugChangeCondition;
debugChangeCondition.Init(NULL, "debug change condition");
error = prepare_debugger_change(teamID, debugChangeCondition, team);
if (error != B_OK)
return error;
bool done = false;
port_id result = B_ERROR;
bool handOver = false;
port_id oldDebuggerPort = -1;
port_id nubPort = -1;
TeamLocker teamLocker(team);
cpu_status state = disable_interrupts();
GRAB_TEAM_DEBUG_INFO_LOCK(team->debug_info);
int32 teamDebugFlags = team->debug_info.flags;
if (teamDebugFlags & B_TEAM_DEBUG_DEBUGGER_INSTALLED) {
if (teamDebugFlags & B_TEAM_DEBUG_DEBUGGER_HANDOVER) {
if (dontReplace) {
error = B_OK;
done = true;
result = team->debug_info.nub_port;
} else {
atomic_or(&team->debug_info.flags,
B_TEAM_DEBUG_DEBUGGER_HANDING_OVER);
oldDebuggerPort = team->debug_info.debugger_port;
result = nubPort = team->debug_info.nub_port;
if (causingThread < 0)
causingThread = team->debug_info.causing_thread;
install_team_debugger_init_debug_infos(team, debuggerTeam,
debuggerPort, nubPort, team->debug_info.nub_thread,
team->debug_info.debugger_write_lock, causingThread);
handOver = true;
done = true;
}
} else {
error = (dontReplace ? B_OK : B_BAD_VALUE);
done = true;
result = team->debug_info.nub_port;
}
} else if ((teamDebugFlags & B_TEAM_DEBUG_DEBUGGER_DISABLED) != 0
&& useDefault) {
error = B_BAD_VALUE;
}
RELEASE_TEAM_DEBUG_INFO_LOCK(team->debug_info);
restore_interrupts(state);
teamLocker.Unlock();
if (handOver && set_port_owner(nubPort, debuggerTeam) != B_OK) {
handOver = false;
done = false;
}
if (handOver) {
debug_handed_over notification;
notification.origin.thread = -1;
notification.origin.team = teamID;
notification.origin.nub_port = nubPort;
notification.debugger = debuggerTeam;
notification.debugger_port = debuggerPort;
notification.causing_thread = causingThread;
error = write_port_etc(debuggerPort,
B_DEBUGGER_MESSAGE_HANDED_OVER, ¬ification,
sizeof(notification), B_RELATIVE_TIMEOUT, 0);
if (error != B_OK) {
dprintf("install_team_debugger(): Failed to send message to new "
"debugger: %s\n", strerror(error));
}
state = disable_interrupts();
GRAB_TEAM_DEBUG_INFO_LOCK(team->debug_info);
atomic_and(&team->debug_info.flags,
~(B_TEAM_DEBUG_DEBUGGER_HANDOVER
| B_TEAM_DEBUG_DEBUGGER_HANDING_OVER));
RELEASE_TEAM_DEBUG_INFO_LOCK(team->debug_info);
restore_interrupts(state);
finish_debugger_change(team);
kill_interruptable_write_port(nubPort, B_DEBUG_MESSAGE_HANDED_OVER,
NULL, 0);
error = write_port_etc(oldDebuggerPort,
B_DEBUGGER_MESSAGE_HANDED_OVER, ¬ification,
sizeof(notification), B_RELATIVE_TIMEOUT, 0);
if (error != B_OK) {
TRACE(("install_team_debugger(): Failed to send message to old "
"debugger: %s\n", strerror(error)));
}
TRACE(("install_team_debugger() done: handed over to debugger: team: "
"%" B_PRId32 ", port: %" B_PRId32 "\n", debuggerTeam,
debuggerPort));
return result;
}
if (done || error != B_OK) {
TRACE(("install_team_debugger() done1: %" B_PRId32 "\n",
(error == B_OK ? result : error)));
finish_debugger_change(team);
return (error == B_OK ? result : error);
}
char nameBuffer[B_OS_NAME_LENGTH];
snprintf(nameBuffer, sizeof(nameBuffer), "team %" B_PRId32 " debugger port "
"write", teamID);
sem_id debuggerWriteLock = create_sem(1, nameBuffer);
if (debuggerWriteLock < 0)
error = debuggerWriteLock;
snprintf(nameBuffer, sizeof(nameBuffer), "team %" B_PRId32 " debug", teamID);
if (error == B_OK) {
nubPort = create_port(1, nameBuffer);
if (nubPort < 0)
error = nubPort;
else
result = nubPort;
}
if (error == B_OK)
error = set_port_owner(nubPort, debuggerTeam);
BreakpointManager* breakpointManager = NULL;
if (error == B_OK) {
breakpointManager = new(std::nothrow) BreakpointManager;
if (breakpointManager != NULL)
error = breakpointManager->Init();
else
error = B_NO_MEMORY;
}
thread_id nubThread = -1;
if (error == B_OK) {
snprintf(nameBuffer, sizeof(nameBuffer), "team %" B_PRId32 " debug task",
teamID);
nubThread = spawn_kernel_thread_etc(debug_nub_thread, nameBuffer,
B_NORMAL_PRIORITY, NULL, teamID);
if (nubThread < 0)
error = nubThread;
}
if (error == B_OK) {
TeamLocker teamLocker(team);
state = disable_interrupts();
GRAB_TEAM_DEBUG_INFO_LOCK(team->debug_info);
team->debug_info.breakpoint_manager = breakpointManager;
install_team_debugger_init_debug_infos(team, debuggerTeam,
debuggerPort, nubPort, nubThread, debuggerWriteLock,
causingThread);
RELEASE_TEAM_DEBUG_INFO_LOCK(team->debug_info);
restore_interrupts(state);
}
finish_debugger_change(team);
if (error == B_OK) {
resume_thread(nubThread);
} else {
if (nubPort >= 0) {
set_port_owner(nubPort, B_CURRENT_TEAM);
delete_port(nubPort);
}
if (nubThread >= 0) {
int32 result;
wait_for_thread(nubThread, &result);
}
delete breakpointManager;
}
TRACE(("install_team_debugger() done2: %" B_PRId32 "\n",
(error == B_OK ? result : error)));
return (error == B_OK ? result : error);
}
static status_t
ensure_debugger_installed()
{
port_id port = install_team_debugger(B_CURRENT_TEAM, -1,
thread_get_current_thread_id(), true, true);
return port >= 0 ? B_OK : port;
}
void
_user_debugger(const char *userMessage)
{
status_t error = ensure_debugger_installed();
if (error != B_OK) {
char buffer[128];
ssize_t length = user_strlcpy(buffer, userMessage, sizeof(buffer));
if (length >= 0) {
dprintf("_user_debugger(): Failed to install debugger. Message is: "
"`%s'\n", buffer);
} else {
dprintf("_user_debugger(): Failed to install debugger. Message is: "
"%p (%s)\n", userMessage, strerror(length));
}
_user_exit_team(1);
}
debug_debugger_call message;
message.message = (void*)userMessage;
thread_hit_debug_event(B_DEBUGGER_MESSAGE_DEBUGGER_CALL, &message,
sizeof(message), true);
}
int
_user_disable_debugger(int state)
{
Team *team = thread_get_current_thread()->team;
TRACE(("_user_disable_debugger(%d): team: %" B_PRId32 "\n", state,
team->id));
cpu_status cpuState = disable_interrupts();
GRAB_TEAM_DEBUG_INFO_LOCK(team->debug_info);
int32 oldFlags;
if (state) {
oldFlags = atomic_or(&team->debug_info.flags,
B_TEAM_DEBUG_DEBUGGER_DISABLED);
} else {
oldFlags = atomic_and(&team->debug_info.flags,
~B_TEAM_DEBUG_DEBUGGER_DISABLED);
}
RELEASE_TEAM_DEBUG_INFO_LOCK(team->debug_info);
restore_interrupts(cpuState);
return !(oldFlags & B_TEAM_DEBUG_DEBUGGER_DISABLED);
}
status_t
_user_install_default_debugger(port_id debuggerPort)
{
if (geteuid() != 0)
return B_PERMISSION_DENIED;
if (debuggerPort >= 0) {
port_info portInfo;
status_t error = get_port_info(debuggerPort, &portInfo);
if (error != B_OK)
return error;
if (portInfo.team == team_get_kernel_team_id())
return B_NOT_ALLOWED;
}
atomic_set(&sDefaultDebuggerPort, debuggerPort);
return B_OK;
}
port_id
_user_install_team_debugger(team_id teamID, port_id debuggerPort)
{
if (geteuid() != 0 && team_geteuid(teamID) != geteuid())
return B_PERMISSION_DENIED;
return install_team_debugger(teamID, debuggerPort, -1, false, false);
}
status_t
_user_remove_team_debugger(team_id teamID)
{
Team* team;
ConditionVariable debugChangeCondition;
debugChangeCondition.Init(NULL, "debug change condition");
status_t status = prepare_debugger_change(teamID, debugChangeCondition,
team);
if (status != B_OK)
return status;
InterruptsSpinLocker debugInfoLocker(team->debug_info.lock);
thread_id nubThread = -1;
port_id nubPort = -1;
if ((team->debug_info.flags & B_TEAM_DEBUG_DEBUGGER_INSTALLED) == 0) {
status = B_BAD_VALUE;
}
if (status == B_OK) {
if (geteuid() != 0 && team->effective_uid != geteuid()
&& team->debug_info.debugger_team != team_get_current_team_id())
status = B_PERMISSION_DENIED;
}
if (status == B_OK) {
nubThread = team->debug_info.nub_thread;
nubPort = team->debug_info.nub_port;
}
debugInfoLocker.Unlock();
if (nubPort >= 0)
delete_port(nubPort);
finish_debugger_change(team);
if (nubThread >= 0)
wait_for_thread(nubThread, NULL);
return status;
}
status_t
_user_debug_thread(thread_id threadID)
{
TRACE(("[%" B_PRId32 "] _user_debug_thread(%" B_PRId32 ")\n",
find_thread(NULL), threadID));
Thread* thread = Thread::GetAndLock(threadID);
if (thread == NULL)
return B_BAD_THREAD_ID;
BReference<Thread> threadReference(thread, true);
ThreadLocker threadLocker(thread, true);
if (thread->team == team_get_kernel_team())
return B_NOT_ALLOWED;
if (geteuid() != 0 && thread->team->effective_uid != geteuid()
&& thread->team->debug_info.debugger_team != team_get_current_team_id())
return B_PERMISSION_DENIED;
InterruptsLocker interruptsLocker;
SpinLocker threadDebugInfoLocker(thread->debug_info.lock);
if ((thread->debug_info.flags & B_THREAD_DEBUG_DYING) != 0)
return B_BAD_THREAD_ID;
if ((thread->debug_info.flags & B_THREAD_DEBUG_NUB_THREAD) != 0)
return B_NOT_ALLOWED;
if ((thread->debug_info.flags
& (B_THREAD_DEBUG_STOPPED | B_THREAD_DEBUG_STOP)) != 0) {
return B_OK;
}
atomic_or(&thread->debug_info.flags, B_THREAD_DEBUG_STOP);
update_thread_user_debug_flag(thread);
threadDebugInfoLocker.Unlock();
ReadSpinLocker teamLocker(thread->team_lock);
SpinLocker locker(thread->team->signal_lock);
send_signal_to_thread_locked(thread, SIGNAL_DEBUG_THREAD, NULL, 0);
return B_OK;
}
void
_user_wait_for_debugger(void)
{
debug_thread_debugged message = {};
thread_hit_debug_event(B_DEBUGGER_MESSAGE_THREAD_DEBUGGED, &message,
sizeof(message), false);
}
status_t
_user_set_debugger_breakpoint(void *address, uint32 type, int32 length,
bool watchpoint)
{
if (address == NULL || !BreakpointManager::CanAccessAddress(address, false))
return B_BAD_ADDRESS;
if (watchpoint && length < 0)
return B_BAD_VALUE;
team_debug_info teamDebugInfo;
get_team_debug_info(teamDebugInfo);
if (teamDebugInfo.flags & B_TEAM_DEBUG_DEBUGGER_INSTALLED)
return B_BAD_VALUE;
status_t result;
if (watchpoint)
result = arch_set_watchpoint(address, type, length);
else
result = arch_set_breakpoint(address);
if (result == B_OK)
update_threads_breakpoints_flag();
return result;
}
status_t
_user_clear_debugger_breakpoint(void *address, bool watchpoint)
{
if (address == NULL || !BreakpointManager::CanAccessAddress(address, false))
return B_BAD_ADDRESS;
team_debug_info teamDebugInfo;
get_team_debug_info(teamDebugInfo);
if (teamDebugInfo.flags & B_TEAM_DEBUG_DEBUGGER_INSTALLED)
return B_BAD_VALUE;
status_t result;
if (watchpoint)
result = arch_clear_watchpoint(address);
else
result = arch_clear_breakpoint(address);
if (result == B_OK)
update_threads_breakpoints_flag();
return result;
}