#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;
}
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);
}
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);
}
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);
}
}
}
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);
}
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);
}
}
}
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;
}
}
}
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;
}
}
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);
}
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);
}
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);
}
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));
}
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);
}
static bigtime_t
profiling_timer_left(Thread* thread)
{
return thread->debug_info.profile.timer_end - system_time();
}
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();
}
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;
}
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);
}
}
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);
}
}
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);
}
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;
}
}
}
}
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;
}