diff options
-rw-r--r-- | debugger.c | 744 | ||||
-rw-r--r-- | debugger.h | 86 | ||||
-rw-r--r-- | helpers.c | 15 | ||||
-rw-r--r-- | helpers.h | 2 | ||||
-rw-r--r-- | misplays.c | 261 |
5 files changed, 825 insertions, 283 deletions
@@ -1,5 +1,8 @@ +#include <dirent.h> +#include <errno.h> #include <stdio.h> #include <stdlib.h> +#include <string.h> #include <sys/ptrace.h> #include <sys/uio.h> #include <sys/wait.h> @@ -10,18 +13,117 @@ #include "debugger.h" #include "helpers.h" -static const int PTRACE_OPTIONS = PTRACE_O_TRACESYSGOOD; +static const int PTRACE_OPTIONS = PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACECLONE; +static const int INTERRUPT_ON_SEIZE = 1; + +static int detect_breakpoint(struct thread *th) { + struct user_regs_struct regs; + int check = 0; + int restart = 0; + + ptrace(PTRACE_GETREGS, th->id, NULL, ®s); + + struct list *breaks = &th->proc->breakpoints; + for (struct breakpoint *b = breaks->head; b != breaks->end; b = b->next) { + /* should be if (b->active) ? + * but there is a potential other bug that can happen + * with the order of events in dbg_wait() */ + if (b->enabled) { + if (!check) { + if (regs.rip - 1 == b->address) { + regs.rip--; + ptrace(PTRACE_SETREGS, th->id, NULL, ®s); + check = 1; + + if (b->stack != 0 && b->stack != regs.rsp) { + restart = 1; + } + + if (b->tid != 0 && b->tid != th->id) { + restart = 1; + } + } + } + + if (b->enabled < 0 && !restart) { + struct breakpoint *del = b; + b = b->prev; + list_remove(del); + free(del); + } + } + } + + return restart; +} + +static void install_breakpoints(struct thread *th) { + struct list *breaks = &th->proc->breakpoints; + for (struct breakpoint *b = breaks->head; b != breaks->end; b = b->next) { + if (b->enabled && !b->active) { + unsigned long data; + data = ptrace(PTRACE_PEEKTEXT, th->id, b->address, NULL); + b->orig = data; + + data = (data & ~0xff) | BREAKPOINT_INSN; + ptrace(PTRACE_POKETEXT, th->id, b->address, data); + b->active = 1; + } + } +} + +static void uninstall_breakpoints(struct process *proc) { + struct list *breaks = &proc->breakpoints; + for (struct breakpoint *b = breaks->tail; b != breaks->end; b = b->prev) { + if (b->active) { + ptrace(PTRACE_POKETEXT, proc->id, b->address, b->orig); + b->active = 0; + } + } +} + +static void clear_breakpoints(struct process *proc) { + while (proc->breakpoints.head != proc->breakpoints.end) { + struct breakpoint *b = proc->breakpoints.head; + list_remove(b); + free(b); + } +} + +static void clear_states(struct thread *th) { + while (th->states.head != th->states.end) { + struct state *s = th->states.head; + + while (s->maps.head != s->maps.end) { + struct map *m = s->maps.head; + list_remove(m); + free(m->data); + free(m); + } + + list_remove(s); + free(s); + } + + th->state = NULL; + th->clearstates = 0; +} + +static void capture_state(struct thread *th) { + if (th->clearstates) { + clear_states(th); + } -static void capture_state(struct tracee *dbg) { struct state *s = xmalloc(sizeof(*s)); - ptrace(PTRACE_GETREGS, dbg->id, NULL, &s->regs); - ptrace(PTRACE_GETFPREGS, dbg->id, NULL, &s->fpregs); + ptrace(PTRACE_GETREGS, th->id, NULL, &s->regs); + ptrace(PTRACE_GETFPREGS, th->id, NULL, &s->fpregs); list_init(&s->maps); - list_insert(dbg->states.end, s); - dbg->state = s; - char mapspath[128], entry[512]; - snprintf(mapspath, sizeof(mapspath), "/proc/%li/maps", (long)dbg->id); + list_insert(th->states.end, s); + th->state = s; + + char mapspath[32], entry[512]; + snprintf(mapspath, sizeof(mapspath), "/proc/%li/maps", (long)th->id); FILE *maps = fopen(mapspath, "r"); if (maps) { @@ -33,7 +135,7 @@ static void capture_state(struct tracee *dbg) { struct iovec loc = { m->data, size }; struct iovec rem = { (void *)m->start, size }; - if (process_vm_readv(dbg->id, &loc, 1, &rem, 1, 0) < 0) { + if (process_vm_readv(th->id, &loc, 1, &rem, 1, 0) < 0) { free(m->data); free(m); continue; @@ -46,232 +148,304 @@ static void capture_state(struct tracee *dbg) { } } -static void clear_states(struct tracee *dbg) { - while (dbg->states.head != dbg->states.end) { - struct state *s = dbg->states.head; - while (s->maps.head != s->maps.end) { - struct map *m = s->maps.head; - list_remove(m); - free(m->data); - free(m); +static struct thread *new_thread(struct process *proc, pid_t id) { + struct thread *th = xmalloc(sizeof(*th)); + th->proc = proc; + list_init(&th->states); + th->state = NULL; + th->clearstates = 0; + th->id = id; + th->stopped = 0; + th->signal = 0; + th->cont = 0; + th->status = "RUNNING"; + return th; +} + +static void interrupt_all_threads(struct process *proc) { + struct list *threads = &proc->threads; + for (struct thread *th = threads->head; th != threads->end; th = th->next) { + if (!th->stopped) { + ptrace(PTRACE_INTERRUPT, th->id, NULL, NULL); + while (!dbg_wait(th, 0)) {} } - list_remove(s); - free(s); } - dbg->state = NULL; } -static void install_breakpoints(struct tracee *dbg) { - struct list *breaks = &dbg->breaks; - for (struct breakpoint *b = breaks->head; b != breaks->end; b = b->next) { - if (b->enabled) { - unsigned long data; - data = ptrace(PTRACE_PEEKTEXT, dbg->id, b->address, NULL); - b->orig = data; +void add_breakpoint(struct process *proc, unsigned long address, unsigned long stack, pid_t tid, int enabled) { + struct breakpoint *b = xmalloc(sizeof(*b)); + b->orig = 0; + b->address = address; + b->stack = stack; + b->tid = tid; + b->enabled = enabled; + b->active = 0; + list_insert(proc->breakpoints.end, b); +} - data = (data & ~0xff) | BREAKPOINT_INSN; - ptrace(PTRACE_POKETEXT, dbg->id, b->address, data); - b->active = 1; +int is_breakpoint(struct process *proc, unsigned long address) { + struct list *breaks = &proc->breakpoints; + for (struct breakpoint *b = breaks->head; b != breaks->end; b = b->next) { + if (b->address == address) { + return 1; } } + return 0; } -static int uninstall_breakpoints(struct tracee *dbg, int stop) { - struct user_regs_struct regs; - int ch = 0; - int reenter = 0; - if (stop) { - ptrace(PTRACE_GETREGS, dbg->id, NULL, ®s); - } +int dbg_process(struct process *proc, pid_t pid, int child) { + proc->id = pid; + proc->child = child; + list_init(&proc->breakpoints); + list_init(&proc->threads); - struct list *breaks = &dbg->breaks; - for (struct breakpoint *b = breaks->head; b != breaks->end; b = b->next) { - if (b->active) { - ptrace(PTRACE_POKETEXT, dbg->id, b->address, b->orig); - b->active = 0; + char taskpath[32]; + snprintf(taskpath, sizeof(taskpath), "/proc/%li/task", (long)pid); - /* detect stop at breakpoint */ - if (stop && !ch) { - if (regs.rip - 1 == b->address) { - regs.rip--; - ptrace(PTRACE_SETREGS, dbg->id, NULL, ®s); - ch = 1; + DIR *taskdir = opendir(taskpath); + if (!taskdir) { + return -1; + } - if (b->stack != 0 && b->stack != regs.rsp) { - reenter = 1; - } - } - } + struct dirent *task; + while ((task = readdir(taskdir))) { + pid_t id = strtoul(task->d_name, NULL, 0); - if (b->enabled < 0 && !reenter) { - struct breakpoint *del = b; - b = b->prev; - list_remove(del); - free(del); + if (id != 0) { + if (ptrace(PTRACE_SEIZE, id, NULL, PTRACE_OPTIONS) < 0) { + closedir(taskdir); + return -1; } + + struct thread *th = new_thread(proc, id); + list_insert(proc->threads.end, th); } } - return reenter; -} - -int dbg_process(struct tracee *dbg, pid_t pid) { - if (ptrace(PTRACE_ATTACH, pid, NULL, NULL) < 0) { - return -1; + if (INTERRUPT_ON_SEIZE) { + interrupt_all_threads(proc); } - list_init(&dbg->breaks); - list_init(&dbg->states); - dbg->state = NULL; - dbg->id = pid; - dbg->child = 0; - dbg->stopped = 0; - dbg->status = 0; - dbg->signal = 0; - dbg->cont = 0; - dbg->buff = NULL; - dbg->buffsize = 0; - - while (!dbg_wait(dbg)) {} - ptrace(PTRACE_SETOPTIONS, pid, NULL, PTRACE_OPTIONS); + closedir(taskdir); return 0; } -int dbg_new_process(struct tracee *dbg, char **argv, struct console *cons) { - pid_t pid = fork(); - if (pid < 0) { - return -1; - } +int dbg_detach(struct process *proc) { + interrupt_all_threads(proc); + uninstall_breakpoints(proc); + clear_breakpoints(proc); - if (pid == 0) { - console_configslave(cons); - setpgid(0, 0); - ptrace(PTRACE_TRACEME, 0, NULL, NULL); - close_range(STDERR_FILENO+1, ~0U, CLOSE_RANGE_UNSHARE); - execvp(argv[0], argv); - exit(-1); + while (proc->threads.head != proc->threads.end) { + struct thread *th = proc->threads.head; + if (th->id >= 0) { + ptrace(PTRACE_DETACH, th->id, NULL, th->signal); + } + clear_states(th); + list_remove(th); + free(th); } - list_init(&dbg->breaks); - list_init(&dbg->states); - dbg->state = NULL; - dbg->id = pid; - dbg->child = 1; - dbg->stopped = 0; - dbg->status = 0; - dbg->signal = 0; - dbg->cont = 0; - dbg->buff = NULL; - dbg->buffsize = 0; - - while (!dbg_wait(dbg)) {} - ptrace(PTRACE_SETOPTIONS, pid, NULL, PTRACE_O_EXITKILL | PTRACE_OPTIONS); return 0; } -int dbg_wait(struct tracee *dbg) { - if (dbg->stopped) { - return 1; +int dbg_wait(struct thread *th, int dostops) { + if (th->id < 0) { + return -1; } - if (waitpid(dbg->id, &dbg->status, WNOHANG | __WALL) <= 0) { + int status; + if (waitpid(th->id, &status, __WALL | WNOHANG) <= 0) { return 0; } - if (dbg->cont != 0) { - install_breakpoints(dbg); - ptrace(dbg->cont, dbg->id, NULL, NULL); - dbg->cont = 0; - return 0; + if (WIFEXITED(status)) { + th->id = -1; + th->stopped = 1; + th->signal = WEXITSTATUS(status); + th->cont = 0; + th->status = "EXITED"; + return 1; } - if (uninstall_breakpoints(dbg, 1)) { - ptrace(PTRACE_SINGLESTEP, dbg->id, NULL, NULL); - dbg->cont = PTRACE_CONT; - return 0; + if (WIFSIGNALED(status)) { + th->id = -2; + th->stopped = 1; + th->signal = WTERMSIG(status); + th->cont = 0; + th->status = "TERMINATED"; + return 1; } - capture_state(dbg); - dbg->stopped = 1; - return 1; -} + unsigned long eventmsg; + struct thread *newth; + ptrace(PTRACE_GETEVENTMSG, th->id, NULL, &eventmsg); + + if (WIFSTOPPED(status)) { + switch (status >> 8) { + case SIGTRAP | (PTRACE_EVENT_CLONE << 8): + newth = new_thread(th->proc, eventmsg); + list_insert(th->proc->threads.end, newth); + while (!dbg_wait(newth, 0)) {} + + th->stopped = 1; + th->signal = 0; + th->cont = 0; + th->status = "CLONE EVENT"; + + if (dostops) { + interrupt_all_threads(th->proc); + uninstall_breakpoints(th->proc); + } -/* note: move head < state < tail checks from main into these function - * add a dbg_stepback function */ + capture_state(th); + return 1; -int dbg_stepin(struct tracee *dbg) { - /* consider making this a no-op if we are not stopped on a call */ - ptrace(PTRACE_SINGLESTEP, dbg->id, NULL, NULL); - dbg->stopped = 0; - dbg->cont = 0; - return 0; + case SIGTRAP | (PTRACE_EVENT_EXIT << 8): + th->stopped = 1; + th->signal = 0; //eventmsg; + th->cont = 0; + th->status = "EXIT EVENT"; + + if (dostops) { + interrupt_all_threads(th->proc); + uninstall_breakpoints(th->proc); + } + + capture_state(th); + return 1; + + case SIGTRAP | (PTRACE_EVENT_STOP << 8): + th->stopped = 1; + th->signal = 0; + th->cont = 0; + th->status = "STOP EVENT"; + + if (dostops) { + interrupt_all_threads(th->proc); + uninstall_breakpoints(th->proc); + } + + capture_state(th); + return 1; + + case SIGTRAP | 0x80: + th->stopped = 1; + th->signal = 0; + th->cont = 0; + th->status = "SYSCALL EVENT"; + + if (dostops) { + interrupt_all_threads(th->proc); + uninstall_breakpoints(th->proc); + } + + capture_state(th); + return 1; + + case SIGTRAP: + th->stopped = 1; + th->signal = 0; + + if (th->cont != 0) { + install_breakpoints(th); + ptrace(th->cont, th->id, NULL, NULL); + th->cont = 0; + th->stopped = 0; + th->status = "RUNNING"; + return 0; + } + + if (dostops) { + interrupt_all_threads(th->proc); + uninstall_breakpoints(th->proc); + } + + if (detect_breakpoint(th)) { + dbg_cont(th, PTRACE_CONT); + return 0; + } + + th->status = "STEP/BREAKPOINT"; + capture_state(th); + return 1; + + default: + th->stopped = 1; + th->signal = WSTOPSIG(status); + th->cont = 0; + th->status = "SIGNAL DELIVERY"; + + if (dostops) { + interrupt_all_threads(th->proc); + uninstall_breakpoints(th->proc); + } + + capture_state(th); + return 1; + } + } + + return -1; } -int dbg_stepover(struct tracee *dbg) { - csh handle; - cs_open(CS_ARCH_X86, CS_MODE_64, &handle); - - uint64_t address = dbg->state->regs.rip; - size_t size = 128; - const uint8_t *code = deref(dbg, address, size); - cs_insn *insn = cs_malloc(handle); - - cs_disasm_iter(handle, &code, &size, &address, insn); - - if (insn->id == X86_INS_CALL) { - struct breakpoint *b = xmalloc(sizeof(*b)); - b->address = address; - b->stack = dbg->state->regs.rsp; - b->enabled = -1; - b->active = 0; - list_insert(dbg->breaks.end, b); - dbg->cont = PTRACE_CONT; - //dbg_cont(dbg, PTRACE_CONT); - } else { - dbg->cont = 0; - //dbg_stepin(dbg); +int dbg_cont(struct thread *th, int cont) { + if (th->id < 0 || !th->stopped) { + return -1; } - ptrace(PTRACE_SINGLESTEP, dbg->id, NULL, NULL); - dbg->stopped = 0; + struct list *threads = &th->proc->threads; + for (struct thread *t = threads->head; t != threads->end; t = t->next) { + ptrace(PTRACE_SINGLESTEP, t->id, NULL, t->signal); + + t->stopped = 0; + t->signal = 0; + t->cont = cont; + t->status = "RUNNING"; + t->clearstates = 1; + } - cs_free(insn, 1); - cs_close(&handle); + /* are there timing concerns here? Critically, we + * are doing this before the event loop has a chance to + * wait on any thread and actually start it in the cont */ + /* cant do it here, since no thread is stopped (ptrace + * returns ESRCH). */ + //install_breakpoints(th->proc); return 0; } -/* -int dbg_stepout(struct tracee *dbg) { - unsigned long bp = dbg->state->regs.rbp; - unsigned long ra = *(unsigned long *)deref(dbg, bp+8, sizeof(unsigned long)); +int dbg_stepin(struct thread *th) { + if (th->id < 0 || !th->stopped) { + return -1; + } - struct breakpoint *b = xmalloc(sizeof(*b)); - b->address = ra; - b->stack = 0; - b->enabled = -1; - b->active = 0; - list_insert(dbg->breaks.end, b); + if (ptrace(PTRACE_SINGLESTEP, th->id, NULL, th->signal) < 0) { + return -1; + } - ptrace(PTRACE_SINGLESTEP, dbg->id, NULL, NULL); - //clear_states(dbg); // keep this? - dbg->stopped = 0; - dbg->cont = PTRACE_CONT; + th->stopped = 0; + th->signal = 0; + th->cont = 0; + th->status = "RUNNING"; + th->clearstates = 0; return 0; } -*/ -int dbg_cont(struct tracee *dbg, int cont) { - ptrace(PTRACE_SINGLESTEP, dbg->id, NULL, NULL); - clear_states(dbg); - dbg->stopped = 0; - dbg->cont = cont; +int dbg_intr(struct thread *th) { + if (th->id < 0 || th->stopped) { + return -1; + } + + ptrace(PTRACE_INTERRUPT, th->id, NULL, NULL); return 0; } -void *deref(struct tracee *dbg, unsigned long addr, size_t size) { - (void)size; // todo +void *deref(struct thread *th, unsigned long addr, size_t size) { + (void)size; + if (!th->state) { + return NULL; + } - struct list *maps = &dbg->state->maps; + struct list *maps = &th->state->maps; for (struct map *m = maps->head; m != maps->end; m = m->next) { if (m->start <= addr && addr < m->end) { return (unsigned char *)m->data + (addr - m->start); @@ -280,3 +454,215 @@ void *deref(struct tracee *dbg, unsigned long addr, size_t size) { return NULL; } + + + + + + + + + + + + + + + +///* +//static const char *strstatus(int status) { +// static char buff[128]; +// +// if (WIFEXITED(status)) { +// snprintf(buff, sizeof(buff), "exited: %i", WEXITSTATUS(status)); +// } else if (WIFSIGNALED(status)) { +// snprintf(buff, sizeof(buff), "terminated: %s", strsignal(WTERMSIG(status))); +// } else if (WIFSTOPPED(status)) { +// snprintf(buff, sizeof(buff), "stopped: %s", strsignal(WSTOPSIG(status))); +// } else { +// snprintf(buff, sizeof(buff), "UNKNOWN STATUS VALUE"); +// } +// +// return buff; +//} +//*/ +// +//static int all_cont(struct process *proc) { +// /* install breakpoints */ +// struct list *breaks = &proc->breakpoints; +// for (struct breakpoint *b = breaks->head; b != breaks->end; b = b->next) { +// if (b->enabled) { +// unsigned long data; +// data = ptrace(PTRACE_PEEKTEXT, proc->id, b->address, NULL); +// b->orig = data; +// +// data = (data & ~0xff) | BREAKPOINT_INSN; +// ptrace(PTRACE_POKETEXT, proc->id, b->address, data); +// b->active = 1; +// } +// } +// +// /* continue all threads */ +// struct list *threads = &proc->threads; +// for (struct thread *th = threads->head; th != threads->end; th = th->next) { +// ptrace(PTRACE_); +// } +// return -1; +//} +// +///* current problem to solve: +// * if a SINGLESTEP is interrupted (it was over a blocking syscall for example), +// * a CONT out of that interruption will stop AS IF the SINGLESTEP had completed. +// */ +///* +// * new: +// * next step is to trigger all threads starting on continue. +// */ + + + + + + + + +/* PTRACE_GETSIGINFO ! */ +/* handle ptrace() -> ESRCH as unexpectedly dead tracee */ + + + + + + + + + + +// +//int dbg_new_process(struct tracee *dbg, char **argv, struct console *cons) { +// pid_t pid = fork(); +// if (pid < 0) { +// return -1; +// } +// +// if (pid == 0) { +// console_configslave(cons); +// setpgid(0, 0); +// ptrace(PTRACE_TRACEME, 0, NULL, NULL); +// close_range(STDERR_FILENO+1, ~0U, CLOSE_RANGE_UNSHARE); +// execvp(argv[0], argv); +// exit(-1); +// } +// +// list_init(&dbg->breaks); +// list_init(&dbg->states); +// dbg->state = NULL; +// dbg->id = pid; +// dbg->gid = pid; +// dbg->child = 1; +// dbg->stopped = 0; +// dbg->status = 0; +// dbg->signal = 0; +// dbg->cont = 0; +// dbg->buff = NULL; +// dbg->buffsize = 0; +// +// while (!dbg_wait(dbg, NULL)) {} +// ptrace(PTRACE_SETOPTIONS, pid, NULL, PTRACE_O_EXITKILL | PTRACE_OPTIONS); +// return 0; +//} +// +///* note: move head < state < tail checks from main into these function +// * add a dbg_stepback function */ +// +//int dbg_stepin(struct tracee *dbg) { +// /* consider making this a no-op if we are not stopped on a call */ +// ptrace(PTRACE_SINGLESTEP, dbg->id, NULL, NULL); +// dbg->stopped = 0; +// dbg->cont = 0; +// return 0; +//} +// +//int dbg_stepover(struct tracee *dbg) { +// csh handle; +// cs_open(CS_ARCH_X86, CS_MODE_64, &handle); +// +// uint64_t address = dbg->state->regs.rip; +// size_t size = 128; +// const uint8_t *code = deref(dbg, address, size); +// cs_insn *insn = cs_malloc(handle); +// +// cs_disasm_iter(handle, &code, &size, &address, insn); +// +// if (insn->id == X86_INS_CALL) { +// struct breakpoint *b = xmalloc(sizeof(*b)); +// b->address = address; +// b->stack = dbg->state->regs.rsp; +// b->enabled = -1; +// b->active = 0; +// list_insert(dbg->breaks.end, b); +// dbg->cont = PTRACE_CONT; +// //dbg_cont(dbg, PTRACE_CONT); +// } else { +// dbg->cont = 0; +// //dbg_stepin(dbg); +// } +// +// ptrace(PTRACE_SINGLESTEP, dbg->id, NULL, NULL); +// dbg->stopped = 0; +// +// cs_free(insn, 1); +// cs_close(&handle); +// return 0; +//} +// +///* +//int dbg_stepout(struct tracee *dbg) { +// unsigned long bp = dbg->state->regs.rbp; +// unsigned long ra = *(unsigned long *)deref(dbg, bp+8, sizeof(unsigned long)); +// +// struct breakpoint *b = xmalloc(sizeof(*b)); +// b->address = ra; +// b->stack = 0; +// b->enabled = -1; +// b->active = 0; +// list_insert(dbg->breaks.end, b); +// +// ptrace(PTRACE_SINGLESTEP, dbg->id, NULL, NULL); +// //clear_states(dbg); // keep this? +// dbg->stopped = 0; +// dbg->cont = PTRACE_CONT; +// return 0; +//} +//*/ +// +//int dbg_cont(struct tracee *dbg, int cont) { +// ptrace(PTRACE_SINGLESTEP, dbg->id, NULL, NULL); +// clear_states(dbg); +// dbg->stopped = 0; +// dbg->cont = cont; +// return 0; +//} +// +//void *deref(struct tracee *dbg, unsigned long addr, size_t size) { +// (void)size; // todo +// +// struct list *maps = &dbg->state->maps; +// for (struct map *m = maps->head; m != maps->end; m = m->next) { +// if (m->start <= addr && addr < m->end) { +// return (unsigned char *)m->data + (addr - m->start); +// } +// } +// +// return NULL; +//} +// +//void dbg_free(struct tracee *dbg) { +// while (dbg->breaks.head != dbg->breaks.end) { +// struct breakpoint *b = dbg->breaks.head; +// list_remove(b); +// free(b); +// } +// clear_states(dbg); +// free(dbg->buff); +//} @@ -8,13 +8,19 @@ #define BREAKPOINT_INSN 0xcc +extern PANEL *consolepan; + struct breakpoint { LINKEDLIST; + unsigned long orig; unsigned long address; unsigned long stack; - unsigned long orig; + pid_t tid; int enabled; int active; + /* add count field + * and stop boolean + * to implement checkpoints? */ }; struct map { @@ -31,25 +37,77 @@ struct state { struct list maps; }; -struct tracee { - struct list breaks; +struct process { + pid_t id; + int child; + struct list breakpoints; + struct list threads; +}; + +struct thread { + LINKEDLIST; + struct process *proc; + struct list states; struct state *state; + int clearstates; + pid_t id; - int child; int stopped; - int status; int signal; int cont; - void *buff; - size_t buffsize; + const char *status; }; -extern int dbg_process(struct tracee *dbg, pid_t pid); -extern int dbg_new_process(struct tracee *dbg, char **argv, struct console *cons); -extern int dbg_wait(struct tracee *dbg); -extern int dbg_stepin(struct tracee *dbg); -extern int dbg_stepover(struct tracee *dbg); +extern void add_breakpoint(struct process *proc, unsigned long address, unsigned long stack, pid_t tid, int enabled); +extern int is_breakpoint(struct process *proc, unsigned long address); + +extern int dbg_process(struct process *proc, pid_t pid, int child); +extern int dbg_detach(struct process *proc); +extern int dbg_wait(struct thread *th, int dostops); +extern int dbg_cont(struct thread *th, int cont); +extern int dbg_stepin(struct thread *th); +extern int dbg_intr(struct thread *th); +extern void *deref(struct thread *th, unsigned long addr, size_t size); + + + + + + + + + +/* how to do an async 'all-cont': + * + * mark all threads for PTRACE_CONT + * singlestep all threads + * this is needed to step past any thread that may be starting at a breakpoint + * wait all single steps (note that some may hang...) + * once all single steps completed, install breakpoints and actually cont threads + */ + +//extern void add_breakpoint(struct process *proc, unsigned long address, unsigned long stack, pid_t tid, int enabled); +//extern int is_breakpoint(struct process *proc, unsigned long address); +// +//extern int dbg_process(struct process *proc, pid_t pid); +//extern int dbg_wait(struct thread *th, int dostops); +//extern int dbg_stepin(struct thread *th); +//extern int dbg_stepover(struct thread *th); +//extern int dbg_cont(struct thread *th, int cont); +//extern int dbg_intr(struct thread *th); +//extern int dbg_detach(struct process *proc); +//extern void *deref(struct thread *th, unsigned long addr, size_t size); + + + + +//extern int dbg_process(struct tracee *dbg, pid_t pid); +//extern int dbg_new_process(struct tracee *dbg, char **argv, struct console *cons); +//extern int dbg_wait(struct tracee *dbg, PANEL *pan); +//extern int dbg_stepin(struct tracee *dbg); +//extern int dbg_stepover(struct tracee *dbg); //extern int dbg_stepout(struct tracee *dbg); -extern int dbg_cont(struct tracee *dbg, int mode); -extern void *deref(struct tracee *dbg, unsigned long addr, size_t size); +//extern int dbg_cont(struct tracee *dbg, int mode); +//extern void *deref(struct tracee *dbg, unsigned long addr, size_t size); +//extern void dbg_free(struct tracee *dbg); @@ -20,6 +20,13 @@ void cursinit(void) { curs_set(FALSE); timeout(25); refresh(); + + start_color(); + use_default_colors(); + + init_pair(1, COLOR_GREEN, -1); + init_pair(2, COLOR_CYAN, -1); + init_pair(3, COLOR_RED, -1); } void cursupdate(void) { @@ -57,3 +64,11 @@ int pprintw(PANEL *pan, const char *fmt, ...) { int pclear(PANEL *pan) { return wclear(panel_window(pan)); } + +int pattron(PANEL *pan, int attrs) { + return wattron(panel_window(pan), attrs); +} + +int pattroff(PANEL *pan, int attrs) { + return wattroff(panel_window(pan), attrs); +} @@ -13,3 +13,5 @@ extern void delpan(PANEL *pan); extern void reset_panel(PANEL *pan, int h, int w, int y, int x); extern int pprintw(PANEL *pan, const char *fmt, ...); extern int pclear(PANEL *pan); +extern int pattron(PANEL *pan, int attrs); +extern int pattroff(PANEL *pan, int attrs); @@ -1,7 +1,9 @@ +#include <errno.h> #include <signal.h> #include <stdlib.h> #include <string.h> #include <sys/ptrace.h> +#include <sys/wait.h> #include <linux/ptrace.h> #include <unistd.h> @@ -15,6 +17,9 @@ static PANEL *left, *right; static struct console cons; static int mode = 0; +PANEL *consolepan; + +/* static void describe_status(struct tracee *_dbg, PANEL *pan) { struct tracee dbg = *_dbg; int status = dbg.status; @@ -85,9 +90,10 @@ static void describe_status(struct tracee *_dbg, PANEL *pan) { } } } +*/ -static void list_breakpoints(struct tracee *dbg, PANEL *pan) { - struct list *breaks = &dbg->breaks; +static void list_breakpoints(struct thread *dbg, PANEL *pan) { + struct list *breaks = &dbg->proc->breakpoints; if (breaks->head != breaks->end) { pprintw(pan, "---\n"); @@ -98,7 +104,7 @@ static void list_breakpoints(struct tracee *dbg, PANEL *pan) { } } -static void describe_states(struct tracee *dbg, PANEL *pan) { +static void describe_states(struct thread *dbg, PANEL *pan) { struct list *states = &dbg->states; for (struct state *s = states->head; s != states->end; s = s->next) { pprintw(pan, "%c", (s == dbg->state ? '#' : '-')); @@ -106,7 +112,7 @@ static void describe_states(struct tracee *dbg, PANEL *pan) { pprintw(pan, "\n"); } -static void dump_registers(struct tracee *dbg, PANEL *pan) { +static void dump_registers(struct thread *dbg, PANEL *pan) { struct user_regs_struct *regs = &dbg->state->regs; pprintw(pan, "rax = 0x%016llx\n", regs->rax); pprintw(pan, "rbx = 0x%016llx\n", regs->rbx); @@ -119,7 +125,7 @@ static void dump_registers(struct tracee *dbg, PANEL *pan) { pprintw(pan, "rip = 0x%016llx\n", regs->rip); } -static void dump_stack(struct tracee *dbg, PANEL *pan) { +static void dump_stack(struct thread *dbg, PANEL *pan) { unsigned long sp = dbg->state->regs.rsp; for (size_t i = 0; i < 16; i++, sp += 8) { unsigned long word = *(unsigned long *)deref(dbg, sp,sizeof(unsigned long)); @@ -127,7 +133,17 @@ static void dump_stack(struct tracee *dbg, PANEL *pan) { } } -static void disasm(struct tracee *dbg, PANEL *pan) { +static int rip_visited(struct thread *th, unsigned long rip) { + struct list *states = &th->states; + for (struct state *s = states->head; s != states->end; s = s->next) { + if (rip == s->regs.rip) { + return 1; + } + } + return 0; +} + +static void disasm(struct thread *dbg, PANEL *pan) { csh handle; cs_insn *insn; @@ -139,11 +155,29 @@ static void disasm(struct tracee *dbg, PANEL *pan) { const uint8_t *code = deref(dbg, address, codez); insn = cs_malloc(handle); - for (size_t i = 0; i < 16; i++) { + for (size_t i = 0; i < 32; i++) { if (!cs_disasm_iter(handle, &code, &codez, &address, insn)) { break; } + if (dbg->stopped) { + if (insn->address == dbg->state->regs.rip) { + pattron(pan, COLOR_PAIR(1)); + } else if (is_breakpoint(dbg->proc, insn->address)) { + pattron(pan, COLOR_PAIR(3)); + } else if (rip_visited(dbg, insn->address)) { + pattron(pan, COLOR_PAIR(2)); + } + } pprintw(pan, "0x%"PRIx64":\t%s %s\n", insn->address, insn->mnemonic, insn->op_str); + if (dbg->stopped) { + if (insn->address == dbg->state->regs.rip) { + pattroff(pan, COLOR_PAIR(1)); + } else if (is_breakpoint(dbg->proc, insn->address)) { + pattroff(pan, COLOR_PAIR(3)); + } else if (rip_visited(dbg, insn->address)) { + pattroff(pan, COLOR_PAIR(2)); + } + } } cs_free(insn, 1); @@ -151,13 +185,14 @@ static void disasm(struct tracee *dbg, PANEL *pan) { } } -static void info_update(struct tracee *dbg, PANEL *pan) { +static void info_update(struct thread *dbg, PANEL *pan) { pclear(pan); - pprintw(pan, "PID: %li\n", (long)dbg->id); - if (!dbg->stopped) { - pprintw(pan, "Process is running...\n"); - } else { - describe_status(dbg, pan); + pprintw(pan, "TID: %li\n", (long)dbg->id); + //if (!dbg->stopped) { + // pprintw(pan, "Thread is running...\n"); + //} else { + //describe_status(dbg, pan); + pprintw(pan, "%s (%i)\n", dbg->status, dbg->signal); list_breakpoints(dbg, pan); describe_states(dbg, pan); dump_registers(dbg, pan); @@ -165,52 +200,68 @@ static void info_update(struct tracee *dbg, PANEL *pan) { dump_stack(dbg, pan); pprintw(pan, "---\n"); disasm(dbg, pan); - } + //} } -static void layout(void) { +static void layout(struct process *proc, struct thread *th) { int w = COLS/2; - reset_panel(left, LINES-1, w, 0, 0); - reset_panel(right, LINES-1, COLS-w, 0, w); + reset_panel(left, LINES-2, w, 1, 0); + reset_panel(right, LINES-2, COLS-w, 1, w); + + clear(); + attron(COLOR_PAIR(1)); + struct list *threads = &proc->threads; + for (struct thread *t = threads->head; t != threads->end; t = t->next) { + if (t == th) { + printw("**"); + } + printw("%li (%s)", (long)t->id, t->status); + if (t == th) { + printw("**"); + } + printw(" "); + } + attroff(COLOR_PAIR(1)); + //refresh(); +} + +static void wait_all_threads(struct process *proc) { + struct list *threads = &proc->threads; + for (struct thread *th = threads->head; th != threads->end; th = th->next) { + dbg_wait(th, 1); + } } int main(int argc, char **argv) { - if (argc < 3) { - fprintf(stderr, "Usage: %s <stop> <command>\n", argv[0]); + if (argc < 2) { + fprintf(stderr, "Usage: %s <pid>\n", argv[0]); return 1; } + //getchar(); + cursinit(); left = newpan(0, 0, 0, 0); right = newpan(0, 0, 0, 0); - layout(); - console_init(&cons); + consolepan = right; - argv[argc] = NULL; - struct tracee dbg; - if (dbg_new_process(&dbg, argv+2, &cons)) { - pprintw(right, "Failed to start child\n"); - } + struct process proc; + struct thread *th; - unsigned long stop = strtoul(argv[1], NULL, 0); - if (stop != 0) { - struct breakpoint *b = xmalloc(sizeof(*b)); - b->address = stop; - b->stack = 0; - b->enabled = -1; - b->active = 0; - list_insert(dbg.breaks.end, b); - dbg_cont(&dbg, PTRACE_CONT); + pid_t pid = strtoul(argv[1], NULL, 0); + if (dbg_process(&proc, pid, 0)) { + pprintw(right, "failed to attach process\n"); } + th = proc.threads.head; + int quit = 0; while (!quit) { - //if (dbg.stopped == NULL) { - dbg_wait(&dbg); - //} + wait_all_threads(&proc); console_update(&cons, right); - info_update(&dbg, left); + info_update(th, left); + layout(&proc, th); cursupdate(); int ch = getch(); @@ -218,7 +269,7 @@ int main(int argc, char **argv) { if (mode == 0) { switch (ch) { case KEY_RESIZE: - layout(); + layout(&proc, th); break; case 'q': quit = 1; @@ -228,60 +279,92 @@ int main(int argc, char **argv) { console_enter(&cons, right); break; case 'j': - if (dbg.stopped) { - if (dbg.state != dbg.states.tail) { - dbg.state = dbg.state->next; + dbg_stepin(th); + /* + if (dbg->stopped) { + if (dbg->state != dbg->states.tail) { + dbg->state = dbg->state->next; } else { - dbg_stepover(&dbg); - } - } - break; - case 'k': - if (dbg.stopped) { - if (dbg.state != dbg.states.head) { - dbg.state = dbg.state->prev; - } - } - break; - case 'l': - if (dbg.stopped) { - if (dbg.state != dbg.states.tail) { - //dbg.state = dbg.state->next; - } else { - dbg_stepin(&dbg); + dbg_stepover(dbg); } } + */ break; + //case 'k': + // if (dbg->stopped) { + // if (dbg->state != dbg->states.head) { + // dbg->state = dbg->state->prev; + // } + // } + // break; + //case 'l': + // if (dbg->stopped) { + // if (dbg->state != dbg->states.tail) { + // //dbg.state = dbg.state->next; + // } else { + // dbg_stepin(dbg); + // } + // } + // break; //case 'h': // if (dbg.stopped) { // dbg_stepout(&dbg); // } // break; - case 'g': - if (dbg.stopped) { - dbg.state = dbg.states.head; - } - break; - case 'G': - if (dbg.stopped) { - dbg.state = dbg.states.tail; - } - break; - case 's': - if (dbg.stopped) { - dbg_cont(&dbg, PTRACE_SYSCALL); - } - break; + //case 'g': + // if (dbg->stopped) { + // dbg->state = dbg->states.head; + // } + // break; + //case 'G': + // if (dbg->stopped) { + // dbg->state = dbg->states.tail; + // } + // break; + //case 's': + // if (dbg->stopped) { + // dbg_cont(dbg, PTRACE_SYSCALL); + // } + // break; case 'c': - if (dbg.stopped) { - dbg_cont(&dbg, PTRACE_CONT); - } + dbg_cont(th, PTRACE_CONT); break; case 'p': - if (!dbg.stopped) { - tgkill(dbg.id, dbg.id, SIGSTOP); - } + dbg_intr(th); break; + case 't': + do { + th = th->next; + } while (th == proc.threads.end); + break; + case 'T': + do { + th = th->prev; + } while (th == proc.threads.end); + break; + //case 'd': + // if (dbg->stopped) { + // while (dbg->breaks.head != dbg->breaks.end) { + // struct breakpoint *b = dbg->breaks.head; + // list_remove(b); + // free(b); + // } + // } + // break; + //case 'D': + // if (dbg->stopped) { + // if (ptrace(PTRACE_DETACH, dbg->id, NULL, NULL) < 0) { + // pprintw(right, "PTRACE_DETACH: %s\n", strerror(errno)); + // } + // struct tracee *rm = dbg; + // do { + // dbg = dbg->next; + // } while (dbg == tracees.end); + // list_remove(rm); + // dbg_free(rm); + // free(rm); + // } + // break; case ':': mvprintw(LINES-1, 0, ":"); curs_set(TRUE); @@ -293,25 +376,22 @@ int main(int argc, char **argv) { noecho(); timeout(25); clear(); - refresh(); + //refresh(); char *t = cmd; - struct breakpoint *b = xmalloc(sizeof(*b)); - b->enabled = 1; - b->active = 0; + int en = 1; if (t[0] == '!') { - b->enabled = -1; + en = -1; t++; } - b->address = strtoul(t, NULL, 0); - b->stack = 0; - list_insert(dbg.breaks.end, b); + unsigned long addr = strtoul(t, NULL, 0); + add_breakpoint(&proc, addr, 0, 0, en); break; } } else { switch (ch) { case KEY_RESIZE: - layout(); + layout(&proc, th); break; case 0x1b: mode = 0; @@ -326,6 +406,7 @@ int main(int argc, char **argv) { } } + dbg_detach(&proc); endwin(); return 0; } |