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 ++++++++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 565 insertions(+), 179 deletions(-) (limited to 'debugger.c') 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); +//} -- 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 +++++++++++++++++++++++++------------------------------------ 1 file changed, 191 insertions(+), 277 deletions(-) (limited to 'debugger.c') 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); -//} -- 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(-) (limited to 'debugger.c') 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(-) (limited to 'debugger.c') 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(-) (limited to 'debugger.c') 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 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 +++++++++++++++++++ 1 file changed, 19 insertions(+) (limited to 'debugger.c') 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) { -- 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 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'debugger.c') 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) { -- 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(+) (limited to 'debugger.c') 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(+) (limited to 'debugger.c') 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 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 +++++++++++++++++++++++++++++++------------------------------ 1 file changed, 300 insertions(+), 292 deletions(-) (limited to 'debugger.c') 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; -- 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 +++++++++++++++++++++++++++++-------------------------------- 1 file changed, 199 insertions(+), 213 deletions(-) (limited to 'debugger.c') 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) { -- 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(-) (limited to 'debugger.c') 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(-) (limited to 'debugger.c') 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 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'debugger.c') 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; -- 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(-) (limited to 'debugger.c') 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(-) (limited to 'debugger.c') 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 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'debugger.c') 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) { -- 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(-) (limited to 'debugger.c') 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(-) (limited to 'debugger.c') 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