summaryrefslogtreecommitdiffstats
path: root/debugger.c
diff options
context:
space:
mode:
Diffstat (limited to 'debugger.c')
-rw-r--r--debugger.c468
1 files changed, 191 insertions, 277 deletions
diff --git a/debugger.c b/debugger.c
index 5fc3978..956b065 100644
--- a/debugger.c
+++ b/debugger.c
@@ -1,39 +1,41 @@
#include <dirent.h>
-#include <errno.h>
-#include <stdio.h>
+#include <signal.h>
#include <stdlib.h>
-#include <string.h>
#include <sys/ptrace.h>
#include <sys/uio.h>
#include <sys/wait.h>
#include <capstone/capstone.h>
-/* #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, &regs);
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, &regs);
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);
-//}