From d070fde6478431c71fb4a55e783a577439c7cb99 Mon Sep 17 00:00:00 2001 From: Malfurious Date: Thu, 20 Jul 2023 14:55:13 -0400 Subject: Multithread version 1 Signed-off-by: Malfurious --- debugger.c | 744 ++++++++++++++++++++++++++++++++++++++++++++++--------------- debugger.h | 86 +++++-- helpers.c | 15 ++ helpers.h | 2 + misplays.c | 261 ++++++++++++++-------- 5 files changed, 825 insertions(+), 283 deletions(-) diff --git a/debugger.c b/debugger.c index 19d9edc..5fc3978 100644 --- a/debugger.c +++ b/debugger.c @@ -1,5 +1,8 @@ +#include +#include #include #include +#include #include #include #include @@ -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); +//} diff --git a/debugger.h b/debugger.h index 6305e96..cf2c442 100644 --- a/debugger.h +++ b/debugger.h @@ -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); diff --git a/helpers.c b/helpers.c index f7cc9c2..8e3be13 100644 --- a/helpers.c +++ b/helpers.c @@ -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); +} diff --git a/helpers.h b/helpers.h index f01e024..941c9b0 100644 --- a/helpers.h +++ b/helpers.h @@ -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); diff --git a/misplays.c b/misplays.c index 8f6e0ee..a1f11a4 100644 --- a/misplays.c +++ b/misplays.c @@ -1,7 +1,9 @@ +#include #include #include #include #include +#include #include #include @@ -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 \n", argv[0]); + if (argc < 2) { + fprintf(stderr, "Usage: %s \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; } -- cgit v1.2.3 From b4abda51217101ceffd19c3d403e40781e15dcec Mon Sep 17 00:00:00 2001 From: Malfurious Date: Tue, 19 Sep 2023 11:02:03 -0400 Subject: Multithread version 2 Signed-off-by: Malfurious --- debugger.c | 468 +++++++++++++++++++++++++------------------------------------ debugger.h | 75 +++------- helpers.h | 2 + misplays.c | 372 ++++++++++++++++++++++-------------------------- 4 files changed, 379 insertions(+), 538 deletions(-) diff --git a/debugger.c b/debugger.c index 5fc3978..956b065 100644 --- a/debugger.c +++ b/debugger.c @@ -1,39 +1,41 @@ #include -#include -#include +#include #include -#include #include #include #include #include -/* #include "config.h" */ #include "debugger.h" #include "helpers.h" -static const int PTRACE_OPTIONS = PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACECLONE; -static const int INTERRUPT_ON_SEIZE = 1; +#define BREAKPOINT_INSN 0xcc + +//struct list global_processes = {0}; +//struct thread *global_thread = NULL; + +static const int PTRACE_OPTIONS = + PTRACE_O_TRACECLONE | + PTRACE_O_TRACEEXIT | + PTRACE_O_TRACESYSGOOD; static int detect_breakpoint(struct thread *th) { - struct user_regs_struct regs; int check = 0; int restart = 0; + struct user_regs_struct regs; 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 (b->installed) { if (!check) { if (regs.rip - 1 == b->address) { regs.rip--; ptrace(PTRACE_SETREGS, th->id, NULL, ®s); check = 1; + b->hits++; if (b->stack != 0 && b->stack != regs.rsp) { restart = 1; @@ -42,9 +44,15 @@ static int detect_breakpoint(struct thread *th) { if (b->tid != 0 && b->tid != th->id) { restart = 1; } + + if (!b->enabled) { + restart = 1; + } } } + /* this needs moved, we have to finish the for-loop + * to actually know if restart is true or false... */ if (b->enabled < 0 && !restart) { struct breakpoint *del = b; b = b->prev; @@ -57,17 +65,17 @@ static int detect_breakpoint(struct thread *th) { return restart; } -static void install_breakpoints(struct thread *th) { - struct list *breaks = &th->proc->breakpoints; +static void install_breakpoints(struct process *proc) { + struct list *breaks = &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; + if (!b->installed) { + unsigned long word; + word = ptrace(PTRACE_PEEKTEXT, proc->id, b->address, NULL); + b->text = word; + + word = (word & ~0xff) | BREAKPOINT_INSN; + ptrace(PTRACE_POKETEXT, proc->id, b->address, word); + b->installed = 1; } } } @@ -75,14 +83,14 @@ static void install_breakpoints(struct thread *th) { 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; + if (b->installed) { + ptrace(PTRACE_POKETEXT, proc->id, b->address, b->text); + b->installed = 0; } } } -static void clear_breakpoints(struct process *proc) { +static void free_breakpoints(struct process *proc) { while (proc->breakpoints.head != proc->breakpoints.end) { struct breakpoint *b = proc->breakpoints.head; list_remove(b); @@ -90,7 +98,7 @@ static void clear_breakpoints(struct process *proc) { } } -static void clear_states(struct thread *th) { +static void free_states(struct thread *th) { while (th->states.head != th->states.end) { struct state *s = th->states.head; @@ -109,9 +117,9 @@ static void clear_states(struct thread *th) { th->clearstates = 0; } -static void capture_state(struct thread *th) { +static void capture_state_thread(struct thread *th) { if (th->clearstates) { - clear_states(th); + free_states(th); } struct state *s = xmalloc(sizeof(*s)); @@ -148,13 +156,33 @@ static void capture_state(struct thread *th) { } } -static struct thread *new_thread(struct process *proc, pid_t id) { +static void capture_state(struct thread *th, int all) { + if (all) { + struct list *threads = &th->proc->threads; + for (struct thread *t = threads->head; t != threads->end; t = t->next) { + capture_state_thread(t); + } + } else { + capture_state_thread(th); + } +} + +static struct process *new_process(pid_t pid, int child) { + struct process *proc = xmalloc(sizeof(*proc)); + proc->id = pid; + proc->child = child; + list_init(&proc->threads); + list_init(&proc->breakpoints); + return proc; +} + +static struct thread *new_thread(struct process *proc, pid_t tid) { struct thread *th = xmalloc(sizeof(*th)); th->proc = proc; list_init(&th->states); th->state = NULL; th->clearstates = 0; - th->id = id; + th->id = tid; th->stopped = 0; th->signal = 0; th->cont = 0; @@ -162,24 +190,28 @@ static struct thread *new_thread(struct process *proc, pid_t id) { return th; } -static void interrupt_all_threads(struct process *proc) { +static int interrupt_all_threads(struct process *proc) { + int stopped = 0; 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)) {} + while (!dbg_wait(th, 1)) {} + stopped = 1; } } + return stopped; } 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->text = 0; + b->installed = 0; + b->hits = 0; b->stack = stack; b->tid = tid; b->enabled = enabled; - b->active = 0; list_insert(proc->breakpoints.end, b); } @@ -193,18 +225,27 @@ int is_breakpoint(struct process *proc, unsigned long address) { return 0; } -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); +//int dbg_attach(pid_t pid, int child) { +struct process *dbg_attach(pid_t pid, int child) { + //if (global_processes.end == NULL) { + // list_init(&global_processes); + //} + + struct process *proc = new_process(pid, child); + //list_insert(global_processes.end, proc); + + int options = PTRACE_OPTIONS; + if (child) { + options |= PTRACE_O_EXITKILL; + } char taskpath[32]; snprintf(taskpath, sizeof(taskpath), "/proc/%li/task", (long)pid); DIR *taskdir = opendir(taskpath); if (!taskdir) { - return -1; + dbg_detach(proc); + return NULL; } struct dirent *task; @@ -212,9 +253,10 @@ int dbg_process(struct process *proc, pid_t pid, int child) { pid_t id = strtoul(task->d_name, NULL, 0); if (id != 0) { - if (ptrace(PTRACE_SEIZE, id, NULL, PTRACE_OPTIONS) < 0) { + if (ptrace(PTRACE_SEIZE, id, NULL, options) < 0) { closedir(taskdir); - return -1; + dbg_detach(proc); + return NULL; } struct thread *th = new_thread(proc, id); @@ -222,37 +264,44 @@ int dbg_process(struct process *proc, pid_t pid, int child) { } } - if (INTERRUPT_ON_SEIZE) { - interrupt_all_threads(proc); - } - closedir(taskdir); - return 0; + + //global_thread = proc->threads.head; + interrupt_all_threads(proc); + return proc; } int dbg_detach(struct process *proc) { interrupt_all_threads(proc); uninstall_breakpoints(proc); - clear_breakpoints(proc); + free_breakpoints(proc); + + if (proc->child) { + kill(proc->id, SIGKILL); + } 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); + free_states(th); list_remove(th); free(th); } + list_remove(proc); + free(proc); return 0; } -int dbg_wait(struct thread *th, int dostops) { +int dbg_wait(struct thread *th, int recursion) { if (th->id < 0) { return -1; } + /* todo: check what happens if we call on an already known + * stopped thread? */ int status; if (waitpid(th->id, &status, __WALL | WNOHANG) <= 0) { return 0; @@ -276,42 +325,45 @@ int dbg_wait(struct thread *th, int dostops) { return 1; } - unsigned long eventmsg; + int stopped; struct thread *newth; + + unsigned long eventmsg; ptrace(PTRACE_GETEVENTMSG, th->id, NULL, &eventmsg); if (WIFSTOPPED(status)) { switch (status >> 8) { + /* todo: other ptrace event stops */ case SIGTRAP | (PTRACE_EVENT_CLONE << 8): newth = new_thread(th->proc, eventmsg); list_insert(th->proc->threads.end, newth); - while (!dbg_wait(newth, 0)) {} + while (!dbg_wait(newth, 1)) {} th->stopped = 1; th->signal = 0; th->cont = 0; th->status = "CLONE EVENT"; - if (dostops) { - interrupt_all_threads(th->proc); + if (!recursion) { + stopped = interrupt_all_threads(th->proc); uninstall_breakpoints(th->proc); + capture_state(th, stopped); } - capture_state(th); return 1; case SIGTRAP | (PTRACE_EVENT_EXIT << 8): th->stopped = 1; - th->signal = 0; //eventmsg; + th->signal = 0; /* eventmsg has exit code, but would inject sig */ th->cont = 0; th->status = "EXIT EVENT"; - if (dostops) { - interrupt_all_threads(th->proc); + if (!recursion) { + stopped = interrupt_all_threads(th->proc); uninstall_breakpoints(th->proc); + capture_state(th, stopped); } - capture_state(th); return 1; case SIGTRAP | (PTRACE_EVENT_STOP << 8): @@ -320,12 +372,12 @@ int dbg_wait(struct thread *th, int dostops) { th->cont = 0; th->status = "STOP EVENT"; - if (dostops) { - interrupt_all_threads(th->proc); + if (!recursion) { + stopped = interrupt_all_threads(th->proc); uninstall_breakpoints(th->proc); + capture_state(th, stopped); } - capture_state(th); return 1; case SIGTRAP | 0x80: @@ -334,20 +386,23 @@ int dbg_wait(struct thread *th, int dostops) { th->cont = 0; th->status = "SYSCALL EVENT"; - if (dostops) { - interrupt_all_threads(th->proc); + if (!recursion) { + stopped = interrupt_all_threads(th->proc); uninstall_breakpoints(th->proc); + capture_state(th, stopped); } - capture_state(th); return 1; case SIGTRAP: th->stopped = 1; th->signal = 0; + th->status = "STEP/BREAKPOINT"; if (th->cont != 0) { - install_breakpoints(th); + /* gdb this portion. are there race conditions + * that matter?? */ + install_breakpoints(th->proc); ptrace(th->cont, th->id, NULL, NULL); th->cont = 0; th->stopped = 0; @@ -355,18 +410,23 @@ int dbg_wait(struct thread *th, int dostops) { return 0; } - if (dostops) { - interrupt_all_threads(th->proc); + /* todo: Test two threads hitting a breakpoint at + * the same time. */ + int restart = detect_breakpoint(th); + + if (!recursion) { + stopped = interrupt_all_threads(th->proc); uninstall_breakpoints(th->proc); + if (!restart) { + capture_state(th, stopped); + } } - if (detect_breakpoint(th)) { + if (restart) { dbg_cont(th, PTRACE_CONT); return 0; } - th->status = "STEP/BREAKPOINT"; - capture_state(th); return 1; default: @@ -375,12 +435,12 @@ int dbg_wait(struct thread *th, int dostops) { th->cont = 0; th->status = "SIGNAL DELIVERY"; - if (dostops) { - interrupt_all_threads(th->proc); + if (!recursion) { + stopped = interrupt_all_threads(th->proc); uninstall_breakpoints(th->proc); + capture_state(th, stopped); } - capture_state(th); return 1; } } @@ -388,6 +448,15 @@ int dbg_wait(struct thread *th, int dostops) { return -1; } +int dbg_intr(struct thread *th) { + if (th->id < 0 || th->stopped) { + return -1; + } + + ptrace(PTRACE_INTERRUPT, th->id, NULL, NULL); + return 0; +} + int dbg_cont(struct thread *th, int cont) { if (th->id < 0 || !th->stopped) { return -1; @@ -404,51 +473,81 @@ int dbg_cont(struct thread *th, int cont) { t->clearstates = 1; } - /* 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_stepin(struct thread *th) { - if (th->id < 0 || !th->stopped) { +int dbg_step(struct thread *th, int stepover) { + // todo: support step-out + //if (th->id < 0 || !th->stopped) { + // return -1; + //} + + if (!th->stopped) { return -1; } - if (ptrace(PTRACE_SINGLESTEP, th->id, NULL, th->signal) < 0) { + if (th->state != th->states.tail) { + th->state = th->state->next; + return 0; + } + + if (th->id < 0) { return -1; } + csh cshandle; + cs_open(CS_ARCH_X86, CS_MODE_64, &cshandle); + cs_insn *insn = cs_malloc(cshandle); + + uint64_t address = th->state->regs.rip; + size_t size = 128; + const uint8_t *code = deref(th, address, size); + + cs_disasm_iter(cshandle, &code, &size, &address, insn); + + if (insn->id == X86_INS_CALL && stepover) { + add_breakpoint(th->proc, address, th->state->regs.rsp, th->id, -1); + th->cont = PTRACE_CONT; + } else { + th->cont = 0; + } + + ptrace(PTRACE_SINGLESTEP, th->id, NULL, th->signal); + th->stopped = 0; th->signal = 0; - th->cont = 0; th->status = "RUNNING"; - th->clearstates = 0; + + cs_free(insn, 1); + cs_close(&cshandle); return 0; } -int dbg_intr(struct thread *th) { - if (th->id < 0 || th->stopped) { +int dbg_pets(struct thread *th) { + if (!th->stopped) { return -1; } - ptrace(PTRACE_INTERRUPT, th->id, NULL, NULL); - return 0; + if (th->state != th->states.head) { + th->state = th->state->prev; + return 0; + } + + return -1; } -void *deref(struct thread *th, unsigned long addr, size_t size) { +void *deref(struct thread *th, unsigned long address, size_t size) { + /* todo: size, and mapping breaks */ (void)size; + if (!th->state) { return NULL; } 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); + if (m->start <= address && address < m->end) { + return (unsigned char *)m->data + (address - m->start); } } @@ -459,164 +558,11 @@ void *deref(struct thread *th, unsigned long addr, size_t size) { - - - - - - - - - - -///* -//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)); @@ -634,35 +580,3 @@ void *deref(struct thread *th, unsigned long addr, size_t size) { // 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); -//} diff --git a/debugger.h b/debugger.h index cf2c442..845bf69 100644 --- a/debugger.h +++ b/debugger.h @@ -3,24 +3,19 @@ #include #include -#include "console.h" #include "list.h" -#define BREAKPOINT_INSN 0xcc - -extern PANEL *consolepan; - struct breakpoint { LINKEDLIST; - unsigned long orig; + unsigned long address; + unsigned long text; + int installed; + int hits; + unsigned long stack; pid_t tid; int enabled; - int active; - /* add count field - * and stop boolean - * to implement checkpoints? */ }; struct map { @@ -38,10 +33,11 @@ struct state { }; struct process { + LINKEDLIST; pid_t id; int child; - struct list breakpoints; struct list threads; + struct list breakpoints; }; struct thread { @@ -56,58 +52,23 @@ struct thread { int stopped; int signal; int cont; + const char *status; }; +//extern struct list global_processes; +//extern struct thread *global_thread; + 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 struct process *dbg_attach(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_wait(struct thread *th, int recursion); +extern int dbg_intr(struct thread *th); +extern int dbg_cont(struct thread *th, int cont); +extern int dbg_step(struct thread *th, int stepover); +extern int dbg_pets(struct thread *th); -//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 void dbg_free(struct tracee *dbg); +extern void *deref(struct thread *th, unsigned long address, size_t size); diff --git a/helpers.h b/helpers.h index 941c9b0..02607bb 100644 --- a/helpers.h +++ b/helpers.h @@ -4,6 +4,8 @@ #include #include +#define KEY_ESCAPE 0x1b + extern void *xmalloc(size_t size); extern void cursinit(void); diff --git a/misplays.c b/misplays.c index a1f11a4..4fec040 100644 --- a/misplays.c +++ b/misplays.c @@ -1,10 +1,7 @@ -#include #include +#include #include -#include #include -#include -#include #include #include @@ -12,85 +9,33 @@ #include "console.h" #include "debugger.h" #include "helpers.h" +#include "list.h" 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; - struct ptrace_syscall_info info; - - if (WIFEXITED(status)) { - pprintw(pan, "exited with code: %u\n", WEXITSTATUS(status)); - } else if (WIFSIGNALED(status)) { - pprintw(pan, "terminated by signal: %s\n", strsignal(WTERMSIG(status))); - } else { - unsigned long msg; - if (ptrace(PTRACE_GETEVENTMSG, dbg.id, NULL, &msg) < 0) { - perror("PTRACE_GETEVENTMSG"); - } - - if (WIFSTOPPED(status)) { - switch (status >> 8) { - case ((PTRACE_EVENT_VFORK << 8) | SIGTRAP): - pprintw(pan, "child vfork()'d to: %lu\n", msg); - break; - case ((PTRACE_EVENT_FORK << 8) | SIGTRAP): - pprintw(pan, "child fork()'d to: %lu\n", msg); - break; - case ((PTRACE_EVENT_CLONE << 8) | SIGTRAP): - pprintw(pan, "child clone()'d to: %lu\n", msg); - break; - case ((PTRACE_EVENT_VFORK_DONE << 8) | SIGTRAP): - pprintw(pan, "finished vfork\n"); - break; - case ((PTRACE_EVENT_EXEC << 8) | SIGTRAP): - pprintw(pan, "child exec()'d from: %lu\n", msg); - break; - case ((PTRACE_EVENT_EXIT << 8) | SIGTRAP): - pprintw(pan, "exiting with code: %lu\n", msg); - break; - case ((PTRACE_EVENT_STOP << 8) | SIGTRAP): - pprintw(pan, "child stopped\n"); - break; - case ((PTRACE_EVENT_SECCOMP << 8) | SIGTRAP): - pprintw(pan, "child seccomp event\n"); - break; - - case (SIGTRAP | 0x80): - ptrace(PTRACE_GET_SYSCALL_INFO, dbg.id, sizeof(info), &info); - pprintw(pan, "child entering syscall: %llu (%llx, %llx, %llx, %llx, %llx, %llx)\n", - info.entry.nr, - info.entry.args[0], - info.entry.args[1], - info.entry.args[2], - info.entry.args[3], - info.entry.args[4], - info.entry.args[5]); - break; +static pid_t parse_pid(const char *nptr) { + char *endptr; + pid_t pid = strtoul(nptr, &endptr, 0); + return (*endptr ? 0 : pid); +} - case SIGSTOP: - case SIGTSTP: - case SIGTTIN: - case SIGTTOU: - pprintw(pan, "child group-stopped\n"); - break; +static pid_t dofork(char **argv, struct console *cons) { + pid_t pid = fork(); + if (pid < 0) { + return -1; + } - default: - pprintw(pan, "received signal: %s\n", strsignal(WSTOPSIG(status))); - break; - } - } else { - pprintw(pan, "child stop event unrecognized\n"); - } + if (pid == 0) { + console_configslave(cons); + close_range(STDERR_FILENO+1, ~0U, CLOSE_RANGE_UNSHARE); + setpgid(0, 0); + raise(SIGSTOP); // ptrace(PTRACE_TRACEME, 0, NULL, NULL); + execvp(argv[0], argv); + exit(EXIT_FAILURE); } + + return pid; } -*/ static void list_breakpoints(struct thread *dbg, PANEL *pan) { struct list *breaks = &dbg->proc->breakpoints; @@ -148,7 +93,7 @@ static void disasm(struct thread *dbg, PANEL *pan) { cs_insn *insn; if (cs_open(CS_ARCH_X86, CS_MODE_64, &handle) != CS_ERR_OK) { - perror("capstone open"); + //perror("capstone open"); } else { uint64_t address = dbg->state->regs.rip; size_t codez = 128; @@ -185,83 +130,97 @@ static void disasm(struct thread *dbg, PANEL *pan) { } } -static void info_update(struct thread *dbg, PANEL *pan) { +static void info_update(struct thread *th, PANEL *pan) { pclear(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); - pprintw(pan, "---\n"); - dump_stack(dbg, pan); - pprintw(pan, "---\n"); - disasm(dbg, pan); - //} + pprintw(pan, "TID: %li\n", (long)th->id); + pprintw(pan, "%s (%i)\n", th->status, th->signal); + list_breakpoints(th, pan); + describe_states(th, pan); + dump_registers(th, pan); + pprintw(pan, "---\n"); + dump_stack(th, pan); + pprintw(pan, "---\n"); + disasm(th, pan); +} + +static void wait_all_threads(struct list *processes) { + for (struct process *proc = processes->head; proc != processes->end; proc = proc->next) { + struct list *threads = &proc->threads; + for (struct thread *th = threads->head; th != threads->end; th = th->next) { + dbg_wait(th, 0); + } + } } -static void layout(struct process *proc, struct thread *th) { +static void layout(struct list *processes, struct thread *th) { int w = COLS/2; 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("**"); + + for (struct process *proc = processes->head; proc != processes->end; proc = proc->next) { + 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(" "); } - 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); - } + attroff(COLOR_PAIR(1)); } int main(int argc, char **argv) { if (argc < 2) { - fprintf(stderr, "Usage: %s \n", argv[0]); - return 1; + fprintf(stderr, "Usage: %s \n", argv[0]); + return EXIT_FAILURE; } - //getchar(); + getchar(); - cursinit(); - left = newpan(0, 0, 0, 0); - right = newpan(0, 0, 0, 0); + struct console cons = {0}; console_init(&cons); - consolepan = right; - struct process proc; - struct thread *th; + struct list processes = {0}; + struct thread *th = NULL; + list_init(&processes); - pid_t pid = strtoul(argv[1], NULL, 0); - if (dbg_process(&proc, pid, 0)) { - pprintw(right, "failed to attach process\n"); + int child = 0; + pid_t pid = parse_pid(argv[1]); + argv[argc] = NULL; + + if (pid == 0) { + pid = dofork(argv+1, &cons); + child = 1; + } + + struct process *proc = dbg_attach(pid, child); + if (!proc) { + fprintf(stderr, "Failed to attach to process %li\n", (long)pid); + return EXIT_FAILURE; } + list_insert(processes.end, proc); + th = proc->threads.head; - th = proc.threads.head; + cursinit(); + left = newpan(0, 0, 0, 0); + right = newpan(0, 0, 0, 0); + layout(&processes, th); int quit = 0; + int mode = 0; while (!quit) { - wait_all_threads(&proc); + wait_all_threads(&processes); + layout(&processes, th); console_update(&cons, right); info_update(th, left); - layout(&proc, th); cursupdate(); int ch = getch(); @@ -269,7 +228,7 @@ int main(int argc, char **argv) { if (mode == 0) { switch (ch) { case KEY_RESIZE: - layout(&proc, th); + layout(&processes, th); break; case 'q': quit = 1; @@ -279,53 +238,30 @@ int main(int argc, char **argv) { console_enter(&cons, right); break; case 'j': - dbg_stepin(th); - /* - if (dbg->stopped) { - if (dbg->state != dbg->states.tail) { - dbg->state = dbg->state->next; - } else { - dbg_stepover(dbg); - } + dbg_step(th, 1); + break; + case 'k': + dbg_pets(th); + break; + case 'l': + dbg_step(th, 0); + break; + case 'h': + /* todo: step out */ + break; + case 'g': + if (th->stopped) { + th->state = th->states.head; + } + break; + case 'G': + if (th->stopped) { + th->state = th->states.tail; } - */ 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 's': + dbg_cont(th, PTRACE_SYSCALL); + break; case 'c': dbg_cont(th, PTRACE_CONT); break; @@ -333,38 +269,19 @@ int main(int argc, char **argv) { dbg_intr(th); break; case 't': + proc = th->proc; do { th = th->next; - } while (th == proc.threads.end); + } while (th == proc->threads.end); + layout(&processes, th); break; case 'T': + proc = th->proc; 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; + } while (th == proc->threads.end); + layout(&processes, th); + break; /* todo: next/prev process bindings */ case ':': mvprintw(LINES-1, 0, ":"); curs_set(TRUE); @@ -376,24 +293,27 @@ int main(int argc, char **argv) { noecho(); timeout(25); clear(); - //refresh(); + layout(&processes, th); char *t = cmd; int en = 1; if (t[0] == '!') { en = -1; t++; + } else if (t[0] == '#') { + en = 0; + t++; } - unsigned long addr = strtoul(t, NULL, 0); - add_breakpoint(&proc, addr, 0, 0, en); + unsigned long address = strtoul(t, NULL, 0); + add_breakpoint(th->proc, address, 0, 0, en); break; } } else { switch (ch) { case KEY_RESIZE: - layout(&proc, th); + layout(&processes, th); break; - case 0x1b: + case KEY_ESCAPE: mode = 0; console_leave(&cons, right); break; @@ -406,7 +326,51 @@ int main(int argc, char **argv) { } } - dbg_detach(&proc); + dbg_detach(th->proc); /* todo: detach all procs */ endwin(); - return 0; + return EXIT_SUCCESS; } + + + + + + + + + + + + + + +//#include +//#include +//#include +//#include +//#include +//#include + +// //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; -- cgit v1.2.3 From c5211aca9ea40487dc4299423f735da04630fcc6 Mon Sep 17 00:00:00 2001 From: Malfurious Date: Mon, 25 Sep 2023 00:39:44 -0400 Subject: Fix capture_state edge cases Use `th->state == NULL` as an indicator that each thread's state is capturable, discard use of the `all` parameter. Signed-off-by: Malfurious --- debugger.c | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/debugger.c b/debugger.c index 956b065..1ad88c8 100644 --- a/debugger.c +++ b/debugger.c @@ -157,14 +157,21 @@ static void capture_state_thread(struct thread *th) { } static void capture_state(struct thread *th, int all) { - if (all) { - struct list *threads = &th->proc->threads; - for (struct thread *t = threads->head; t != threads->end; t = t->next) { + (void)all; + struct list *threads = &th->proc->threads; + for (struct thread *t = threads->head; t != threads->end; t = t->next) { + if (!t->state) { capture_state_thread(t); } - } else { - capture_state_thread(th); } + //if (all) { + // struct list *threads = &th->proc->threads; + // for (struct thread *t = threads->head; t != threads->end; t = t->next) { + // capture_state_thread(t); + // } + //} else { + // capture_state_thread(th); + //} } static struct process *new_process(pid_t pid, int child) { @@ -268,6 +275,7 @@ struct process *dbg_attach(pid_t pid, int child) { //global_thread = proc->threads.head; interrupt_all_threads(proc); + capture_state(proc->threads.head, 0); return proc; } @@ -343,6 +351,7 @@ int dbg_wait(struct thread *th, int recursion) { th->signal = 0; th->cont = 0; th->status = "CLONE EVENT"; + th->state = NULL; if (!recursion) { stopped = interrupt_all_threads(th->proc); @@ -357,6 +366,7 @@ int dbg_wait(struct thread *th, int recursion) { th->signal = 0; /* eventmsg has exit code, but would inject sig */ th->cont = 0; th->status = "EXIT EVENT"; + th->state = NULL; if (!recursion) { stopped = interrupt_all_threads(th->proc); @@ -371,6 +381,7 @@ int dbg_wait(struct thread *th, int recursion) { th->signal = 0; th->cont = 0; th->status = "STOP EVENT"; + th->state = NULL; if (!recursion) { stopped = interrupt_all_threads(th->proc); @@ -385,6 +396,7 @@ int dbg_wait(struct thread *th, int recursion) { th->signal = 0; th->cont = 0; th->status = "SYSCALL EVENT"; + th->state = NULL; if (!recursion) { stopped = interrupt_all_threads(th->proc); @@ -413,6 +425,9 @@ int dbg_wait(struct thread *th, int recursion) { /* todo: Test two threads hitting a breakpoint at * the same time. */ int restart = detect_breakpoint(th); + if (!restart) { + th->state = NULL; + } if (!recursion) { stopped = interrupt_all_threads(th->proc); @@ -434,6 +449,7 @@ int dbg_wait(struct thread *th, int recursion) { th->signal = WSTOPSIG(status); th->cont = 0; th->status = "SIGNAL DELIVERY"; + th->state = NULL; if (!recursion) { stopped = interrupt_all_threads(th->proc); -- cgit v1.2.3 From 6a9e590b847a034d190fe3c89ef37656073d229a Mon Sep 17 00:00:00 2001 From: Malfurious Date: Mon, 25 Sep 2023 14:46:34 -0400 Subject: Don't drop temporary breakpoints when execution is to be restarted Signed-off-by: Malfurious --- debugger.c | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/debugger.c b/debugger.c index 1ad88c8..73db856 100644 --- a/debugger.c +++ b/debugger.c @@ -51,14 +51,14 @@ static int detect_breakpoint(struct thread *th) { } } - /* this needs moved, we have to finish the for-loop - * to actually know if restart is true or false... */ - if (b->enabled < 0 && !restart) { - struct breakpoint *del = b; - b = b->prev; - list_remove(del); - free(del); - } + ///* this needs moved, we have to finish the for-loop + // * to actually know if restart is true or false... */ + //if (b->enabled < 0 && !restart) { + // struct breakpoint *del = b; + // b = b->prev; + // list_remove(del); + // free(del); + //} } } @@ -87,6 +87,13 @@ static void uninstall_breakpoints(struct process *proc) { ptrace(PTRACE_POKETEXT, proc->id, b->address, b->text); b->installed = 0; } + + if (b->enabled < 0) { + struct breakpoint *del = b; + b = b->next; + list_remove(del); + free(del); + } } } @@ -431,8 +438,8 @@ int dbg_wait(struct thread *th, int recursion) { if (!recursion) { stopped = interrupt_all_threads(th->proc); - uninstall_breakpoints(th->proc); if (!restart) { + uninstall_breakpoints(th->proc); capture_state(th, stopped); } } -- cgit v1.2.3 From 566d752918789b178a47393a78b41c90288e40e9 Mon Sep 17 00:00:00 2001 From: Malfurious Date: Mon, 25 Sep 2023 14:49:19 -0400 Subject: (Un)install breakpoints via specific thread We need to perform these changes with a thread ID that is known to be in ptrace stop. This is a requirement of the API even though the memory change is seen by all threads of the guest process. Signed-off-by: Malfurious --- debugger.c | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/debugger.c b/debugger.c index 73db856..ca774e8 100644 --- a/debugger.c +++ b/debugger.c @@ -65,26 +65,26 @@ static int detect_breakpoint(struct thread *th) { return restart; } -static void install_breakpoints(struct process *proc) { - struct list *breaks = &proc->breakpoints; +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->installed) { unsigned long word; - word = ptrace(PTRACE_PEEKTEXT, proc->id, b->address, NULL); + word = ptrace(PTRACE_PEEKTEXT, th->id, b->address, NULL); b->text = word; word = (word & ~0xff) | BREAKPOINT_INSN; - ptrace(PTRACE_POKETEXT, proc->id, b->address, word); + ptrace(PTRACE_POKETEXT, th->id, b->address, word); b->installed = 1; } } } -static void uninstall_breakpoints(struct process *proc) { - struct list *breaks = &proc->breakpoints; +static void uninstall_breakpoints(struct thread *th) { + struct list *breaks = &th->proc->breakpoints; for (struct breakpoint *b = breaks->tail; b != breaks->end; b = b->prev) { if (b->installed) { - ptrace(PTRACE_POKETEXT, proc->id, b->address, b->text); + ptrace(PTRACE_POKETEXT, th->id, b->address, b->text); b->installed = 0; } @@ -288,7 +288,7 @@ struct process *dbg_attach(pid_t pid, int child) { int dbg_detach(struct process *proc) { interrupt_all_threads(proc); - uninstall_breakpoints(proc); + uninstall_breakpoints(proc->threads.head); free_breakpoints(proc); if (proc->child) { @@ -362,7 +362,7 @@ int dbg_wait(struct thread *th, int recursion) { if (!recursion) { stopped = interrupt_all_threads(th->proc); - uninstall_breakpoints(th->proc); + uninstall_breakpoints(th); capture_state(th, stopped); } @@ -377,7 +377,7 @@ int dbg_wait(struct thread *th, int recursion) { if (!recursion) { stopped = interrupt_all_threads(th->proc); - uninstall_breakpoints(th->proc); + uninstall_breakpoints(th); capture_state(th, stopped); } @@ -392,7 +392,7 @@ int dbg_wait(struct thread *th, int recursion) { if (!recursion) { stopped = interrupt_all_threads(th->proc); - uninstall_breakpoints(th->proc); + uninstall_breakpoints(th); capture_state(th, stopped); } @@ -407,7 +407,7 @@ int dbg_wait(struct thread *th, int recursion) { if (!recursion) { stopped = interrupt_all_threads(th->proc); - uninstall_breakpoints(th->proc); + uninstall_breakpoints(th); capture_state(th, stopped); } @@ -421,7 +421,7 @@ int dbg_wait(struct thread *th, int recursion) { if (th->cont != 0) { /* gdb this portion. are there race conditions * that matter?? */ - install_breakpoints(th->proc); + install_breakpoints(th); ptrace(th->cont, th->id, NULL, NULL); th->cont = 0; th->stopped = 0; @@ -439,7 +439,7 @@ int dbg_wait(struct thread *th, int recursion) { if (!recursion) { stopped = interrupt_all_threads(th->proc); if (!restart) { - uninstall_breakpoints(th->proc); + uninstall_breakpoints(th); capture_state(th, stopped); } } @@ -460,7 +460,7 @@ int dbg_wait(struct thread *th, int recursion) { if (!recursion) { stopped = interrupt_all_threads(th->proc); - uninstall_breakpoints(th->proc); + uninstall_breakpoints(th); capture_state(th, stopped); } -- cgit v1.2.3 From 5e20ea14800abaa62b15fcf2bb8462e5661ffdc7 Mon Sep 17 00:00:00 2001 From: Malfurious Date: Mon, 25 Sep 2023 14:50:50 -0400 Subject: Display installed status of breakpoints Signed-off-by: Malfurious --- misplays.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misplays.c b/misplays.c index 4fec040..0910012 100644 --- a/misplays.c +++ b/misplays.c @@ -43,7 +43,7 @@ static void list_breakpoints(struct thread *dbg, PANEL *pan) { pprintw(pan, "---\n"); for (struct breakpoint *bp=breaks->head; bp!=breaks->end; bp=bp->next) { - pprintw(pan, "0x%lx ", bp->address); + pprintw(pan, "0x%lx (%c) ", bp->address, (bp->installed ? '*' : ' ')); } pprintw(pan, "\n"); } -- cgit v1.2.3 From 41945242524f9ecc795138fdb5beb31362f7826a Mon Sep 17 00:00:00 2001 From: Malfurious Date: Thu, 28 Sep 2023 15:05:51 -0400 Subject: dbg_realcont for testing purposes Signed-off-by: Malfurious --- debugger.c | 19 +++++++++++++++++++ debugger.h | 1 + misplays.c | 3 +++ 3 files changed, 23 insertions(+) diff --git a/debugger.c b/debugger.c index ca774e8..572231c 100644 --- a/debugger.c +++ b/debugger.c @@ -499,6 +499,25 @@ int dbg_cont(struct thread *th, int cont) { return 0; } +int dbg_realcont(struct thread *th) { + if (th->id < 0 || !th->stopped) { + return -1; + } + + struct list *threads = &th->proc->threads; + for (struct thread *t = threads->head; t != threads->end; t = t->next) { + ptrace(PTRACE_CONT, t->id, NULL, NULL); + + t->stopped = 0; + t->signal = 0; + t->cont = 0; + t->status = "RUNNING"; + t->clearstates = 1; + } + + return 0; +} + int dbg_step(struct thread *th, int stepover) { // todo: support step-out //if (th->id < 0 || !th->stopped) { diff --git a/debugger.h b/debugger.h index 845bf69..2705f92 100644 --- a/debugger.h +++ b/debugger.h @@ -68,6 +68,7 @@ extern int dbg_wait(struct thread *th, int recursion); extern int dbg_intr(struct thread *th); extern int dbg_cont(struct thread *th, int cont); +extern int dbg_realcont(struct thread *th); extern int dbg_step(struct thread *th, int stepover); extern int dbg_pets(struct thread *th); diff --git a/misplays.c b/misplays.c index 0910012..272a60a 100644 --- a/misplays.c +++ b/misplays.c @@ -265,6 +265,9 @@ int main(int argc, char **argv) { case 'c': dbg_cont(th, PTRACE_CONT); break; + case 'C': + dbg_realcont(th); + break; case 'p': dbg_intr(th); break; -- cgit v1.2.3 From 888c7ed10d7d82079c7266f4899c1c6f0c805832 Mon Sep 17 00:00:00 2001 From: Malfurious Date: Thu, 28 Sep 2023 15:36:11 -0400 Subject: Display name of pending signal Signed-off-by: Malfurious --- misplays.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/misplays.c b/misplays.c index 272a60a..62d89e7 100644 --- a/misplays.c +++ b/misplays.c @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -133,7 +134,7 @@ static void disasm(struct thread *dbg, PANEL *pan) { static void info_update(struct thread *th, PANEL *pan) { pclear(pan); pprintw(pan, "TID: %li\n", (long)th->id); - pprintw(pan, "%s (%i)\n", th->status, th->signal); + pprintw(pan, "%s (%i: %s)\n", th->status, th->signal, strsignal(th->signal)); list_breakpoints(th, pan); describe_states(th, pan); dump_registers(th, pan); -- cgit v1.2.3 From f9584d0417ed8d3fc72d9dcb297b1738fef2d00c Mon Sep 17 00:00:00 2001 From: Malfurious Date: Thu, 28 Sep 2023 15:40:40 -0400 Subject: setpgid is redundant with setsid and causes an error Signed-off-by: Malfurious --- misplays.c | 1 - 1 file changed, 1 deletion(-) diff --git a/misplays.c b/misplays.c index 62d89e7..da15a30 100644 --- a/misplays.c +++ b/misplays.c @@ -29,7 +29,6 @@ static pid_t dofork(char **argv, struct console *cons) { if (pid == 0) { console_configslave(cons); close_range(STDERR_FILENO+1, ~0U, CLOSE_RANGE_UNSHARE); - setpgid(0, 0); raise(SIGSTOP); // ptrace(PTRACE_TRACEME, 0, NULL, NULL); execvp(argv[0], argv); exit(EXIT_FAILURE); -- cgit v1.2.3 From cca59c4d757c99df14978d1778b6be562dd886cd Mon Sep 17 00:00:00 2001 From: Malfurious Date: Fri, 29 Sep 2023 18:48:41 -0400 Subject: Workaround SIGSTOP on child process startup The debugger design prefers to use PTRACE_SEIZE instead of PTRACE_ATTACH, due to the simpler thread control semantics that are available. However, to utilize the same featureset for forked processes, we can no longer use PTRACE_TRACEME to guarantee that the child becomes a tracee before it execs into the target program. Manually raising SIGSTOP to act as a synchronization point is problematic for a couple reasons: - We need to detect whether the special SIGSTOP was or was not yet encountered by the time our debugger module attaches and interrupts the thread. This complicates the dance of input controls to ensure we are at the exec (and nowhere else) when the real user takes over the controls. - The injection of an extra signal circumvents the benefits we hope to leverage by using the PTRACE_SEIZE semantics. We can no longer assume that all incoming signals are genuine. For the time being, sleep in the newly forked child for the scheduler delay period. This is not bullet-proof, but tends to allow the debugger module enough time to actually seize the thread before anything interesting happens. At this point a single dbg_cont() will cause the child to arrive and stop at the user's exec. Signed-off-by: Malfurious --- debugger.c | 3 ++- misplays.c | 7 ++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/debugger.c b/debugger.c index 572231c..d016076 100644 --- a/debugger.c +++ b/debugger.c @@ -17,7 +17,8 @@ static const int PTRACE_OPTIONS = PTRACE_O_TRACECLONE | - PTRACE_O_TRACEEXIT | + //PTRACE_O_TRACEEXIT | + PTRACE_O_TRACEEXEC | PTRACE_O_TRACESYSGOOD; static int detect_breakpoint(struct thread *th) { diff --git a/misplays.c b/misplays.c index da15a30..2f0ce8f 100644 --- a/misplays.c +++ b/misplays.c @@ -27,9 +27,10 @@ static pid_t dofork(char **argv, struct console *cons) { } if (pid == 0) { + usleep(100000); console_configslave(cons); close_range(STDERR_FILENO+1, ~0U, CLOSE_RANGE_UNSHARE); - raise(SIGSTOP); // ptrace(PTRACE_TRACEME, 0, NULL, NULL); + //raise(SIGSTOP); // ptrace(PTRACE_TRACEME, 0, NULL, NULL); execvp(argv[0], argv); exit(EXIT_FAILURE); } @@ -209,6 +210,10 @@ int main(int argc, char **argv) { list_insert(processes.end, proc); th = proc->threads.head; + if (child) { + dbg_cont(th, PTRACE_CONT); + } + cursinit(); left = newpan(0, 0, 0, 0); right = newpan(0, 0, 0, 0); -- cgit v1.2.3 From 66c02b503ac283506eabfcb943b4247ee6efc7b5 Mon Sep 17 00:00:00 2001 From: Malfurious Date: Fri, 29 Sep 2023 21:39:33 -0400 Subject: Handle PTRACE_EVENT_EXEC Signed-off-by: Malfurious --- debugger.c | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/debugger.c b/debugger.c index d016076..878ce21 100644 --- a/debugger.c +++ b/debugger.c @@ -343,6 +343,7 @@ int dbg_wait(struct thread *th, int recursion) { int stopped; struct thread *newth; + struct list *threads; unsigned long eventmsg; ptrace(PTRACE_GETEVENTMSG, th->id, NULL, &eventmsg); @@ -369,6 +370,34 @@ int dbg_wait(struct thread *th, int recursion) { return 1; + case SIGTRAP | (PTRACE_EVENT_EXEC << 8): + /* eventmsg contains the tid that actually did the execve */ + threads = &th->proc->threads; + for (struct thread *t = threads->head; t != threads->end; t = t->next) { + if (t->id == (pid_t)eventmsg && (pid_t)eventmsg != th->id) { + t->id = -1; + t->stopped = 1; + t->signal = 0; + t->cont = 0; + t->status = "EXITED"; + break; + } + } + + th->stopped = 1; + th->signal = 0; + th->cont = 0; + th->status = "EXEC EVENT"; + th->state = NULL; + + if (!recursion) { + stopped = interrupt_all_threads(th->proc); + uninstall_breakpoints(th); + capture_state(th, stopped); + } + + return 1; + case SIGTRAP | (PTRACE_EVENT_EXIT << 8): th->stopped = 1; th->signal = 0; /* eventmsg has exit code, but would inject sig */ -- cgit v1.2.3 From d6add3a39a71081efe8b5b45ba2ed8a4f5075969 Mon Sep 17 00:00:00 2001 From: Malfurious Date: Fri, 29 Sep 2023 21:45:07 -0400 Subject: Prevent lingering traps after detach There can sometimes be a pending SIGTRAP when we resume a thread. This is usually due to interrupting a hung single-step. If this trap is hit after the debugger leaves, the original process will crash. This is a quick workaround to attempt to consume such traps ourselves before detaching. Signed-off-by: Malfurious --- debugger.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/debugger.c b/debugger.c index 878ce21..46a6a15 100644 --- a/debugger.c +++ b/debugger.c @@ -288,6 +288,9 @@ struct process *dbg_attach(pid_t pid, int child) { } int dbg_detach(struct process *proc) { + interrupt_all_threads(proc); + dbg_realcont(proc->threads.head); + usleep(100000); interrupt_all_threads(proc); uninstall_breakpoints(proc->threads.head); free_breakpoints(proc); -- cgit v1.2.3 From 67a0755a248c9793a1e7a3cf73f4041b2103ebf7 Mon Sep 17 00:00:00 2001 From: Malfurious Date: Mon, 2 Oct 2023 02:34:50 -0400 Subject: Add strict_strtoul Signed-off-by: Malfurious --- helpers.c | 6 ++++++ helpers.h | 1 + 2 files changed, 7 insertions(+) diff --git a/helpers.c b/helpers.c index 8e3be13..163cd2e 100644 --- a/helpers.c +++ b/helpers.c @@ -11,6 +11,12 @@ void *xmalloc(size_t size) { return ptr; } +unsigned long strict_strtoul(const char *nptr, int base) { + char *endptr; + unsigned long ret = strtoul(nptr, &endptr, base); + return (*endptr ? 0 : ret); +} + void cursinit(void) { setlocale(LC_ALL, ""); initscr(); diff --git a/helpers.h b/helpers.h index 02607bb..a415a6c 100644 --- a/helpers.h +++ b/helpers.h @@ -7,6 +7,7 @@ #define KEY_ESCAPE 0x1b extern void *xmalloc(size_t size); +extern unsigned long strict_strtoul(const char *nptr, int base); extern void cursinit(void); extern void cursupdate(void); -- cgit v1.2.3 From 46f72be263cf29688f684e90f2e149e5c911016b Mon Sep 17 00:00:00 2001 From: Malfurious Date: Mon, 2 Oct 2023 03:18:21 -0400 Subject: Multithread version 3 Signed-off-by: Malfurious --- debugger.c | 592 +++++++++++++++++++++++++++++++------------------------------ debugger.h | 30 ++-- misplays.c | 27 +-- 3 files changed, 330 insertions(+), 319 deletions(-) diff --git a/debugger.c b/debugger.c index 46a6a15..6a1cb9d 100644 --- a/debugger.c +++ b/debugger.c @@ -1,6 +1,9 @@ #include +#include #include +#include #include +#include #include #include #include @@ -10,60 +13,32 @@ #include "debugger.h" #include "helpers.h" -#define BREAKPOINT_INSN 0xcc - -//struct list global_processes = {0}; -//struct thread *global_thread = NULL; - static const int PTRACE_OPTIONS = PTRACE_O_TRACECLONE | - //PTRACE_O_TRACEEXIT | PTRACE_O_TRACEEXEC | PTRACE_O_TRACESYSGOOD; -static int detect_breakpoint(struct thread *th) { - int check = 0; - int restart = 0; - - struct user_regs_struct regs; - 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) { - if (b->installed) { - if (!check) { - if (regs.rip - 1 == b->address) { - regs.rip--; - ptrace(PTRACE_SETREGS, th->id, NULL, ®s); - check = 1; - b->hits++; - - if (b->stack != 0 && b->stack != regs.rsp) { - restart = 1; - } +static const int PTRACE_CHILD_OPTIONS = PTRACE_OPTIONS | PTRACE_O_EXITKILL; +static const int STOP_ALL_ON_EVENT = 1; +static const useconds_t SCHEDULER_DELAY = 100000; +static const unsigned int BREAKPOINT_INSN = 0xcc; - if (b->tid != 0 && b->tid != th->id) { - restart = 1; - } - - if (!b->enabled) { - restart = 1; - } - } - } - - ///* this needs moved, we have to finish the for-loop - // * to actually know if restart is true or false... */ - //if (b->enabled < 0 && !restart) { - // struct breakpoint *del = b; - // b = b->prev; - // list_remove(del); - // free(del); - //} +static struct thread *thread_by_id(struct process *proc, pid_t id) { + struct list *threads = &proc->threads; + for (struct thread *th = threads->head; th != threads->end; th = th->next) { + if (th->id == id) { + return th; } } + return NULL; +} - return restart; +static void free_breakpoints(struct process *proc) { + while (proc->breakpoints.head != proc->breakpoints.end) { + struct breakpoint *b = proc->breakpoints.head; + list_remove(b); + free(b); + } } static void install_breakpoints(struct thread *th) { @@ -88,22 +63,51 @@ static void uninstall_breakpoints(struct thread *th) { ptrace(PTRACE_POKETEXT, th->id, b->address, b->text); b->installed = 0; } - if (b->enabled < 0) { - struct breakpoint *del = b; - b = b->next; - list_remove(del); - free(del); + struct thread *t; + if (b->tid == 0 || + ((t = thread_by_id(th->proc, b->tid)) && !t->shouldcont)) { + struct breakpoint *del = b; + b = b->next; + list_remove(del); + free(del); + } } } } -static void free_breakpoints(struct process *proc) { - while (proc->breakpoints.head != proc->breakpoints.end) { - struct breakpoint *b = proc->breakpoints.head; - list_remove(b); - free(b); +static int detect_breakpoint(struct thread *th, int *restart) { + int ret = 0; + *restart = 0; + + struct user_regs_struct regs; + struct iovec ivregs = { ®s, sizeof(regs) }; + ptrace(PTRACE_GETREGSET, th->id, NT_PRSTATUS, &ivregs); + + /* implement with get_breakpoint? */ + struct list *breaks = &th->proc->breakpoints; + for (struct breakpoint *b = breaks->tail; b != breaks->end; b = b->prev) { + if (b->installed && (regs.rip - 1 == b->address)) { + regs.rip--; + ptrace(PTRACE_SETREGSET, th->id, NT_PRSTATUS, &ivregs); + b->hits++; + ret = b->user; + + if (b->stack != 0 && b->stack != regs.rsp) { + *restart = 1; + } + if (b->tid != 0 && b->tid != th->id) { + *restart = 1; + } + if (!b->enabled) { + *restart = 1; + } + + break; + } } + + return ret; } static void free_states(struct thread *th) { @@ -125,155 +129,166 @@ static void free_states(struct thread *th) { th->clearstates = 0; } -static void capture_state_thread(struct thread *th) { - if (th->clearstates) { - free_states(th); - } - - struct state *s = xmalloc(sizeof(*s)); - ptrace(PTRACE_GETREGS, th->id, NULL, &s->regs); - ptrace(PTRACE_GETFPREGS, th->id, NULL, &s->fpregs); - list_init(&s->maps); - - list_insert(th->states.end, s); - th->state = s; +static void capture_state(struct process *proc) { + struct list *threads = &proc->threads; + for (struct thread *th = threads->head; th != threads->end; th = th->next) { + if (!th->shouldcont) { + if (th->clearstates) { + free_states(th); + } - char mapspath[32], entry[512]; - snprintf(mapspath, sizeof(mapspath), "/proc/%li/maps", (long)th->id); + struct state *s = xmalloc(sizeof(*s)); + struct iovec regs = { &s->regs, sizeof(s->regs) }; + ptrace(PTRACE_GETREGSET, th->id, NT_PRSTATUS, ®s); + list_init(&s->maps); + + 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) { + while (fgets(entry, sizeof(entry), maps)) { + struct map *m = xmalloc(sizeof(*m)); + sscanf(entry, "%lx-%lx ", &m->start, &m->end); + size_t size = m->end - m->start; + m->data = xmalloc(size); + + struct iovec loc = { m->data, size }; + struct iovec rem = { (void *)m->start, size }; + if (process_vm_readv(th->id, &loc, 1, &rem, 1, 0) < 0) { + free(m->data); + free(m); + continue; + } - FILE *maps = fopen(mapspath, "r"); - if (maps) { - while (fgets(entry, sizeof(entry), maps)) { - struct map *m = xmalloc(sizeof(*m)); - sscanf(entry, "%lx-%lx ", &m->start, &m->end); - size_t size = m->end - m->start; - m->data = xmalloc(size); + list_insert(s->maps.end, m); + } - struct iovec loc = { m->data, size }; - struct iovec rem = { (void *)m->start, size }; - if (process_vm_readv(th->id, &loc, 1, &rem, 1, 0) < 0) { - free(m->data); - free(m); - continue; + fclose(maps); } + } + } +} - list_insert(s->maps.end, m); +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)) {} + } + if (STOP_ALL_ON_EVENT) { + th->shouldcont = 0; } + } +} - fclose(maps); +static void continue_all_threads(struct process *proc) { + struct list *threads = &proc->threads; + for (struct thread *th = threads->head; th != threads->end; th = th->next) { + if (th->id > 0) { + ptrace(PTRACE_CONT, th->id, NULL, th->signal); + th->stopped = 0; + th->signal = 0; + th->cont = 0; + th->shouldcont = 2; + strcpy(th->status, "RUNNING"); + th->clearstates = 1; + } } } -static void capture_state(struct thread *th, int all) { - (void)all; - struct list *threads = &th->proc->threads; - for (struct thread *t = threads->head; t != threads->end; t = t->next) { - if (!t->state) { - capture_state_thread(t); +/* 0: stop 1: singlestep 2: cont 3: syscall */ +/* this is simplified, surely incorrect, and needs updated once testable */ +static void resume_threads(struct process *proc) { + struct list *threads = &proc->threads; + for (struct thread *th = threads->head; th != threads->end; th = th->next) { + if (th->shouldcont) { + install_breakpoints(th); + ptrace(PTRACE_CONT, th->id, NULL, th->signal); + th->stopped = 0; + th->signal = 0; + th->cont = 0; + strcpy(th->status, "RUNNING"); } } - //if (all) { - // struct list *threads = &th->proc->threads; - // for (struct thread *t = threads->head; t != threads->end; t = t->next) { - // capture_state_thread(t); - // } - //} else { - // capture_state_thread(th); - //} } -static struct process *new_process(pid_t pid, int child) { +static struct process *new_process(pid_t id, int child) { struct process *proc = xmalloc(sizeof(*proc)); - proc->id = pid; + proc->id = id; proc->child = child; - list_init(&proc->threads); list_init(&proc->breakpoints); + list_init(&proc->threads); + memset(proc->status, 0, sizeof(proc->status)); return proc; } -static struct thread *new_thread(struct process *proc, pid_t tid) { +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 = tid; + th->id = id; th->stopped = 0; th->signal = 0; th->cont = 0; - th->status = "RUNNING"; + th->shouldcont = 0; + strcpy(th->status, "RUNNING"); return th; } -static int interrupt_all_threads(struct process *proc) { - int stopped = 0; - 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, 1)) {} - stopped = 1; - } - } - return stopped; -} - -void add_breakpoint(struct process *proc, unsigned long address, unsigned long stack, pid_t tid, int enabled) { +struct breakpoint *add_breakpoint(struct process *proc, unsigned long address) { struct breakpoint *b = xmalloc(sizeof(*b)); b->address = address; b->text = 0; b->installed = 0; b->hits = 0; - b->stack = stack; - b->tid = tid; - b->enabled = enabled; + b->user = 1; + b->stack = 0; + b->tid = 0; + b->enabled = 1; list_insert(proc->breakpoints.end, b); + return b; } -int is_breakpoint(struct process *proc, unsigned long address) { +struct breakpoint *get_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 b; } } - return 0; + return NULL; } -//int dbg_attach(pid_t pid, int child) { struct process *dbg_attach(pid_t pid, int child) { - //if (global_processes.end == NULL) { - // list_init(&global_processes); - //} - - struct process *proc = new_process(pid, child); - //list_insert(global_processes.end, proc); - - int options = PTRACE_OPTIONS; - if (child) { - options |= PTRACE_O_EXITKILL; - } + const int OPTIONS = (child ? PTRACE_CHILD_OPTIONS : PTRACE_OPTIONS); char taskpath[32]; snprintf(taskpath, sizeof(taskpath), "/proc/%li/task", (long)pid); DIR *taskdir = opendir(taskpath); if (!taskdir) { - dbg_detach(proc); return NULL; } struct dirent *task; + struct process *proc = new_process(pid, child); + while ((task = readdir(taskdir))) { - pid_t id = strtoul(task->d_name, NULL, 0); + pid_t id = strict_strtoul(task->d_name, 0); if (id != 0) { - if (ptrace(PTRACE_SEIZE, id, NULL, options) < 0) { + if (ptrace(PTRACE_SEIZE, id, NULL, OPTIONS) < 0) { closedir(taskdir); dbg_detach(proc); return NULL; } - struct thread *th = new_thread(proc, id); list_insert(proc->threads.end, th); } @@ -281,137 +296,137 @@ struct process *dbg_attach(pid_t pid, int child) { closedir(taskdir); - //global_thread = proc->threads.head; interrupt_all_threads(proc); - capture_state(proc->threads.head, 0); + capture_state(proc); return proc; } -int dbg_detach(struct process *proc) { +void dbg_detach(struct process *proc) { + /* ptrace requires that threads be stopped before calling PTRACE_DETACH. + * In some cases, a blocked thread can possibly have a pending trap which + * would crash the process if encountered after ptracing. We cycle an extra + * continue/interrupt to consume this trap event ourselves before detaching. + * Threads must actually get scheduled for this to be effective, which is + * the reason for the usleep. A better approach is welcome here. */ interrupt_all_threads(proc); - dbg_realcont(proc->threads.head); - usleep(100000); + continue_all_threads(proc); + usleep(SCHEDULER_DELAY); interrupt_all_threads(proc); - uninstall_breakpoints(proc->threads.head); - free_breakpoints(proc); + /* Supplement to PTRACE_O_EXITKILL */ if (proc->child) { kill(proc->id, SIGKILL); } while (proc->threads.head != proc->threads.end) { struct thread *th = proc->threads.head; - if (th->id >= 0) { + if (th->id > 0) { + uninstall_breakpoints(th); ptrace(PTRACE_DETACH, th->id, NULL, th->signal); + th->id = 0; } - free_states(th); - list_remove(th); - free(th); + dbg_free(th); } + free_breakpoints(proc); list_remove(proc); free(proc); - return 0; } -int dbg_wait(struct thread *th, int recursion) { - if (th->id < 0) { +int dbg_free(struct thread *th) { + if (th->id <= 0) { + free_states(th); + list_remove(th); + free(th); + return 0; + } + return -1; +} + +int dbg_wait(struct thread *th, int primary) { + if (th->id <= 0) { return -1; + } else if (th->stopped) { + return 1; } - /* todo: check what happens if we call on an already known - * stopped thread? */ int status; if (waitpid(th->id, &status, __WALL | WNOHANG) <= 0) { return 0; } if (WIFEXITED(status)) { - th->id = -1; + th->id = 0; th->stopped = 1; th->signal = WEXITSTATUS(status); th->cont = 0; - th->status = "EXITED"; + th->shouldcont = 0; + strcpy(th->status, "EXITED"); return 1; } if (WIFSIGNALED(status)) { - th->id = -2; + th->id = 0; th->stopped = 1; th->signal = WTERMSIG(status); th->cont = 0; - th->status = "TERMINATED"; + th->shouldcont = 0; + strcpy(th->status, "TERMINATED"); return 1; } - int stopped; - struct thread *newth; - struct list *threads; - + struct thread *newth; // event thread unsigned long eventmsg; ptrace(PTRACE_GETEVENTMSG, th->id, NULL, &eventmsg); + /* todo: process status messages */ if (WIFSTOPPED(status)) { switch (status >> 8) { /* todo: other ptrace event stops */ case SIGTRAP | (PTRACE_EVENT_CLONE << 8): newth = new_thread(th->proc, eventmsg); list_insert(th->proc->threads.end, newth); - while (!dbg_wait(newth, 1)) {} + while (!dbg_wait(newth, 0)) {} th->stopped = 1; th->signal = 0; th->cont = 0; - th->status = "CLONE EVENT"; + th->shouldcont = 0; + strcpy(th->status, "CLONE"); th->state = NULL; - if (!recursion) { - stopped = interrupt_all_threads(th->proc); + if (primary) { + interrupt_all_threads(th->proc); uninstall_breakpoints(th); - capture_state(th, stopped); + capture_state(th->proc); + resume_threads(th->proc); } return 1; case SIGTRAP | (PTRACE_EVENT_EXEC << 8): - /* eventmsg contains the tid that actually did the execve */ - threads = &th->proc->threads; - for (struct thread *t = threads->head; t != threads->end; t = t->next) { - if (t->id == (pid_t)eventmsg && (pid_t)eventmsg != th->id) { - t->id = -1; - t->stopped = 1; - t->signal = 0; - t->cont = 0; - t->status = "EXITED"; - break; - } + if ((pid_t)eventmsg != th->id) { + newth = thread_by_id(th->proc, eventmsg); + newth->id = 0; + newth->stopped = 1; + newth->signal = 0; + newth->cont = 0; + newth->shouldcont = 0; + strcpy(newth->status, "EXITED"); } th->stopped = 1; th->signal = 0; th->cont = 0; - th->status = "EXEC EVENT"; - th->state = NULL; - - if (!recursion) { - stopped = interrupt_all_threads(th->proc); - uninstall_breakpoints(th); - capture_state(th, stopped); - } - - return 1; - - case SIGTRAP | (PTRACE_EVENT_EXIT << 8): - th->stopped = 1; - th->signal = 0; /* eventmsg has exit code, but would inject sig */ - th->cont = 0; - th->status = "EXIT EVENT"; + th->shouldcont = 0; + strcpy(th->status, "EXECVE"); th->state = NULL; - if (!recursion) { - stopped = interrupt_all_threads(th->proc); + if (primary) { + interrupt_all_threads(th->proc); uninstall_breakpoints(th); - capture_state(th, stopped); + capture_state(th->proc); + resume_threads(th->proc); } return 1; @@ -420,28 +435,30 @@ int dbg_wait(struct thread *th, int recursion) { th->stopped = 1; th->signal = 0; th->cont = 0; - th->status = "STOP EVENT"; - th->state = NULL; + strcpy(th->status, "STOPPED"); - if (!recursion) { - stopped = interrupt_all_threads(th->proc); + if (primary) { + interrupt_all_threads(th->proc); uninstall_breakpoints(th); - capture_state(th, stopped); + capture_state(th->proc); + resume_threads(th->proc); } - return 1; + return th->stopped; case SIGTRAP | 0x80: th->stopped = 1; th->signal = 0; th->cont = 0; - th->status = "SYSCALL EVENT"; + th->shouldcont = 0; + strcpy(th->status, "SYSCALL"); th->state = NULL; - if (!recursion) { - stopped = interrupt_all_threads(th->proc); + if (primary) { + interrupt_all_threads(th->proc); uninstall_breakpoints(th); - capture_state(th, stopped); + capture_state(th->proc); + resume_threads(th->proc); } return 1; @@ -449,52 +466,35 @@ int dbg_wait(struct thread *th, int recursion) { case SIGTRAP: th->stopped = 1; th->signal = 0; - th->status = "STEP/BREAKPOINT"; - - if (th->cont != 0) { - /* gdb this portion. are there race conditions - * that matter?? */ - install_breakpoints(th); - ptrace(th->cont, th->id, NULL, NULL); - th->cont = 0; - th->stopped = 0; - th->status = "RUNNING"; - return 0; - } - - /* todo: Test two threads hitting a breakpoint at - * the same time. */ - int restart = detect_breakpoint(th); - if (!restart) { - th->state = NULL; - } - if (!recursion) { - stopped = interrupt_all_threads(th->proc); - if (!restart) { - uninstall_breakpoints(th); - capture_state(th, stopped); - } - } + int restart; + int bp = detect_breakpoint(th, &restart); + strcpy(th->status, (bp ? "BREAKPOINT" : "STEP")); + //th->shouldcont = (b && b->enabled == 0); + th->shouldcont = (restart ? 2 : 0); - if (restart) { - dbg_cont(th, PTRACE_CONT); - return 0; + if (primary) { + interrupt_all_threads(th->proc); + uninstall_breakpoints(th); + capture_state(th->proc); + resume_threads(th->proc); } - return 1; + return th->stopped; default: th->stopped = 1; th->signal = WSTOPSIG(status); th->cont = 0; - th->status = "SIGNAL DELIVERY"; + th->shouldcont = 0; + strcpy(th->status, "SIGNAL DELIVERY"); th->state = NULL; - if (!recursion) { - stopped = interrupt_all_threads(th->proc); + if (primary) { + interrupt_all_threads(th->proc); uninstall_breakpoints(th); - capture_state(th, stopped); + capture_state(th->proc); + resume_threads(th->proc); } return 1; @@ -505,58 +505,70 @@ int dbg_wait(struct thread *th, int recursion) { } int dbg_intr(struct thread *th) { - if (th->id < 0 || th->stopped) { + if (th->id <= 0 || th->stopped) { return -1; } ptrace(PTRACE_INTERRUPT, th->id, NULL, NULL); + th->shouldcont = 0; return 0; } -int dbg_cont(struct thread *th, int cont) { - if (th->id < 0 || !th->stopped) { +int dbg_cont(struct thread *th) { + if (th->id <= 0 || !th->stopped) { return -1; } - 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); + ptrace(PTRACE_SINGLESTEP, th->id, NULL, th->signal); + th->stopped = 0; + th->signal = 0; + th->cont = PTRACE_CONT; + th->shouldcont = 1; + strcpy(th->status, "RUNNING"); + th->clearstates = 1; + return 0; +} - t->stopped = 0; - t->signal = 0; - t->cont = cont; - t->status = "RUNNING"; - t->clearstates = 1; +int dbg_syscall(struct thread *th) { + if (th->id <= 0 || !th->stopped) { + return -1; } + ptrace(PTRACE_SINGLESTEP, th->id, NULL, th->signal); + th->stopped = 0; + th->signal = 0; + th->cont = PTRACE_SYSCALL; + th->shouldcont = 1; + strcpy(th->status, "RUNNING"); + th->clearstates = 1; return 0; } -int dbg_realcont(struct thread *th) { - if (th->id < 0 || !th->stopped) { +int dbg_stepin(struct thread *th) { + if (!th->stopped) { return -1; } - struct list *threads = &th->proc->threads; - for (struct thread *t = threads->head; t != threads->end; t = t->next) { - ptrace(PTRACE_CONT, t->id, NULL, NULL); + if (th->state != th->states.tail) { + th->state = th->state->next; + return 0; + } - t->stopped = 0; - t->signal = 0; - t->cont = 0; - t->status = "RUNNING"; - t->clearstates = 1; + if (th->id <= 0) { + return -1; } + ptrace(PTRACE_SINGLESTEP, th->id, NULL, th->signal); + th->stopped = 0; + th->signal = 0; + th->cont = PTRACE_SINGLESTEP; + th->shouldcont = 1; + strcpy(th->status, "RUNNING"); + th->clearstates = 0; return 0; } -int dbg_step(struct thread *th, int stepover) { - // todo: support step-out - //if (th->id < 0 || !th->stopped) { - // return -1; - //} - +int dbg_stepover(struct thread *th) { if (!th->stopped) { return -1; } @@ -566,39 +578,39 @@ int dbg_step(struct thread *th, int stepover) { return 0; } - if (th->id < 0) { + if (th->id <= 0) { return -1; } - csh cshandle; - cs_open(CS_ARCH_X86, CS_MODE_64, &cshandle); - cs_insn *insn = cs_malloc(cshandle); + csh handle; + cs_open(CS_ARCH_X86, CS_MODE_64, &handle); + cs_insn *insn = cs_malloc(handle); uint64_t address = th->state->regs.rip; size_t size = 128; const uint8_t *code = deref(th, address, size); - cs_disasm_iter(cshandle, &code, &size, &address, insn); + cs_disasm_iter(handle, &code, &size, &address, insn); + + int ret = -1; + if (insn->id == X86_INS_CALL) { + struct breakpoint *b = add_breakpoint(th->proc, address); + b->user = 0; + b->stack = th->state->regs.rsp; + b->tid = th->id; + b->enabled = -1; - if (insn->id == X86_INS_CALL && stepover) { - add_breakpoint(th->proc, address, th->state->regs.rsp, th->id, -1); - th->cont = PTRACE_CONT; + ret = dbg_cont(th); } else { - th->cont = 0; + ret = dbg_stepin(th); } - ptrace(PTRACE_SINGLESTEP, th->id, NULL, th->signal); - - th->stopped = 0; - th->signal = 0; - th->status = "RUNNING"; - cs_free(insn, 1); - cs_close(&cshandle); - return 0; + cs_close(&handle); + return ret; } -int dbg_pets(struct thread *th) { +int dbg_stepback(struct thread *th) { if (!th->stopped) { return -1; } @@ -633,10 +645,6 @@ void *deref(struct thread *th, unsigned long address, size_t size) { -///* 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. -// */ //int dbg_stepout(struct tracee *dbg) { // unsigned long bp = dbg->state->regs.rbp; diff --git a/debugger.h b/debugger.h index 2705f92..ef08d43 100644 --- a/debugger.h +++ b/debugger.h @@ -12,6 +12,7 @@ struct breakpoint { unsigned long text; int installed; int hits; + int user; unsigned long stack; pid_t tid; @@ -28,7 +29,6 @@ struct map { struct state { LINKEDLIST; struct user_regs_struct regs; - struct user_fpregs_struct fpregs; struct list maps; }; @@ -36,8 +36,9 @@ struct process { LINKEDLIST; pid_t id; int child; - struct list threads; struct list breakpoints; + struct list threads; + char status[128]; }; struct thread { @@ -52,24 +53,25 @@ struct thread { int stopped; int signal; int cont; + int shouldcont; - const char *status; + char status[128]; }; -//extern struct list global_processes; -//extern struct thread *global_thread; - -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 struct breakpoint*add_breakpoint(struct process*proc,unsigned long address); +extern struct breakpoint*get_breakpoint(struct process*proc,unsigned long address); extern struct process *dbg_attach(pid_t pid, int child); -extern int dbg_detach(struct process *proc); -extern int dbg_wait(struct thread *th, int recursion); +extern void dbg_detach(struct process *proc); +extern int dbg_free(struct thread *th); + +extern int dbg_wait(struct thread *th, int primary); extern int dbg_intr(struct thread *th); -extern int dbg_cont(struct thread *th, int cont); -extern int dbg_realcont(struct thread *th); -extern int dbg_step(struct thread *th, int stepover); -extern int dbg_pets(struct thread *th); +extern int dbg_cont(struct thread *th); +extern int dbg_syscall(struct thread *th); +extern int dbg_stepin(struct thread *th); +extern int dbg_stepover(struct thread *th); +extern int dbg_stepback(struct thread *th); extern void *deref(struct thread *th, unsigned long address, size_t size); diff --git a/misplays.c b/misplays.c index 2f0ce8f..815e045 100644 --- a/misplays.c +++ b/misplays.c @@ -44,7 +44,10 @@ static void list_breakpoints(struct thread *dbg, PANEL *pan) { pprintw(pan, "---\n"); for (struct breakpoint *bp=breaks->head; bp!=breaks->end; bp=bp->next) { - pprintw(pan, "0x%lx (%c) ", bp->address, (bp->installed ? '*' : ' ')); + pprintw(pan, "0x%lx (%c) (%i) ", + bp->address, + (bp->installed ? '*' : ' '), + bp->hits); } pprintw(pan, "\n"); } @@ -108,7 +111,7 @@ static void disasm(struct thread *dbg, PANEL *pan) { if (dbg->stopped) { if (insn->address == dbg->state->regs.rip) { pattron(pan, COLOR_PAIR(1)); - } else if (is_breakpoint(dbg->proc, insn->address)) { + } else if (get_breakpoint(dbg->proc, insn->address)) { pattron(pan, COLOR_PAIR(3)); } else if (rip_visited(dbg, insn->address)) { pattron(pan, COLOR_PAIR(2)); @@ -118,7 +121,7 @@ static void disasm(struct thread *dbg, PANEL *pan) { if (dbg->stopped) { if (insn->address == dbg->state->regs.rip) { pattroff(pan, COLOR_PAIR(1)); - } else if (is_breakpoint(dbg->proc, insn->address)) { + } else if (get_breakpoint(dbg->proc, insn->address)) { pattroff(pan, COLOR_PAIR(3)); } else if (rip_visited(dbg, insn->address)) { pattroff(pan, COLOR_PAIR(2)); @@ -211,7 +214,7 @@ int main(int argc, char **argv) { th = proc->threads.head; if (child) { - dbg_cont(th, PTRACE_CONT); + dbg_cont(th); } cursinit(); @@ -243,13 +246,13 @@ int main(int argc, char **argv) { console_enter(&cons, right); break; case 'j': - dbg_step(th, 1); + dbg_stepover(th); break; case 'k': - dbg_pets(th); + dbg_stepback(th); break; case 'l': - dbg_step(th, 0); + dbg_stepin(th); break; case 'h': /* todo: step out */ @@ -265,13 +268,10 @@ int main(int argc, char **argv) { } break; case 's': - dbg_cont(th, PTRACE_SYSCALL); + dbg_syscall(th); break; case 'c': - dbg_cont(th, PTRACE_CONT); - break; - case 'C': - dbg_realcont(th); + dbg_cont(th); break; case 'p': dbg_intr(th); @@ -313,7 +313,8 @@ int main(int argc, char **argv) { t++; } unsigned long address = strtoul(t, NULL, 0); - add_breakpoint(th->proc, address, 0, 0, en); + struct breakpoint *b = add_breakpoint(th->proc, address); + b->enabled = en; break; } } else { -- cgit v1.2.3 From 66db439988aa07828593aac109f5690bb48f2dc9 Mon Sep 17 00:00:00 2001 From: Malfurious Date: Fri, 6 Oct 2023 04:55:18 -0400 Subject: Independent thread control refactor Signed-off-by: Malfurious --- debugger.c | 412 +++++++++++++++++++++++++++++-------------------------------- debugger.h | 6 +- misplays.c | 5 +- 3 files changed, 203 insertions(+), 220 deletions(-) diff --git a/debugger.c b/debugger.c index 6a1cb9d..a1ff940 100644 --- a/debugger.c +++ b/debugger.c @@ -19,10 +19,35 @@ static const int PTRACE_OPTIONS = PTRACE_O_TRACESYSGOOD; static const int PTRACE_CHILD_OPTIONS = PTRACE_OPTIONS | PTRACE_O_EXITKILL; -static const int STOP_ALL_ON_EVENT = 1; +//static const int STOP_ALL_ON_EVENT = 1; static const useconds_t SCHEDULER_DELAY = 100000; static const unsigned int BREAKPOINT_INSN = 0xcc; +static struct process *new_process(pid_t id, int child) { + struct process *proc = xmalloc(sizeof(*proc)); + proc->id = id; + proc->child = child; + list_init(&proc->breakpoints); + list_init(&proc->threads); + memset(proc->status, 0, sizeof(proc->status)); + return proc; +} + +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->doing = 0; + th->donext = 0; + strcpy(th->status, "RUNNING"); + return th; +} + static struct thread *thread_by_id(struct process *proc, pid_t id) { struct list *threads = &proc->threads; for (struct thread *th = threads->head; th != threads->end; th = th->next) { @@ -132,7 +157,7 @@ static void free_states(struct thread *th) { static void capture_state(struct process *proc) { struct list *threads = &proc->threads; for (struct thread *th = threads->head; th != threads->end; th = th->next) { - if (!th->shouldcont) { + if (!th->state) { if (th->clearstates) { free_states(th); } @@ -173,15 +198,17 @@ static void capture_state(struct process *proc) { } } +static int wait_thread(struct thread *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)) {} - } - if (STOP_ALL_ON_EVENT) { - th->shouldcont = 0; + while (!wait_thread(th)) {} + //if (STOP_ALL_ON_EVENT) { + // th->cont = 0; + // th->shouldcont = 0; + //} } } } @@ -193,53 +220,157 @@ static void continue_all_threads(struct process *proc) { ptrace(PTRACE_CONT, th->id, NULL, th->signal); th->stopped = 0; th->signal = 0; - th->cont = 0; - th->shouldcont = 2; + th->donext = 0; + th->doing = PTRACE_CONT; strcpy(th->status, "RUNNING"); th->clearstates = 1; } } } -/* 0: stop 1: singlestep 2: cont 3: syscall */ -/* this is simplified, surely incorrect, and needs updated once testable */ static void resume_threads(struct process *proc) { struct list *threads = &proc->threads; + + for (struct thread *th = threads->head; th != threads->end; th = th->next) { + if (th->doing == PTRACE_SINGLESTEP) { + ptrace(PTRACE_SINGLESTEP, th->id, NULL, th->signal); + th->stopped = 0; + th->signal = 0; + strcpy(th->status, "RUNNING"); + } + } + for (struct thread *th = threads->head; th != threads->end; th = th->next) { - if (th->shouldcont) { + if (th->doing && th->doing != PTRACE_SINGLESTEP) { install_breakpoints(th); - ptrace(PTRACE_CONT, th->id, NULL, th->signal); + ptrace(th->doing, th->id, NULL, th->signal); th->stopped = 0; th->signal = 0; - th->cont = 0; strcpy(th->status, "RUNNING"); } } } -static struct process *new_process(pid_t id, int child) { - struct process *proc = xmalloc(sizeof(*proc)); - proc->id = id; - proc->child = child; - list_init(&proc->breakpoints); - list_init(&proc->threads); - memset(proc->status, 0, sizeof(proc->status)); - return proc; -} +static int wait_thread(struct thread *th) { + if (th->id <= 0) { + return -1; + } else if (th->stopped) { + return 1; + } -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->shouldcont = 0; - strcpy(th->status, "RUNNING"); - return th; + int status; + if (waitpid(th->id, &status, __WALL | WNOHANG) <= 0) { + return 0; + } + + if (WIFEXITED(status)) { + th->id = 0; + th->stopped = 1; + th->signal = WEXITSTATUS(status); + th->doing = 0; + th->donext = 0; + strcpy(th->status, "EXITED"); + return 1; + } else if (WIFSIGNALED(status)) { + th->id = 0; + th->stopped = 1; + th->signal = WTERMSIG(status); + th->doing = 0; + th->donext = 0; + strcpy(th->status, "TERMINATED"); + return 1; + } else if (WIFSTOPPED(status)) { + struct thread *eventth; + unsigned long eventmsg; + ptrace(PTRACE_GETEVENTMSG, th->id, NULL, &eventmsg); + + /* todo: process status messages */ + switch (status >> 8) { + /* todo: other ptrace event stops */ + case SIGTRAP | (PTRACE_EVENT_CLONE << 8): + eventth = new_thread(th->proc, eventmsg); + list_insert(th->proc->threads.end, eventth); + while (!wait_thread(eventth)) {} + + th->stopped = 1; + th->signal = 0; + th->doing = 0; + th->donext = 0; + strcpy(th->status, "CLONE"); + th->state = NULL; + return 1; + + case SIGTRAP | (PTRACE_EVENT_EXEC << 8): + eventth = thread_by_id(th->proc, eventmsg); + if (eventth->id != th->id) { + eventth->id = 0; + eventth->stopped = 1; + eventth->signal = 0; + eventth->doing = 0; + eventth->donext = 0; + strcpy(eventth->status, "EXITED"); + } + + th->stopped = 1; + th->signal = 0; + th->doing = 0; + th->donext = 0; + strcpy(th->status, "EXECVE"); + th->state = NULL; + return 1; + + case SIGTRAP | (PTRACE_EVENT_STOP << 8): + th->stopped = 1; + th->signal = 0; + strcpy(th->status, "STOPPED"); + if (!th->doing) { + th->state = NULL; + } + return 1; + + case SIGTRAP | 0x80: + th->stopped = 1; + th->signal = 0; + th->doing = 0; + th->donext = 0; + strcpy(th->status, "SYSCALL"); + th->state = NULL; + return 1; + + case SIGTRAP: + th->stopped = 1; + th->signal = 0; + + int restart; + int bp = detect_breakpoint(th, &restart); + strcpy(th->status, (bp ? "BREAKPOINT" : "STEP")); + + if (restart) { + th->donext = th->doing; + th->doing = (th->doing ? PTRACE_SINGLESTEP : 0); + } else { + th->doing = th->donext; + th->donext = 0; + } + + if (!th->doing) { + th->state = NULL; + } + + return 1; + + default: + th->stopped = 1; + th->signal = WSTOPSIG(status); + th->doing = 0; + th->donext = 0; + strcpy(th->status, "SIGNAL DELIVERY"); + th->state = NULL; + return 1; + } + } + + return -1; } struct breakpoint *add_breakpoint(struct process *proc, unsigned long address) { @@ -343,165 +474,26 @@ int dbg_free(struct thread *th) { return -1; } -int dbg_wait(struct thread *th, int primary) { - if (th->id <= 0) { - return -1; - } else if (th->stopped) { - return 1; - } - - int status; - if (waitpid(th->id, &status, __WALL | WNOHANG) <= 0) { - return 0; - } - - if (WIFEXITED(status)) { - th->id = 0; - th->stopped = 1; - th->signal = WEXITSTATUS(status); - th->cont = 0; - th->shouldcont = 0; - strcpy(th->status, "EXITED"); - return 1; - } - - if (WIFSIGNALED(status)) { - th->id = 0; - th->stopped = 1; - th->signal = WTERMSIG(status); - th->cont = 0; - th->shouldcont = 0; - strcpy(th->status, "TERMINATED"); - return 1; - } - - struct thread *newth; // event thread - unsigned long eventmsg; - ptrace(PTRACE_GETEVENTMSG, th->id, NULL, &eventmsg); - - /* todo: process status messages */ - if (WIFSTOPPED(status)) { - switch (status >> 8) { - /* todo: other ptrace event stops */ - 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->shouldcont = 0; - strcpy(th->status, "CLONE"); - th->state = NULL; - - if (primary) { - interrupt_all_threads(th->proc); - uninstall_breakpoints(th); - capture_state(th->proc); - resume_threads(th->proc); - } - - return 1; - - case SIGTRAP | (PTRACE_EVENT_EXEC << 8): - if ((pid_t)eventmsg != th->id) { - newth = thread_by_id(th->proc, eventmsg); - newth->id = 0; - newth->stopped = 1; - newth->signal = 0; - newth->cont = 0; - newth->shouldcont = 0; - strcpy(newth->status, "EXITED"); - } - - th->stopped = 1; - th->signal = 0; - th->cont = 0; - th->shouldcont = 0; - strcpy(th->status, "EXECVE"); - th->state = NULL; - - if (primary) { - interrupt_all_threads(th->proc); - uninstall_breakpoints(th); - capture_state(th->proc); - resume_threads(th->proc); - } - - return 1; - - case SIGTRAP | (PTRACE_EVENT_STOP << 8): - th->stopped = 1; - th->signal = 0; - th->cont = 0; - strcpy(th->status, "STOPPED"); - - if (primary) { - interrupt_all_threads(th->proc); - uninstall_breakpoints(th); - capture_state(th->proc); - resume_threads(th->proc); - } - - return th->stopped; - - case SIGTRAP | 0x80: - th->stopped = 1; - th->signal = 0; - th->cont = 0; - th->shouldcont = 0; - strcpy(th->status, "SYSCALL"); - th->state = NULL; - - if (primary) { - interrupt_all_threads(th->proc); - uninstall_breakpoints(th); - capture_state(th->proc); - resume_threads(th->proc); - } - - return 1; +void dbg_sync(struct process *proc) { + struct thread *acted = NULL; - case SIGTRAP: - th->stopped = 1; - th->signal = 0; - - int restart; - int bp = detect_breakpoint(th, &restart); - strcpy(th->status, (bp ? "BREAKPOINT" : "STEP")); - //th->shouldcont = (b && b->enabled == 0); - th->shouldcont = (restart ? 2 : 0); - - if (primary) { - interrupt_all_threads(th->proc); - uninstall_breakpoints(th); - capture_state(th->proc); - resume_threads(th->proc); - } - - return th->stopped; - - default: - th->stopped = 1; - th->signal = WSTOPSIG(status); - th->cont = 0; - th->shouldcont = 0; - strcpy(th->status, "SIGNAL DELIVERY"); - th->state = NULL; - - if (primary) { - interrupt_all_threads(th->proc); - uninstall_breakpoints(th); - capture_state(th->proc); - resume_threads(th->proc); - } - - return 1; + struct list *threads = &proc->threads; + for (struct thread *th = threads->head; th != threads->end; th = th->next) { + if (th->id > 0) { + if (!th->stopped && wait_thread(th)) { + acted = th; + } else if (th->stopped && th->doing) { + acted = th; + } } } - return -1; + if (acted) { + interrupt_all_threads(proc); + uninstall_breakpoints(acted); + capture_state(proc); + resume_threads(proc); + } } int dbg_intr(struct thread *th) { @@ -509,8 +501,11 @@ int dbg_intr(struct thread *th) { return -1; } + /* todo: move this into dbg_sync? */ + /* probably not */ ptrace(PTRACE_INTERRUPT, th->id, NULL, NULL); - th->shouldcont = 0; + th->doing = 0; + th->donext = 0; return 0; } @@ -519,12 +514,8 @@ int dbg_cont(struct thread *th) { return -1; } - ptrace(PTRACE_SINGLESTEP, th->id, NULL, th->signal); - th->stopped = 0; - th->signal = 0; - th->cont = PTRACE_CONT; - th->shouldcont = 1; - strcpy(th->status, "RUNNING"); + th->doing = PTRACE_SINGLESTEP; + th->donext = PTRACE_CONT; th->clearstates = 1; return 0; } @@ -534,12 +525,8 @@ int dbg_syscall(struct thread *th) { return -1; } - ptrace(PTRACE_SINGLESTEP, th->id, NULL, th->signal); - th->stopped = 0; - th->signal = 0; - th->cont = PTRACE_SYSCALL; - th->shouldcont = 1; - strcpy(th->status, "RUNNING"); + th->doing = PTRACE_SINGLESTEP; + th->donext = PTRACE_SYSCALL; th->clearstates = 1; return 0; } @@ -558,12 +545,8 @@ int dbg_stepin(struct thread *th) { return -1; } - ptrace(PTRACE_SINGLESTEP, th->id, NULL, th->signal); - th->stopped = 0; - th->signal = 0; - th->cont = PTRACE_SINGLESTEP; - th->shouldcont = 1; - strcpy(th->status, "RUNNING"); + th->doing = PTRACE_SINGLESTEP; + th->donext = 0; th->clearstates = 0; return 0; } @@ -592,7 +575,6 @@ int dbg_stepover(struct thread *th) { cs_disasm_iter(handle, &code, &size, &address, insn); - int ret = -1; if (insn->id == X86_INS_CALL) { struct breakpoint *b = add_breakpoint(th->proc, address); b->user = 0; @@ -600,14 +582,18 @@ int dbg_stepover(struct thread *th) { b->tid = th->id; b->enabled = -1; - ret = dbg_cont(th); + th->doing = PTRACE_SINGLESTEP; + th->donext = PTRACE_CONT; + th->clearstates = 0; } else { - ret = dbg_stepin(th); + th->doing = PTRACE_SINGLESTEP; + th->donext = 0; + th->clearstates = 0; } cs_free(insn, 1); cs_close(&handle); - return ret; + return 0; } int dbg_stepback(struct thread *th) { diff --git a/debugger.h b/debugger.h index ef08d43..cea3bba 100644 --- a/debugger.h +++ b/debugger.h @@ -52,8 +52,8 @@ struct thread { pid_t id; int stopped; int signal; - int cont; - int shouldcont; + int doing; + int donext; char status[128]; }; @@ -65,7 +65,7 @@ extern struct process *dbg_attach(pid_t pid, int child); extern void dbg_detach(struct process *proc); extern int dbg_free(struct thread *th); -extern int dbg_wait(struct thread *th, int primary); +extern void dbg_sync(struct process *proc); extern int dbg_intr(struct thread *th); extern int dbg_cont(struct thread *th); diff --git a/misplays.c b/misplays.c index 815e045..c064f75 100644 --- a/misplays.c +++ b/misplays.c @@ -149,10 +149,7 @@ static void info_update(struct thread *th, PANEL *pan) { static void wait_all_threads(struct list *processes) { for (struct process *proc = processes->head; proc != processes->end; proc = proc->next) { - struct list *threads = &proc->threads; - for (struct thread *th = threads->head; th != threads->end; th = th->next) { - dbg_wait(th, 0); - } + dbg_sync(proc); } } -- cgit v1.2.3 From 3a8bf604bf4d086bdc4fee70a82371a338835222 Mon Sep 17 00:00:00 2001 From: Malfurious Date: Sat, 7 Oct 2023 02:02:44 -0400 Subject: Fix bug with cleaning temporary breakpoints dont remove them before initial use Signed-off-by: Malfurious --- debugger.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/debugger.c b/debugger.c index a1ff940..b08faf6 100644 --- a/debugger.c +++ b/debugger.c @@ -87,15 +87,16 @@ static void uninstall_breakpoints(struct thread *th) { if (b->installed) { ptrace(PTRACE_POKETEXT, th->id, b->address, b->text); b->installed = 0; - } - if (b->enabled < 0) { - struct thread *t; - if (b->tid == 0 || - ((t = thread_by_id(th->proc, b->tid)) && !t->shouldcont)) { - struct breakpoint *del = b; - b = b->next; - list_remove(del); - free(del); + + if (b->enabled < 0) { + struct thread *t; + if (b->tid == 0 || + ((t = thread_by_id(th->proc, b->tid)) && !t->doing)) { + struct breakpoint *del = b; + b = b->next; + list_remove(del); + free(del); + } } } } -- cgit v1.2.3 From 5589a9e3afd51bdf3d8715fb09b1667c6773b73f Mon Sep 17 00:00:00 2001 From: Malfurious Date: Sat, 7 Oct 2023 02:07:04 -0400 Subject: Ignore breakpoints during singlestep Due to new independent thread control, it is now possible and likely that breakpoints will be installed before singlesteps are waited upon to be completed. also clean detect_breakpoint with get_breakpoint. Signed-off-by: Malfurious --- debugger.c | 33 +++++++++++++-------------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/debugger.c b/debugger.c index b08faf6..b646dc4 100644 --- a/debugger.c +++ b/debugger.c @@ -110,26 +110,19 @@ static int detect_breakpoint(struct thread *th, int *restart) { struct iovec ivregs = { ®s, sizeof(regs) }; ptrace(PTRACE_GETREGSET, th->id, NT_PRSTATUS, &ivregs); - /* implement with get_breakpoint? */ - struct list *breaks = &th->proc->breakpoints; - for (struct breakpoint *b = breaks->tail; b != breaks->end; b = b->prev) { - if (b->installed && (regs.rip - 1 == b->address)) { - regs.rip--; - ptrace(PTRACE_SETREGSET, th->id, NT_PRSTATUS, &ivregs); - b->hits++; - ret = b->user; - - if (b->stack != 0 && b->stack != regs.rsp) { - *restart = 1; - } - if (b->tid != 0 && b->tid != th->id) { - *restart = 1; - } - if (!b->enabled) { - *restart = 1; - } - - break; + struct breakpoint *b = get_breakpoint(th->proc, regs.rip - 1); + if (b && b->installed && th->doing != PTRACE_SINGLESTEP) { + regs.rip--; + ptrace(PTRACE_SETREGSET, th->id, NT_PRSTATUS, &ivregs); + b->hits++; /* todo: consider whether this is firing too much */ + ret = b->user; + + if (b->stack != 0 && b->stack != regs.rsp) { + *restart = 1; + } else if (b->tid != 0 && b->tid != th->id) { + *restart = 1; + } else if (!b->enabled) { + *restart = 1; } } -- cgit v1.2.3 From 5bb0dfbdb6c0dc1f4a4f0e28393619469a1d4851 Mon Sep 17 00:00:00 2001 From: Malfurious Date: Sat, 7 Oct 2023 02:26:08 -0400 Subject: Tweak SCHEDULER_DELAY for use with installing breakpoints Signed-off-by: Malfurious --- debugger.c | 7 ++++++- misplays.c | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/debugger.c b/debugger.c index b646dc4..3435cf4 100644 --- a/debugger.c +++ b/debugger.c @@ -20,7 +20,7 @@ static const int PTRACE_OPTIONS = static const int PTRACE_CHILD_OPTIONS = PTRACE_OPTIONS | PTRACE_O_EXITKILL; //static const int STOP_ALL_ON_EVENT = 1; -static const useconds_t SCHEDULER_DELAY = 100000; +static const useconds_t SCHEDULER_DELAY = 10000; static const unsigned int BREAKPOINT_INSN = 0xcc; static struct process *new_process(pid_t id, int child) { @@ -224,6 +224,7 @@ static void continue_all_threads(struct process *proc) { static void resume_threads(struct process *proc) { struct list *threads = &proc->threads; + int once = 0; for (struct thread *th = threads->head; th != threads->end; th = th->next) { if (th->doing == PTRACE_SINGLESTEP) { @@ -236,6 +237,10 @@ static void resume_threads(struct process *proc) { for (struct thread *th = threads->head; th != threads->end; th = th->next) { if (th->doing && th->doing != PTRACE_SINGLESTEP) { + if (!once) { + usleep(SCHEDULER_DELAY); + once = 1; + } install_breakpoints(th); ptrace(th->doing, th->id, NULL, th->signal); th->stopped = 0; diff --git a/misplays.c b/misplays.c index c064f75..e30cc2c 100644 --- a/misplays.c +++ b/misplays.c @@ -27,7 +27,7 @@ static pid_t dofork(char **argv, struct console *cons) { } if (pid == 0) { - usleep(100000); + usleep(10000); console_configslave(cons); close_range(STDERR_FILENO+1, ~0U, CLOSE_RANGE_UNSHARE); //raise(SIGSTOP); // ptrace(PTRACE_TRACEME, 0, NULL, NULL); -- cgit v1.2.3 From 0991376bb2c94dc1e2dc85015de6b7e3767247b5 Mon Sep 17 00:00:00 2001 From: Malfurious Date: Sat, 7 Oct 2023 03:28:38 -0400 Subject: Add orig_rax to register display Signed-off-by: Malfurious --- misplays.c | 1 + 1 file changed, 1 insertion(+) diff --git a/misplays.c b/misplays.c index e30cc2c..6391d98 100644 --- a/misplays.c +++ b/misplays.c @@ -63,6 +63,7 @@ static void describe_states(struct thread *dbg, PANEL *pan) { static void dump_registers(struct thread *dbg, PANEL *pan) { struct user_regs_struct *regs = &dbg->state->regs; + pprintw(pan, "orig_rax = 0x%016llx\n", regs->orig_rax); pprintw(pan, "rax = 0x%016llx\n", regs->rax); pprintw(pan, "rbx = 0x%016llx\n", regs->rbx); pprintw(pan, "rcx = 0x%016llx\n", regs->rcx); -- cgit v1.2.3 From e68b37e4f8af8051ab3ac55b49a742a46b98081b Mon Sep 17 00:00:00 2001 From: Malfurious Date: Sat, 7 Oct 2023 03:32:24 -0400 Subject: Enable user creation of thread-specific breakpoints Signed-off-by: Malfurious --- misplays.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/misplays.c b/misplays.c index 6391d98..cda2abb 100644 --- a/misplays.c +++ b/misplays.c @@ -303,16 +303,21 @@ int main(int argc, char **argv) { char *t = cmd; int en = 1; + pid_t tid = 0; if (t[0] == '!') { en = -1; t++; } else if (t[0] == '#') { en = 0; t++; + } else if (t[0] == '@') { + tid = th->id; + t++; } unsigned long address = strtoul(t, NULL, 0); struct breakpoint *b = add_breakpoint(th->proc, address); b->enabled = en; + b->tid = tid; break; } } else { -- cgit v1.2.3 From 80a43bdbdeee87605fdbcdd79195edc98b98fd1d Mon Sep 17 00:00:00 2001 From: Malfurious Date: Mon, 9 Oct 2023 13:54:40 -0400 Subject: Detect out-of-band thread exec state changes to prevent deadlock Signed-off-by: Malfurious --- debugger.c | 53 +++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 41 insertions(+), 12 deletions(-) diff --git a/debugger.c b/debugger.c index 3435cf4..5981660 100644 --- a/debugger.c +++ b/debugger.c @@ -192,13 +192,37 @@ static void capture_state(struct process *proc) { } } +static char thread_state(struct thread *th) { + char statpath[32], stat[512]; + snprintf(statpath, sizeof(statpath), "/proc/%li/stat", (long)th->id); + + FILE *f = fopen(statpath, "r"); + if (f) { + fread(stat, 1, sizeof(stat), f); + fclose(f); + + char *c = strchr(stat, ' '); + c = strchr(c+1, ' ') + 1; + return *c; + } + + return 0; +} + static int wait_thread(struct thread *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 (!wait_thread(th)) {} + + char state; + do { + state = thread_state(th); + } while (state != 't' && state != 'D'); + + wait_thread(th); + //if (STOP_ALL_ON_EVENT) { // th->cont = 0; // th->shouldcont = 0; @@ -211,13 +235,20 @@ static void continue_all_threads(struct process *proc) { struct list *threads = &proc->threads; for (struct thread *th = threads->head; th != threads->end; th = th->next) { if (th->id > 0) { - ptrace(PTRACE_CONT, th->id, NULL, th->signal); - th->stopped = 0; - th->signal = 0; - th->donext = 0; - th->doing = PTRACE_CONT; - strcpy(th->status, "RUNNING"); - th->clearstates = 1; + char state; + do { + wait_thread(th); + + ptrace(PTRACE_CONT, th->id, NULL, th->signal); + th->stopped = 0; + th->signal = 0; + th->donext = 0; + th->doing = PTRACE_CONT; + strcpy(th->status, "RUNNING"); + th->clearstates = 1; + + state = thread_state(th); + } while (state == 't'); } } } @@ -435,12 +466,10 @@ void dbg_detach(struct process *proc) { /* ptrace requires that threads be stopped before calling PTRACE_DETACH. * In some cases, a blocked thread can possibly have a pending trap which * would crash the process if encountered after ptracing. We cycle an extra - * continue/interrupt to consume this trap event ourselves before detaching. - * Threads must actually get scheduled for this to be effective, which is - * the reason for the usleep. A better approach is welcome here. */ + * continue/interrupt to consume this trap event ourselves before + * detaching. */ interrupt_all_threads(proc); continue_all_threads(proc); - usleep(SCHEDULER_DELAY); interrupt_all_threads(proc); /* Supplement to PTRACE_O_EXITKILL */ -- cgit v1.2.3 From ee936125622325a4b33bafe336b44e44a24ce1aa Mon Sep 17 00:00:00 2001 From: Malfurious Date: Mon, 9 Oct 2023 16:08:34 -0400 Subject: Handle PTRACE_EVENT_EXIT to capture a final state snapshot Also, it is now possible for interrupt_all_threads to fail to stop threads in uninterruptable sleep (eg: the main thread during execve). This may happen in more general cases as well, but it is now common enough in that case to worry about. Signed-off-by: Malfurious --- debugger.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/debugger.c b/debugger.c index 5981660..18f91e1 100644 --- a/debugger.c +++ b/debugger.c @@ -16,6 +16,7 @@ static const int PTRACE_OPTIONS = PTRACE_O_TRACECLONE | PTRACE_O_TRACEEXEC | + PTRACE_O_TRACEEXIT | PTRACE_O_TRACESYSGOOD; static const int PTRACE_CHILD_OPTIONS = PTRACE_OPTIONS | PTRACE_O_EXITKILL; @@ -258,7 +259,7 @@ static void resume_threads(struct process *proc) { int once = 0; for (struct thread *th = threads->head; th != threads->end; th = th->next) { - if (th->doing == PTRACE_SINGLESTEP) { + if (th->stopped && th->doing == PTRACE_SINGLESTEP) { ptrace(PTRACE_SINGLESTEP, th->id, NULL, th->signal); th->stopped = 0; th->signal = 0; @@ -267,7 +268,7 @@ static void resume_threads(struct process *proc) { } for (struct thread *th = threads->head; th != threads->end; th = th->next) { - if (th->doing && th->doing != PTRACE_SINGLESTEP) { + if (th->stopped && th->doing && th->doing != PTRACE_SINGLESTEP) { if (!once) { usleep(SCHEDULER_DELAY); once = 1; @@ -349,6 +350,15 @@ static int wait_thread(struct thread *th) { th->state = NULL; return 1; + case SIGTRAP | (PTRACE_EVENT_EXIT << 8): + th->stopped = 1; + th->signal = 0; + th->doing = PTRACE_CONT; + th->donext = 0; + strcpy(th->status, "EXITING"); + th->state = NULL; + return 1; + case SIGTRAP | (PTRACE_EVENT_STOP << 8): th->stopped = 1; th->signal = 0; -- cgit v1.2.3 From 574d5a2c4f07bed91d9682e4f48e655e88e37498 Mon Sep 17 00:00:00 2001 From: Malfurious Date: Mon, 9 Oct 2023 16:31:56 -0400 Subject: Implement support for PTRACE_EVENT_FORK and ui Signed-off-by: Malfurious --- debugger.c | 18 ++++++++++ helpers.c | 1 + misplays.c | 115 +++++++++++++++++++++++++++++++------------------------------ 3 files changed, 78 insertions(+), 56 deletions(-) diff --git a/debugger.c b/debugger.c index 18f91e1..9a4d142 100644 --- a/debugger.c +++ b/debugger.c @@ -15,6 +15,7 @@ static const int PTRACE_OPTIONS = PTRACE_O_TRACECLONE | + PTRACE_O_TRACEFORK | PTRACE_O_TRACEEXEC | PTRACE_O_TRACEEXIT | PTRACE_O_TRACESYSGOOD; @@ -311,6 +312,7 @@ static int wait_thread(struct thread *th) { strcpy(th->status, "TERMINATED"); return 1; } else if (WIFSTOPPED(status)) { + struct process *eventproc; struct thread *eventth; unsigned long eventmsg; ptrace(PTRACE_GETEVENTMSG, th->id, NULL, &eventmsg); @@ -331,6 +333,22 @@ static int wait_thread(struct thread *th) { th->state = NULL; return 1; + case SIGTRAP | (PTRACE_EVENT_FORK << 8): + eventproc = new_process(eventmsg, th->proc->child); + eventth = new_thread(eventproc, eventmsg); + list_insert(eventproc->threads.end, eventth); + list_insert(th->proc->next, eventproc); + //while (!wait_thread(eventth)) {} + dbg_sync(eventproc); + + th->stopped = 1; + th->signal = 0; + th->doing = 0; + th->donext = 0; + strcpy(th->status, "FORK"); + th->state = NULL; + return 1; + case SIGTRAP | (PTRACE_EVENT_EXEC << 8): eventth = thread_by_id(th->proc, eventmsg); if (eventth->id != th->id) { diff --git a/helpers.c b/helpers.c index 163cd2e..9973093 100644 --- a/helpers.c +++ b/helpers.c @@ -33,6 +33,7 @@ void cursinit(void) { init_pair(1, COLOR_GREEN, -1); init_pair(2, COLOR_CYAN, -1); init_pair(3, COLOR_RED, -1); + init_pair(4, COLOR_YELLOW, -1); } void cursupdate(void) { diff --git a/misplays.c b/misplays.c index cda2abb..1ccf3d3 100644 --- a/misplays.c +++ b/misplays.c @@ -160,9 +160,17 @@ static void layout(struct list *processes, struct thread *th) { reset_panel(right, LINES-2, COLS-w, 1, w); clear(); - attron(COLOR_PAIR(1)); for (struct process *proc = processes->head; proc != processes->end; proc = proc->next) { + if (th->proc == proc) { + attron(COLOR_PAIR(1)); + printw("{ "); + } else { + attron(COLOR_PAIR(4)); + printw("{ "); + attroff(COLOR_PAIR(4)); + } + struct list *threads = &proc->threads; for (struct thread *t = threads->head; t != threads->end; t = t->next) { if (t == th) { @@ -172,11 +180,22 @@ static void layout(struct list *processes, struct thread *th) { if (t == th) { printw("**"); } - printw(" "); + + if (t->next != threads->end) { + printw(" "); + } } - } - attroff(COLOR_PAIR(1)); + + if (th->proc == proc) { + printw(" } "); + attroff(COLOR_PAIR(1)); + } else { + attron(COLOR_PAIR(4)); + printw(" } "); + attroff(COLOR_PAIR(4)); + } + } } int main(int argc, char **argv) { @@ -276,18 +295,41 @@ int main(int argc, char **argv) { break; case 't': proc = th->proc; - do { - th = th->next; - } while (th == proc->threads.end); + th = th->next; + if (th == proc->threads.end) { + do { + proc = proc->next; + } while (proc == processes.end); + th = proc->threads.head; + } layout(&processes, th); break; case 'T': proc = th->proc; - do { - th = th->prev; - } while (th == proc->threads.end); + th = th->prev; + if (th == proc->threads.end) { + do { + proc = proc->prev; + } while (proc == processes.end); + th = proc->threads.tail; + } layout(&processes, th); - break; /* todo: next/prev process bindings */ + break; + case 'd': + proc = th->proc; + if (proc->child) { + break; + } + if (proc->prev == proc->next) { + break; + } + struct process *del = proc; + do { + proc = proc->next; + } while (proc == processes.end); + th = proc->threads.head; + dbg_detach(del); + break; case ':': mvprintw(LINES-1, 0, ":"); curs_set(TRUE); @@ -338,51 +380,12 @@ int main(int argc, char **argv) { } } - dbg_detach(th->proc); /* todo: detach all procs */ + for (struct process *p = processes.head; p != processes.end; p = p->next) { + struct process *del = p; + p = p->prev; + dbg_detach(del); + } + endwin(); return EXIT_SUCCESS; } - - - - - - - - - - - - - - -//#include -//#include -//#include -//#include -//#include -//#include - -// //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; -- cgit v1.2.3 From bda13f10b17baf1c2543a500f09eaeccc8c2d876 Mon Sep 17 00:00:00 2001 From: Malfurious Date: Tue, 10 Oct 2023 11:54:32 -0400 Subject: Allow termination and exec events to propagate immediately Signed-off-by: Malfurious --- debugger.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/debugger.c b/debugger.c index 9a4d142..fdf74f9 100644 --- a/debugger.c +++ b/debugger.c @@ -286,8 +286,6 @@ static void resume_threads(struct process *proc) { static int wait_thread(struct thread *th) { if (th->id <= 0) { return -1; - } else if (th->stopped) { - return 1; } int status; @@ -536,7 +534,7 @@ void dbg_sync(struct process *proc) { struct list *threads = &proc->threads; for (struct thread *th = threads->head; th != threads->end; th = th->next) { if (th->id > 0) { - if (!th->stopped && wait_thread(th)) { + if (/*!th->stopped &&*/ wait_thread(th)) { acted = th; } else if (th->stopped && th->doing) { acted = th; -- cgit v1.2.3 From b86dd03abe59b6b410de4da3e44f62e62599c5ce Mon Sep 17 00:00:00 2001 From: Malfurious Date: Tue, 10 Oct 2023 15:21:08 -0400 Subject: Don't spin waiting to interrupt zombie process Targets can get into this state, for example, when receiving a killing signal. Signed-off-by: Malfurious --- debugger.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debugger.c b/debugger.c index fdf74f9..bacd09c 100644 --- a/debugger.c +++ b/debugger.c @@ -221,7 +221,7 @@ static void interrupt_all_threads(struct process *proc) { char state; do { state = thread_state(th); - } while (state != 't' && state != 'D'); + } while (state != 't' && state != 'D' && state != 'Z'); wait_thread(th); -- cgit v1.2.3