/** @addtogroup tdebug
* @{
*/
/**
* @file
* @brief Task Debugging.
*/
#include <arch.h>
#include <atomic.h>
#include <ipc/ipc.h>
#include <proc/task.h>
#include <synch/spinlock.h>
#include <synch/waitq.h>
#include <console/klog.h>
#include <errno.h>
//#include <arch/tdebug.h>
#include <tdebug/tdebug.h>
//FIXME: need to reset tdebug_thread structure for each thread
//either on monitor attach or on monitor detach
static void _tdebug_send_msg(task_t *ta, unative_t method, unative_t a1, unative_t a2,
unative_t a3, unative_t a4, unative_t a5);
void tdebug_task_init(struct task *ta)
{
atomic_set(&ta->tdebug.have_monitor, 0);
ta->tdebug.monitor = NULL;
list_initialize(&ta->tdebug.targets);
}
/**
* Initialize the tdebug part of a thread structure.
*/
void tdebug_thread_init(struct thread *t)
{
t->tdebug.stopped = 0;
condvar_initialize(&t->tdebug.stopped_cv);
t->tdebug.stop_request = 0;
t->tdebug.event_mask = TDEBUG_EVMASK_SYSCALL | TDEBUG_EVMASK_EXCEPTION;
t->tdebug.syscall_args = NULL;
}
/**
* Clean-up all tdebug activities of the current task.
*/
void tdebug_cleanup(void)
{
ipl_t ipl;
task_t *mon_task;
klog_printf("tdebug_cleanup()");
/*
* First detach any targets monitored by this task.
*/
loop_1:
ipl = interrupts_disable();
spinlock_lock(&TASK->lock);
while (TASK->tdebug.targets.next != &TASK->tdebug.targets) {
link_t *cur = TASK->tdebug.targets.next;
task_t *ta;
DEADLOCK_PROBE_INIT(p_target_lock);
klog_printf(" - detaching a target");
ta = list_get_instance(cur, task_t, tdebug.targets_link);
if (!spinlock_trylock(&ta->lock)) {
/*
* Avoid deadlock by trying again.
*/
spinlock_unlock(&TASK->lock);
interrupts_restore(ipl);
DEADLOCK_PROBE(p_target_lock, DEADLOCK_THRESHOLD);
goto loop_1;
}
/* this task should be the target's monitor */
ASSERT(TASK == ta->tdebug.monitor);
ta->tdebug.monitor = NULL;
list_remove(&ta->tdebug.targets_link);
spinlock_unlock(&ta->lock);
}
spinlock_unlock(&TASK->lock);
interrupts_restore(ipl);
/*
* Now if this task is being monitored, detach from the monitor.
*/
loop_2:
ipl = interrupts_disable();
spinlock_lock(&TASK->lock);
if (TASK->tdebug.monitor != NULL) {
/* yes, so detach */
DEADLOCK_PROBE_INIT(p_monitor_lock);
klog_printf(" - detaching from the monitor");
mon_task = TASK->tdebug.monitor;
if (!spinlock_trylock(&mon_task->lock)) {
/*
* Avoid deadlock by trying again.
*/
spinlock_unlock(&TASK->lock);
interrupts_restore(ipl);
DEADLOCK_PROBE(p_monitor_lock, DEADLOCK_THRESHOLD);
goto loop_2;
}
list_remove(&TASK->tdebug.targets_link);
TASK->tdebug.monitor = NULL;
spinlock_unlock(&mon_task->lock);
}
spinlock_unlock(&TASK->lock);
interrupts_restore(ipl);
klog_printf("tdebug_cleanup done");
}
static void _tdebug_clear_event_unsafe(void)
{
THREAD->tdebug.stopped = 0;
THREAD->tdebug.syscall_args = NULL;
}
static void _tdebug_reset_thread_struct_unsafe(void)
{
_tdebug_clear_event_unsafe();
THREAD->tdebug.stop_request = 0;
THREAD->tdebug.event_mask = 0;
}
/**
* Sleep until the thread is no longer stopped.
*
* Go to sleep iff the thread still has stopped==1
* and sleep until it is resumed.
*
* This will also clear the debugging event.!!!!
*/
static void _tdebug_wait_and_clear(void)
{
ipl_t ipl, ipl2;
int rc;
waitq_t *wq;
/* wait until we are resumed */
klog_printf("wait...");
ipl = interrupts_disable();
spinlock_lock(&THREAD->lock);
wq = &THREAD->tdebug.stopped_cv.wq;
/*
* Waiting for THREAD->tdebug.stopped to become 0.
*
* This is almost the same as condvar_wait, except that
* instead of a mutex, the THREAD->lock spinlock is used
* for mutual exclusion.
*/
while (THREAD->tdebug.stopped != 0) {
spinlock_unlock(&THREAD->lock);
/* "spinvar_wait()" */
//printf("waitq_sleep_prepare...\n");
ipl2 = waitq_sleep_prepare(wq);
//printf("waitq_sleep_prepare... ok\n");
wq->missed_wakeups = 0;
rc = waitq_sleep_timeout_unsafe(wq, SYNCH_NO_TIMEOUT, SYNCH_FLAGS_NONE);
spinlock_lock(&THREAD->lock);
waitq_sleep_finish(wq, rc, ipl2);
}
_tdebug_clear_event_unsafe();
spinlock_unlock(&THREAD->lock);
interrupts_restore(ipl);
klog_printf("done waiting");
}
/**
* Send a notification to the monitoring task.
*
* Returns 0 on success or -1 if no debugger is present.
*/
static int _tdebug_notify_debugger(thread_id_t tid, unative_t a1,
unative_t a2, unative_t a3)
{
unative_t tid_lo, tid_hi;
ipl_t ipl;
task_t *monitor;
DEADLOCK_PROBE_INIT(p_monitor_lock);
tid_lo = tid & 0xffffffff;
tid_hi = tid >> 16;
/* send notification to the monitoring task */
loop:
ipl = interrupts_disable();
spinlock_lock(&TASK->lock);
monitor = TASK->tdebug.monitor;
if (monitor == NULL) {
/* there's no monitor anymore */
spinlock_unlock(&TASK->lock);
/*
* Reset the debugging struct
*/
spinlock_lock(&THREAD->lock);
_tdebug_reset_thread_struct_unsafe();
spinlock_unlock(&THREAD->lock);
interrupts_restore(ipl);
return -1;
}
if (!spinlock_trylock(&monitor->lock)) {
/*
* Avoid deadlock by trying again
*/
spinlock_unlock(&TASK->lock);
interrupts_restore(ipl);
DEADLOCK_PROBE(p_monitor_lock, DEADLOCK_THRESHOLD);
goto loop;
}
_tdebug_send_msg(TASK->tdebug.monitor, TASK->tdebug.method,
tid_lo, tid_hi, a1, a2, a3);
spinlock_unlock(&TASK->lock);
spinlock_unlock(&monitor->lock);
interrupts_restore(ipl);
return 0;
}
/**
* Generate a syscall debug event.
*
* Called from syscall_handler() in syscall.c. The current task
* may or may not have a monitor attached.
* If a monitor is not attached or syscall events are not enabled,
* no action is taken.
*/
void tdebug_syscall_event(unative_t a1, unative_t a2, unative_t a3,
unative_t a4, unative_t a5, unative_t a6, unative_t id, unative_t sc_rc)
{
unative_t sc_args[6];
ipl_t ipl;
thread_id_t tid;
klog_printf("TASK %llu: Syscall id %lx", TASK->taskid, id);
klog_printf("THREAD %llu", THREAD->tid);
/* no need to lock this */
tid = THREAD->tid;
/* copy down arguments to an array */
sc_args[0] = a1;
sc_args[1] = a2;
sc_args[2] = a3;
sc_args[3] = a4;
sc_args[4] = a5;
sc_args[5] = a6;
ipl = interrupts_disable();
spinlock_lock(&THREAD->lock);
if ((THREAD->tdebug.event_mask & TDEBUG_EVMASK_SYSCALL) == 0) {
/* syscall event not enabled */
spinlock_unlock(&THREAD->lock);
interrupts_restore(ipl);
return;
}
/* record the debugging event into our thread structure */
THREAD->tdebug.syscall_args = sc_args;
THREAD->tdebug.stopped = 1;
THREAD->tdebug.current_event = TDEBUG_EV_SYSCALL;
spinlock_unlock(&THREAD->lock);
interrupts_restore(ipl);
/*
* Send notification to the monitoring task
* This may fail as we needn't have a monitor attached
*/
if (_tdebug_notify_debugger(tid, TDEBUG_EV_SYSCALL, id, sc_rc) == 0) {
/* wait for resume */
_tdebug_wait_and_clear();
}
}
/**
* Generate an exception debug event.
*
* Called from exc_dispatch() in interrupt.c. The current task
* may or may not have a monitor attached.
* If a monitor is not attached or exception events are not enabled,
* no action is taken.
*/
void tdebug_exception_event(int n)
{
ipl_t ipl;
thread_id_t tid;
klog_printf("TASK %llu: exception %d", TASK->taskid, n);
klog_printf("THREAD %llu", THREAD->tid);
/* no need to lock this */
tid = THREAD->tid;
ipl = interrupts_disable();
spinlock_lock(&THREAD->lock);
if ((THREAD->tdebug.event_mask & TDEBUG_EVMASK_EXCEPTION) == 0) {
/* exception event not enabled */
spinlock_unlock(&THREAD->lock);
interrupts_restore(ipl);
return;
}
/* record the debugging event into our thread structure */
THREAD->tdebug.stopped = 1;
THREAD->tdebug.current_event = TDEBUG_EV_EXCEPTION;
spinlock_unlock(&THREAD->lock);
interrupts_restore(ipl);
/*
* Send notification to the monitoring task
* This may fail as we needn't have a monitor attached
*/
if (_tdebug_notify_debugger(tid, TDEBUG_EV_EXCEPTION, n, 0) == 0) {
/* wait for resume */
_tdebug_wait_and_clear();
}
}
/**
* Stop the thread here if a stop request is pending.
*/
void tdebug_stopping_point(void)
{
ipl_t ipl;
thread_id_t tid;
klog_printf("TASK %llu: stopping point", TASK->taskid);
klog_printf("THREAD %llu", THREAD->tid);
/* no need to lock this */
tid = THREAD->tid;
ipl = interrupts_disable();
spinlock_lock(&THREAD->lock);
if (THREAD->tdebug.stop_request == 0) {
/* stop not requested */
spinlock_unlock(&THREAD->lock);
interrupts_restore(ipl);
return;
}
/* record the debugging event into our thread structure */
THREAD->tdebug.stopped = 1;
THREAD->tdebug.current_event = TDEBUG_EV_STOP;
spinlock_unlock(&THREAD->lock);
interrupts_restore(ipl);
/* send notification to the monitoring task */
if (_tdebug_notify_debugger(tid, TDEBUG_EV_STOP, 0, 0) == 0) {
/* wait for resume */
_tdebug_wait_and_clear();
}
}
/**
* Send an IPC notification to a task.
*
* When calling the task's lock must be already held and interrupts
* must be disabled.
*/
static void _tdebug_send_msg(task_t *ta, unative_t method, unative_t a1, unative_t a2,
unative_t a3, unative_t a4, unative_t a5)
{
call_t *call;
answerbox_t *answerbox;
/* prepare an IPC notification structure */
call = ipc_call_alloc(FRAME_ATOMIC);
if (!call) {
return;
}
call->flags |= IPC_CALL_NOTIF;
IPC_SET_METHOD(call->data, method);
IPC_SET_ARG1(call->data, a1);
IPC_SET_ARG2(call->data, a2);
IPC_SET_ARG3(call->data, a3);
IPC_SET_ARG4(call->data, a4);
IPC_SET_ARG5(call->data, a5);
answerbox = &ta->answerbox;
/* analogy of send_call() from irq.c */
spinlock_lock(&answerbox->irq_lock);
list_append(&call->link, &answerbox->irq_notifs);
spinlock_unlock(&answerbox->irq_lock);
waitq_wakeup(&answerbox->wq, WAKEUP_FIRST);
}
/**
* Start debugging a task.
*
* The specified task (target) is attached to the current task (monitor).
* The monitoring task will be informed of debug events in the
* target task through IPC notifications with the specified method number.
*/
int tdebug_attach_task(task_id_t taskid, unative_t method)
{
task_t *ta;
ipl_t ipl;
DEADLOCK_PROBE_INIT(p_target_lock);
//FIXME: pokud bychom si podrzeli tasks_lock, nemusime pokazde
// znovu hledat ta podle id
loop:
ipl = interrupts_disable();
spinlock_lock(&tasks_lock);
spinlock_lock(&TASK->lock);
ta = task_find_by_id(taskid);
if (!ta) {
spinlock_unlock(&tasks_lock);
spinlock_unlock(&TASK->lock);
interrupts_restore(ipl);
klog_printf("fail: no such task\n");
return ENOENT;
}
if (!spinlock_trylock(&ta->lock)) {
/*
* Avoid deadlock by trying again.
*/
spinlock_unlock(&TASK->lock);
spinlock_unlock(&tasks_lock);
interrupts_restore(ipl);
DEADLOCK_PROBE(p_target_lock, DEADLOCK_THRESHOLD);
goto loop;
}
/* we don't need tasks_lock anymore */
spinlock_unlock(&tasks_lock);
if (ta->tdebug.monitor != NULL) {
spinlock_unlock(&TASK->lock);
spinlock_unlock(&ta->lock);
interrupts_restore(ipl);
klog_printf("fail: already being debugged\n");
return EBUSY; /* task already being debugged */
}
atomic_set(&ta->tdebug.have_monitor, 1);
ta->tdebug.monitor = TASK;
ta->tdebug.method = method;
/* insert ta into this task's debug-target list */
list_append(&ta->tdebug.targets_link, &TASK->tdebug.targets);
spinlock_unlock(&TASK->lock);
spinlock_unlock(&ta->lock);
interrupts_restore(ipl);
return 0;
}
/**
* Stop debugging a task.
*/
int tdebug_detach_task(task_id_t taskid)
{
task_t *ta;
ipl_t ipl;
DEADLOCK_PROBE_INIT(p_target_lock);
//FIXME: pokud bychom si podrzeli tasks_lock, nemusime pokazde
// znovu hledat ta podle id
loop:
ipl = interrupts_disable();
spinlock_lock(&tasks_lock);
spinlock_lock(&TASK->lock);
ta = task_find_by_id(taskid);
if (!ta) {
spinlock_unlock(&tasks_lock);
spinlock_unlock(&TASK->lock);
interrupts_restore(ipl);
klog_printf("fail: no such task\n");
return ENOENT;
}
if (!spinlock_trylock(&ta->lock)) {
/*
* Avoid deadlock by trying again.
*/
spinlock_unlock(&TASK->lock);
spinlock_unlock(&tasks_lock);
interrupts_restore(ipl);
DEADLOCK_PROBE(p_target_lock, DEADLOCK_THRESHOLD);
goto loop;
}
/* we don't need tasks_lock anymore */
spinlock_unlock(&tasks_lock);
if (ta->tdebug.monitor == NULL) {
spinlock_unlock(&TASK->lock);
spinlock_unlock(&ta->lock);
interrupts_restore(ipl);
klog_printf("fail: not being debugged\n");
return EINVAL; /* task not being debugged */
}
atomic_set(&ta->tdebug.have_monitor, 0);
ta->tdebug.monitor = NULL;
ta->tdebug.method = 0;
/* remove target from this task's list */
list_remove(&ta->tdebug.targets_link);
spinlock_unlock(&TASK->lock);
spinlock_unlock(&ta->lock);
interrupts_restore(ipl);
return 0;
}
/**
* Resume a thread stopped in a debugging event.
*/
int tdebug_continue_thread(thread_id_t tid)
{
thread_t *t;
ipl_t ipl;
klog_printf("sys_tdebug_continue_thread(%lld)", tid);
//FIXME: need to verify that we are the monitor
ipl = interrupts_disable();
spinlock_lock(&threads_lock);
t = thread_find_by_id(tid);
if (t == NULL) {
spinlock_unlock(&threads_lock);
interrupts_restore(ipl);
klog_printf("fail: no such thread\n");
return ENOENT;
}
/* lock the thread */
spinlock_lock(&t->lock);
/* verify that the thread is stopped */
if (t->tdebug.stopped == 0) {
spinlock_unlock(&t->lock);
interrupts_restore(ipl);
return EINVAL;
}
t->tdebug.stopped = 0;
/* no thread should be unlocked when calling waitq_wakeup */
spinlock_unlock(&t->lock);
/* "spincv_signal()" */
waitq_wakeup(&t->tdebug.stopped_cv.wq, WAKEUP_FIRST);
spinlock_unlock(&threads_lock);
interrupts_restore(ipl);
klog_printf("success\n");
return 0;
}
/**
* Retrieve the arguments of a syscall.
*
* When a thread is stopped in a TDEBUG_EV_SYSCALL event,
* this function may be used to retrieve the arguments of the
* corresponding syscall.
*/
int tdebug_get_syscall_args(thread_id_t tid, unative_t *sc_args)
{
thread_t *t;
ipl_t ipl;
//FIXME: need to verify that we are the monitor
ipl = interrupts_disable();
spinlock_lock(&threads_lock);
t = thread_find_by_id(tid);
if (t == NULL) {
spinlock_unlock(&threads_lock);
interrupts_restore(ipl);
klog_printf("fail: no such thread\n");
return ENOENT;
}
/* keep a lock on the thread */
spinlock_lock(&t->lock);
spinlock_unlock(&threads_lock);
/* verify that the thread is stopped in a syscall event*/
if (t->tdebug.stopped == 0 ||
t->tdebug.current_event != TDEBUG_EV_SYSCALL) {
spinlock_unlock(&t->lock);
interrupts_restore(ipl);
return EINVAL;
}
/* copy syscall arguments */
sc_args[0] = t->tdebug.syscall_args[0];
sc_args[1] = t->tdebug.syscall_args[1];
sc_args[2] = t->tdebug.syscall_args[2];
sc_args[3] = t->tdebug.syscall_args[3];
sc_args[4] = t->tdebug.syscall_args[4];
sc_args[5] = t->tdebug.syscall_args[5];
spinlock_unlock(&t->lock);
interrupts_restore(ipl);
return 0;
}
/**
* Select which debug events will be generated.
*/
int tdebug_set_event_mask(thread_id_t tid, unative_t ev_mask)
{
thread_t *t;
ipl_t ipl;
int rc;
//FIXME: need to verify that we are the monitor
ipl = interrupts_disable();
spinlock_lock(&threads_lock);
t = thread_find_by_id(tid);
if (t == NULL) {
spinlock_unlock(&threads_lock);
interrupts_restore(ipl);
klog_printf("fail: no such thread\n");
return ENOENT;
}
/* keep a lock on the thread */
spinlock_lock(&t->lock);
spinlock_unlock(&threads_lock);
/* verify that the thread is stopped */
if (t->tdebug.stopped == 0) {
spinlock_unlock(&t->lock);
interrupts_restore(ipl);
klog_printf("fail: thread not stopped\n");
return EINVAL;
}
t->tdebug.event_mask = ev_mask;
/* arch-specific update for IAFTER setting */
//disable this as it would break compile for all archs except ia32
//rc = tdebug_iafter_update(t);
rc = 0;
spinlock_unlock(&t->lock);
interrupts_restore(ipl);
return rc;
}
/**
* Ask a thread to stop (without waiting for it to happen).
*/
int tdebug_stop_thread(thread_id_t tid)
{
thread_t *t;
ipl_t ipl;
//FIXME: need to verify that we are the monitor
ipl = interrupts_disable();
spinlock_lock(&threads_lock);
t = thread_find_by_id(tid);
if (t == NULL) {
spinlock_unlock(&threads_lock);
interrupts_restore(ipl);
klog_printf("fail: no such thread\n");
return ENOENT;
}
/* keep a lock on the thread */
spinlock_lock(&t->lock);
spinlock_unlock(&threads_lock);
/* verify that the thread isn't stopped */
if (t->tdebug.stopped == 0) {
spinlock_unlock(&t->lock);
interrupts_restore(ipl);
return EINVAL;
}
t->tdebug.stop_request = 1;
spinlock_unlock(&t->lock);
interrupts_restore(ipl);
return 0;
}
/**
* Ask all threads in a task to stop (without waiting for it to happen).
*/
int tdebug_stop_task(task_id_t taskid)
{
return 0;
}