/** @addtogroup generic
* @{
*/
/**
* @file
* @brief Tdebug.
*/
#include <console/klog.h>
#include <proc/task.h>
#include <proc/thread.h>
#include <arch.h>
#include <errno.h>
#include <ipc/ipc.h>
#include <syscall/copy.h>
#include <udebug/udebug.h>
#include <udebug/udebug_ipc.h>
/**
* Get a phone's callee task id.
*
* This will return the id of the task to which the phone
* is connected.
*
* Interrupts must be already disabled.
*/
static task_id_t get_callee_task_id(phone_t *phone)
{
answerbox_t *box;
task_id_t taskid;
spinlock_lock(&phone->lock);
if (phone->state != IPC_PHONE_CONNECTED) {
spinlock_unlock(&phone->lock);
return NULL;
}
box = phone->callee;
spinlock_lock(&box->lock);
taskid = box->task->taskid;
spinlock_unlock(&box->lock);
spinlock_unlock(&phone->lock);
return taskid;
}
/**
* Prepare a thread for a debugging operation.
*
* Simply put, return thread t with t->debug_lock held,
* but only if it verifies all conditions.
*
* Specifically, verifies that thread t exists, is a userspace thread,
* belongs to the callee of 'phone'. It also locks t->debug_lock,
* making sure that t->debug_active is true - that the thread is
* in a valid debugging session.
*
* Returns EOK if all went well, or an error code otherwise.
* Interrupts must be already disabled when calling this function.
*
* Note: This function sports complicated locking.
*/
static int _thread_op_begin(phone_t *phone, thread_t *t)
{
int rc;
task_id_t taskid;
int task_match;
DEADLOCK_PROBE_INIT(p_tasklock);
taskid = get_callee_task_id(phone);
/* Need to lock down the thread and than it's owner task */
grab_locks:
spinlock_lock(&threads_lock);
if (!thread_exists(t)) {
spinlock_unlock(&threads_lock);
return ENOENT;
}
spinlock_lock(&t->debug_lock);
spinlock_lock(&t->lock);
if (!spinlock_trylock(&t->task->lock)) {
spinlock_unlock(&t->lock);
spinlock_unlock(&t->debug_lock);
DEADLOCK_PROBE(p_tasklock, DEADLOCK_THRESHOLD);
goto grab_locks; /* avoid deadlock */
}
/* Now verify that it's the callee */
task_match = (t->task->taskid == taskid);
spinlock_unlock(&t->task->lock);
if (!task_match) {
/* No such thread belonging to callee */
rc = ENOENT;
goto error_exit;
}
/* Verify that 't' is a userspace thread */
if ((t->flags & THREAD_FLAG_USPACE) == 0) {
/* It's not, deny its existence */
rc = ENOENT;
goto error_exit;
}
if ((t->debug_active != true) || (t->debug_stop != true)) {
/* Not in debugging session or already has GO */
rc = ENOENT;
goto error_exit;
}
spinlock_unlock(&threads_lock);
spinlock_unlock(&t->lock);
/* Only t->debug_lock left */
return EOK; /* All went well */
/* Executed when a check on the thread fails */
error_exit:
spinlock_unlock(&t->lock);
spinlock_unlock(&t->debug_lock);
spinlock_unlock(&threads_lock);
/* No locks left here */
return rc; /* Some errors occured */
}
static void _thread_op_end(thread_t *t)
{
spinlock_unlock(&t->debug_lock);
}
static int udebug_rp_go(call_t *call, phone_t *phone)
{
thread_t *t;
ipl_t ipl;
int rc;
klog_printf("debug_go()");
t = (thread_t *)IPC_GET_ARG2(call->data);
ipl = interrupts_disable();
/* On success, this will lock t->debug_lock */
rc = _thread_op_begin(phone, t);
if (rc != EOK) {
interrupts_restore(ipl);
return rc;
}
t->debug_go_call = call;
t->debug_stop = false;
t->cur_event = 0; /* none */
/*
* Neither t's lock nor threads_lock may be held during wakeup
*/
waitq_wakeup(&t->go_wq, WAKEUP_FIRST);
_thread_op_end(t);
interrupts_restore(ipl);
return 0; /* no backsend */
}
static int udebug_rp_args_read(call_t *call, phone_t *phone)
{
thread_t *t;
void *uspace_buffer;
int rc;
ipl_t ipl;
unative_t buffer[6];
klog_printf("debug_args_read()");
t = (thread_t *)IPC_GET_ARG2(call->data);
ipl = interrupts_disable();
/* On success, this will lock t->debug_lock */
rc = _thread_op_begin(phone, t);
if (rc != EOK) {
interrupts_restore(ipl);
return rc;
}
/* Additionally we need to verify that we are inside a syscall */
if (t->cur_event != UDEBUG_EVENT_SYSCALL) {
_thread_op_end(t);
interrupts_restore(ipl);
return EINVAL;
}
/* Copy to a local buffer before releasing the lock */
memcpy(buffer
, t
->syscall_args
, 6 * sizeof(unative_t
));
_thread_op_end(t);
interrupts_restore(ipl);
/* Now copy to userspace */
uspace_buffer = (void *)IPC_GET_ARG3(call->data);
rc = copy_to_uspace(uspace_buffer, buffer, 6 * sizeof(unative_t));
if (rc != 0) {
klog_printf("debug_args_read() - copy failed");
return rc;
}
klog_printf("debug_args_read() done");
return 1; /* actually need becksend with retval 0 */
}
static int udebug_rp_regs_read(call_t *call, phone_t *phone)
{
thread_t *t;
void *uspace_buffer;
unative_t to_copy;
int rc;
istate_t *state;
istate_t state_copy;
ipl_t ipl;
klog_printf("debug_regs_read()");
t = (thread_t *) IPC_GET_ARG2(call->data);
ipl = interrupts_disable();
/* On success, this will lock t->debug_lock */
rc = _thread_op_begin(phone, t);
if (rc != EOK) {
interrupts_restore(ipl);
return rc;
}
state = t->uspace_state;
if (state == NULL) {
_thread_op_end(t);
interrupts_restore(ipl);
klog_printf("debug_regs_read() - istate not available");
return EBUSY;
}
/* Copy to a local buffer so that we can release the lock */
memcpy(&state_copy
, state
, sizeof(state_copy
));
_thread_op_end(t);
interrupts_restore(ipl);
uspace_buffer = (void *)IPC_GET_ARG3(call->data);
to_copy = IPC_GET_ARG4(call->data);
if (to_copy > sizeof(istate_t)) to_copy = sizeof(istate_t);
rc = copy_to_uspace(uspace_buffer, &state_copy, to_copy);
if (rc != 0) {
klog_printf("debug_regs_read() - copy failed");
return rc;
}
IPC_SET_ARG1(call->data, to_copy);
IPC_SET_ARG2(call->data, sizeof(istate_t));
klog_printf("debug_regs_read() done");
return 1; /* actually need becksend with retval 0 */
}
static int udebug_rp_regs_write(call_t *call, phone_t *phone)
{
thread_t *t;
void *uspace_data;
unative_t to_copy;
int rc;
istate_t *state;
istate_t data_copy;
ipl_t ipl;
klog_printf("debug_regs_write()");
/* First copy to a local buffer */
uspace_data = (void *)IPC_GET_ARG3(call->data);
to_copy = IPC_GET_ARG4(call->data);
if (to_copy > sizeof(istate_t)) to_copy = sizeof(istate_t);
rc = copy_from_uspace(&data_copy, uspace_data, to_copy);
if (rc != 0) {
klog_printf("debug_regs_write() - copy failed");
return rc;
}
/* Now try to change the thread's uspace_state */
ipl = interrupts_disable();
t = (thread_t *) IPC_GET_ARG2(call->data);
/* On success, this will lock t->debug_lock */
rc = _thread_op_begin(phone, t);
if (rc != EOK) {
interrupts_restore(ipl);
return rc;
}
state = t->uspace_state;
if (state == NULL) {
_thread_op_end(t);
interrupts_restore(ipl);
klog_printf("debug_regs_write() - istate not available");
return EBUSY;
}
memcpy(t
->uspace_state
, &data_copy
, sizeof(t
->uspace_state
));
_thread_op_end(t);
interrupts_restore(ipl);
/* Set answer values */
IPC_SET_ARG1(call->data, to_copy);
IPC_SET_ARG2(call->data, sizeof(istate_t));
klog_printf("debug_regs_write() done");
return 1; /* actually need becksend with retval 0 */
}
static int udebug_rp_mem_write(call_t *call, phone_t *phone)
{
void *uspace_data;
unative_t to_copy;
int rc;
void *buffer;
klog_printf("udebug_rp_mem_write()");
uspace_data = (void *)IPC_GET_ARG2(call->data);
to_copy = IPC_GET_ARG4(call->data);
buffer
= malloc(to_copy
, 0); // ???
rc = copy_from_uspace(buffer, uspace_data, to_copy);
if (rc != 0) {
klog_printf(" - copy failed");
return rc;
}
call->buffer = buffer;
klog_printf(" - done");
return 1; /* actually need becksend with retval 0 */
}
int udebug_request_preprocess(call_t *call, phone_t *phone)
{
int rc;
switch (IPC_GET_ARG1(call->data)) {
case UDEBUG_M_GO:
rc = udebug_rp_go(call, phone);
return rc;
case UDEBUG_M_ARGS_READ:
rc = udebug_rp_args_read(call, phone);
return rc;
case UDEBUG_M_REGS_READ:
rc = udebug_rp_regs_read(call, phone);
return rc;
case UDEBUG_M_REGS_WRITE:
rc = udebug_rp_regs_write(call, phone);
return rc;
case UDEBUG_M_MEM_WRITE:
rc = udebug_rp_mem_write(call, phone);
return rc;
default:
break;
}
return 0;
}
static void udebug_receive_begin(call_t *call)
{
ipl_t ipl;
int reply;
thread_t *t;
link_t *cur;
klog_printf("debug_begin()");
ipl = interrupts_disable();
klog_printf("debugging task %llu", TASK->taskid);
spinlock_lock(&TASK->lock);
if (TASK->dt_state != UDEBUG_TS_INACTIVE) {
spinlock_unlock(&TASK->lock);
interrupts_restore(ipl);
klog_printf("debug_begin(): busy error");
IPC_SET_RETVAL(call->data, EBUSY);
ipc_answer(&TASK->kernel_box, call);
}
TASK->dt_state = UDEBUG_TS_BEGINNING;
TASK->debug_begin_call = call;
TASK->debugger = call->sender;
if (TASK->not_stoppable_count == 0) {
TASK->dt_state = UDEBUG_TS_ACTIVE;
TASK->debug_begin_call = NULL;
reply = 1; /* immediate reply */
} else {
reply = 0; /* no reply */
}
/* Set debug_active on all of the task's userspace threads */
for (cur = TASK->th_head.next; cur != &TASK->th_head; cur = cur->next) {
t = list_get_instance(cur, thread_t, th_link);
spinlock_lock(&t->debug_lock);
if ((t->flags & THREAD_FLAG_USPACE) != 0)
t->debug_active = true;
spinlock_unlock(&t->debug_lock);
}
spinlock_unlock(&TASK->lock);
interrupts_restore(ipl);
klog_printf("debug_begin() done (%s)",
reply ? "reply" : "stoppability wait");
if (reply) ipc_answer(&TASK->kernel_box, call);
}
static void udebug_receive_end(call_t *call)
{
ipl_t ipl;
int rc;
klog_printf("udebug_receive_end()");
ipl = interrupts_disable();
spinlock_lock(&TASK->lock);
rc = udebug_task_cleanup(TASK);
klog_printf("task %llu", TASK->taskid);
spinlock_unlock(&TASK->lock);
interrupts_restore(ipl);
if (rc < 0) {
IPC_SET_RETVAL(call->data, EINVAL);
ipc_answer(&TASK->kernel_box, call);
return;
}
IPC_SET_RETVAL(call->data, 0);
ipc_answer(&TASK->kernel_box, call);
}
static void udebug_receive_thread_read(call_t *call)
{
thread_t *t;
link_t *cur;
unative_t uspace_addr;
unative_t to_copy;
unsigned total_bytes;
unsigned buf_size;
unative_t tid;
unsigned num_threads, copied_ids;
ipl_t ipl;
unative_t *buffer;
int flags;
klog_printf("debug_thread_read()");
ipl = interrupts_disable();
spinlock_lock(&TASK->lock);
/* Verify task state */
if (TASK->dt_state != UDEBUG_TS_ACTIVE) {
spinlock_unlock(&TASK->lock);
interrupts_restore(ipl);
IPC_SET_RETVAL(call->data, EINVAL);
ipc_answer(&TASK->kernel_box, call);
return;
}
/* Count the threads first */
num_threads = 0;
for (cur = TASK->th_head.next; cur != &TASK->th_head; cur = cur->next) {
/* Count all threads, to be on the safe side */
++num_threads;
}
/* Allocate a buffer and copy down the threads' ids */
buffer
= malloc(num_threads
* sizeof(unative_t
), 0); // ???
copied_ids = 0;
for (cur = TASK->th_head.next; cur != &TASK->th_head; cur = cur->next) {
t = list_get_instance(cur, thread_t, th_link);
spinlock_lock(&t->lock);
flags = t->flags;
spinlock_unlock(&t->lock);
/* Not interested in kernel threads */
if ((flags & THREAD_FLAG_USPACE) != 0) {
/* Using thread struct pointer for identification */
tid = (unative_t) t;
buffer[copied_ids++] = tid;
}
}
spinlock_unlock(&TASK->lock);
interrupts_restore(ipl);
/*
* Prepare data and send it back through call->buffer
*/
uspace_addr = IPC_GET_ARG2(call->data);
buf_size = IPC_GET_ARG3(call->data);
total_bytes = copied_ids * sizeof(unative_t);
if (buf_size > total_bytes)
to_copy = total_bytes;
else
to_copy = buf_size;
IPC_SET_RETVAL(call->data, 0);
/* ARG1=dest, ARG2=size as in IPC_M_DATA_READ so that
same code in process_answer() can be used
(no way to distinguish method in answer) */
IPC_SET_ARG1(call->data, uspace_addr);
IPC_SET_ARG2(call->data, to_copy);
IPC_SET_ARG3(call->data, total_bytes);
call->buffer = (void *)buffer;
ipc_answer(&TASK->kernel_box, call);
}
static void udebug_receive_mem_read(call_t *call)
{
unative_t uspace_dst;
void *uspace_ptr;
unsigned size;
void *buffer;
int rc;
klog_printf("debug_mem_read()");
uspace_dst = IPC_GET_ARG2(call->data);
uspace_ptr = (void *)IPC_GET_ARG3(call->data);
size = IPC_GET_ARG4(call->data);
buffer
= malloc(size
, 0); // ???
klog_printf("debug_mem_read: src=%u, size=%u", uspace_ptr, size);
/* NOTE: this is not strictly from a syscall... but that shouldn't
* be a problem */
rc = copy_from_uspace(buffer, uspace_ptr, size);
if (rc) {
IPC_SET_RETVAL(call->data, rc);
return;
}
klog_printf("first word: %u", *((unative_t *)buffer));
IPC_SET_RETVAL(call->data, 0);
/* Hack: ARG1=dest, ARG2=size as in IPC_M_DATA_READ so that
same code in process_answer() can be used
(no way to distinguish method in answer) */
IPC_SET_ARG1(call->data, uspace_dst);
IPC_SET_ARG2(call->data, size);
call->buffer = buffer;
ipc_answer(&TASK->kernel_box, call);
}
static void udebug_receive_mem_write(call_t *call)
{
void *uspace_dst;
unsigned size;
void *buffer;
int rc;
udebug_task_state_t dts;
klog_printf("udebug_receive_mem_write()");
/* Verify task state */
spinlock_lock(&TASK->lock);
dts = TASK->dt_state;
spinlock_unlock(&TASK->lock);
if (dts != UDEBUG_TS_ACTIVE) {
IPC_SET_RETVAL(call->data, EBUSY);
ipc_answer(&TASK->kernel_box, call);
return;
}
uspace_dst = (void *)IPC_GET_ARG3(call->data);
size = IPC_GET_ARG4(call->data);
buffer = call->buffer;
klog_printf("dst=%u, size=%u", uspace_dst, size);
/* NOTE: this is not strictly from a syscall... but that shouldn't
* be a problem */
rc = copy_to_uspace(uspace_dst, buffer, size);
if (rc) {
IPC_SET_RETVAL(call->data, rc);
ipc_answer(&TASK->kernel_box, call);
return;
}
IPC_SET_RETVAL(call->data, 0);
call->buffer = NULL;
ipc_answer(&TASK->kernel_box, call);
}
/**
* Handle a debug call received on the kernel answerbox.
*
* This is called by the kbox servicing thread.
*/
void udebug_call_receive(call_t *call)
{
int debug_method;
debug_method = IPC_GET_ARG1(call->data);
switch (debug_method) {
case UDEBUG_M_BEGIN:
udebug_receive_begin(call);
break;
case UDEBUG_M_END:
udebug_receive_end(call);
break;
case UDEBUG_M_THREAD_READ:
udebug_receive_thread_read(call);
break;
case UDEBUG_M_MEM_READ:
udebug_receive_mem_read(call);
break;
case UDEBUG_M_MEM_WRITE:
udebug_receive_mem_write(call);
break;
}
}
/** @}
*/