/*
* Copyright (c) 2008 Jiri Svoboda
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* - The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/** @addtogroup debug
* @{
*/
/** @file
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <libarch/syscall.h>
#include <ipc/ipc.h>
#include <fibril.h>
#include <loader/loader.h>
#include <errno.h>
#include <udebug.h>
#include <async.h>
#include <string.h>
#include "cmd.h"
#include "cons.h"
#include "dthread.h"
#include "breakpoint.h"
#include "include/arch.h"
#include "main.h"
void thread_debug_start(unsigned thread_hash);
#define IN_BUF_SIZE 64
static char in_buf[IN_BUF_SIZE];
#define MAX_ARGC 10
int cmd_argc;
char *cmd_argv[MAX_ARGC + 1]; /* need one spare field for cmd_split() */
int next_thread_id;
int app_phone;
volatile bool abort_debug;
volatile int paused;
static task_id_t task_id;
static loader_t *task_ldr;
static int program_run_fibril(void *arg);
static void program_run(void)
{
fid_t fid;
fid = fibril_create(program_run_fibril, NULL);
if (fid == 0) {
printf("Error creating fibril\n");
}
fibril_add_ready(fid);
}
static int program_run_fibril(void *arg)
{
int rc;
/*
* This must be done in background as it will block until
* we let the task reply to this call.
*/
rc = loader_run(task_ldr);
if (rc != 0) {
printf("Error running program\n");
}
task_ldr = NULL;
return 0;
}
static loader_t *preload_task(const char *path, char *const argv[],
task_id_t *task_id)
{
loader_t *ldr;
int rc;
/* Spawn a program loader */
ldr = loader_spawn(path);
if (ldr == NULL)
return 0;
/* Get task ID. */
rc = loader_get_task_id(ldr, task_id);
if (rc != EOK)
goto error;
/* Send program pathname */
rc = loader_set_pathname(ldr, path);
if (rc != EOK)
goto error;
/* Send arguments */
rc = loader_set_args(ldr, argv);
if (rc != EOK)
goto error;
/* Load the program. */
rc = loader_load_program(ldr);
if (rc != EOK)
goto error;
/* Success */
return ldr;
/* Error exit */
error:
loader_abort(ldr);
return NULL;
}
static void command_split(char *cmd_str)
{
char *p = cmd_str;
if (*p == '\0') {
cmd_argc = 0;
return;
}
cmd_argc = 1;
cmd_argv[0] = p;
while (*p != '\0') {
if (*p == ' ') {
cmd_argv[cmd_argc++] = p + 1;
*p = '\0';
}
++p;
}
}
static void command_run(void)
{
int i;
int cmp_len;
int len;
int idx_found;
int num_found;
len = str_length(cmd_argv[0]);
cmp_len = 1;
/* Silence warnings */
num_found = 0;
idx_found = 0;
while (cmp_len <= len + 1) {
num_found = 0;
i = 0;
while (cmd_table[i].name != NULL) {
if (str_lcmp(cmd_table[i].name, cmd_argv[0], cmp_len) == 0) {
idx_found = i;
++num_found;
}
++i;
}
if (num_found < 2) break;
++cmp_len;
}
if (num_found == 0) {
cons_printf("Unknown command. Try one of:\n");
cmd_help(0, NULL);
return;
}
if (cmd_argc - 1 != cmd_table[idx_found].argc) {
cons_printf("Command '%s' expects %d arguments\n",
cmd_table[idx_found].name, cmd_table[idx_found].argc);
return;
}
(*cmd_table[idx_found].proc)(cmd_argc, cmd_argv);
}
static void thread_stop(void)
{
dthread_t *dt;
uintptr_t pc;
dt = dthread_get();
pc = dthread_get_pc(dt);
cons_printf("[thread %d] stopped at 0x%lx\n", dt->id, pc);
dthread_stop_me();
cons_printf("[thread %d] go\n", dt->id);
}
/*
* Called by a fibril (from arch code) when a breakpoint is hit.
*/
void breakpoint_hit(breakpoint_t *b)
{
dthread_t *dt;
dt = dthread_get();
cons_printf("Thread %d hit breakpoint %d at 0x%lx\n",
dt->id, b->id, b->addr);
thread_stop();
}
/*
* Called by a fibril (from arch code) when a single instruction
* in singlestep is executed
*/
void singlestep_hit(void)
{
cons_printf("singlestep hit\n");
thread_stop();
}
static int connect_task(task_id_t task_id)
{
int rc;
unsigned evmask;
cons_printf("ipc_connect_kbox(%;;d)... ", task_id);
rc = ipc_connect_kbox(task_id);
cons_printf("-> %d\n", rc);
app_phone = rc;
if (rc < 0) return rc;
cons_printf("udebug_begin()... ");
rc = udebug_begin(app_phone);
cons_printf("-> %d\n", rc);
if (rc < 0) return rc;
evmask = UDEBUG_EM_ALL & ~(UDEBUG_EM_SYSCALL_B | UDEBUG_EM_SYSCALL_E);
cons_printf("udebug_set_evmask(0x%x)... ", evmask);
rc = udebug_set_evmask(app_phone, evmask);
cons_printf("-> %d\n", rc);
if (rc < 0) return rc;
return 0;
}
#define THASH_BUF_INIT_LENGTH 32
static int get_thread_list(thash_t **thash_buf_ptr, int *n)
{
int rc;
size_t tb_copied;
size_t tb_needed;
int i;
size_t tb_size;
thash_t *thash_buf;
tb_size = THASH_BUF_INIT_LENGTH * sizeof(thash_t);
rc = udebug_thread_read(app_phone, thash_buf,
tb_size, &tb_copied, &tb_needed);
if (rc < 0) return rc;
if (tb_needed > tb_size) {
/* Larger buffer needed */
tb_size = tb_needed;
if (!thash_buf) {
}
/* Try again */
rc = udebug_thread_read(app_phone, thash_buf,
tb_size, &tb_copied, &tb_needed);
if (rc < 0) return rc;
}
*n = tb_copied / sizeof(thash_t);
cons_printf("thread hashes:");
for (i = 0; i < *n; ++i) {
cons_printf("0x%x\n", thash_buf[i]);
}
cons_printf("Total of %u threads\n", *n);
*thash_buf_ptr = thash_buf;
return 0;
}
static void event_thread_b(unsigned hash)
{
async_serialize_start();
cons_printf("new thread, hash 0x%x\n", hash);
async_serialize_end();
thread_debug_start(hash);
}
static void debug_event(thash_t thash, udebug_event_t ev_type, sysarg_t val0)
{
switch (ev_type) {
case UDEBUG_EVENT_STOP:
cons_printf("stop event\n");
thread_stop();
break;
case UDEBUG_EVENT_THREAD_B:
event_thread_b(val0);
break;
case UDEBUG_EVENT_THREAD_E:
cons_printf("thread 0x%x exited\n", val0);
abort_debug = true;
break;
case UDEBUG_EVENT_BREAKPOINT:
arch_event_breakpoint(thash);
break;
case UDEBUG_EVENT_TRAP:
arch_event_trap(dthread_get());
break;
default:
cons_printf("unknown event type %d\n", ev_type);
break;
}
}
static int debug_loop(void *dt_arg)
{
int rc;
udebug_event_t ev_type;
unsigned val0, val1;
dthread_t *dt;
dt = (dthread_t *)dt_arg;
cons_printf("debug_loop(%d)\n", dt->id);
while (!abort_debug) {
/* Run thread until an event occurs */
rc = udebug_go(app_phone, dt->hash, &ev_type, &val0, &val1);
if (ev_type == UDEBUG_EVENT_FINISHED) {
cons_printf("thread %u debugging finished\n", dt->id);
break;
}
if (rc >= 0) debug_event(dt->hash, ev_type, val0);
}
cons_printf("debug_loop(%d) exiting\n", dt->id);
return 0;
}
void thread_debug_start(unsigned thash)
{
fid_t fid;
dthread_t *dt;
dt = dthread_new(thash);
fid = fibril_create(debug_loop, (void *)dt);
if (fid == 0) {
cons_printf("Warning: Failed creating fibril\n");
}
dt->fid = fid;
fibril_add_ready(fid);
}
static void debug_task(task_id_t task_id)
{
int i;
int rc;
thash_t *thash_buffer;
int n_threads;
rc = get_thread_list(&thash_buffer, &n_threads);
if (rc < 0) {
cons_printf("Failed to get thread list\n", rc);
return;
}
abort_debug = false;
for (i = 0; i < n_threads; i++) {
thread_debug_start(thash_buffer[i]);
}
while (!quit) {
cons_read_line(in_buf, IN_BUF_SIZE);
command_split(in_buf);
if (cmd_argc == 0) continue;
command_run();
}
cons_printf("terminate debugging session...\n");
abort_debug = true;
udebug_end(app_phone);
ipc_hangup(app_phone);
cons_printf("done\n");
return;
}
static void main_init(void)
{
next_thread_id = 1;
paused = 0;
list_initialize(&dthreads);
breakpoint_init();
cwt = NULL;
}
static void print_syntax()
{
printf("\tdebug <executable> [<arg1> [...]]\n");
printf("or\tdebug -t <task_id>\n");
}
static int parse_args(int argc, char *argv[])
{
char *arg;
char *err_p;
task_id = 0;
--argc; ++argv;
while (argc > 0) {
arg = *argv;
if (arg[0] == '-') {
if (arg[1] == 't') {
/* Trace an already running task */
--argc; ++argv;
task_id
= strtol(*argv
, &err_p
, 10);
task_ldr = NULL;
if (*err_p) {
printf("Task ID syntax error\n");
print_syntax();
return -1;
}
} else {
printf("Uknown option '%s'\n", arg
[0]);
print_syntax();
return -1;
}
} else {
break;
}
--argc; ++argv;
}
if (task_id != 0) {
if (argc == 0) return 0;
print_syntax();
return -1;
}
if (argc < 1) {
print_syntax();
return -1;
}
/* Preload the specified program file. */
printf("Spawning '%s' with arguments:\n", *argv
);
{
char **cp = argv;
while (*cp
) printf("'%s'\n", *cp
++);
}
task_ldr = preload_task(*argv, argv, &task_id);
return 0;
}
int main(int argc, char *argv[])
{
int rc;
cons_printf("Breakpoint Debugger\n");
main_init();
if (parse_args(argc, argv) < 0)
return 1;
rc = connect_task(task_id);
if (rc < 0) {
printf("Failed connecting to task %lld\n", task_id
);
return 1;
}
cons_printf("Connected to task %lld\n", task_id);
if (task_ldr != NULL) {
program_run();
}
debug_task(task_id);
return 0;
}
/** @}
*/