0,0 → 1,757 |
/** @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; |
} |