summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt3
-rw-r--r--arch/arm-singlestep.c154
-rw-r--r--arch/arm-singlestep.h5
-rw-r--r--architecture.h23
-rw-r--r--console.c9
-rw-r--r--console.h2
-rw-r--r--debugger.c126
-rw-r--r--debugger.h5
-rw-r--r--misplays.c54
9 files changed, 319 insertions, 62 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index f35b859..cc098fd 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -13,7 +13,10 @@ add_compile_definitions(
_GNU_SOURCE
)
+include_directories(${CMAKE_SOURCE_DIR})
+
add_executable(${PROJECT_NAME}
+ arch/arm-singlestep.c
architecture.c
console.c
debugger.c
diff --git a/arch/arm-singlestep.c b/arch/arm-singlestep.c
new file mode 100644
index 0000000..e98feb3
--- /dev/null
+++ b/arch/arm-singlestep.c
@@ -0,0 +1,154 @@
+#include "arm-singlestep.h"
+
+#ifdef ARCH_AARCH64
+
+static void break_imm(unsigned long address, struct thread *th) {
+ struct breakpoint *b = add_breakpoint(th->proc, address, 1);
+ b->user = 0;
+ b->tid = th->id;
+ b->enabled = -1;
+}
+
+static void break_reg(int reg, struct thread *th) {
+ unsigned long address = 0;
+ unsigned int *regs = th->state->regs.arm32.regs;
+
+ switch (reg) {
+ case ARM_REG_R0: address = regs[0]; break;
+ case ARM_REG_R1: address = regs[1]; break;
+ case ARM_REG_R2: address = regs[2]; break;
+ case ARM_REG_R3: address = regs[3]; break;
+ case ARM_REG_R4: address = regs[4]; break;
+ case ARM_REG_R5: address = regs[5]; break;
+ case ARM_REG_R6: address = regs[6]; break;
+ case ARM_REG_R7: address = regs[7]; break;
+ case ARM_REG_R8: address = regs[8]; break;
+ case ARM_REG_R9: address = regs[9]; break;
+ case ARM_REG_R10: address = regs[10]; break;
+ case ARM_REG_R11: address = regs[11]; break;
+ case ARM_REG_R12: address = regs[12]; break;
+ case ARM_REG_R13: address = regs[13]; break;
+ case ARM_REG_R14: address = regs[14]; break;
+ case ARM_REG_R15: address = regs[15]; break;
+ default: /* todo thread error */ break;
+ }
+
+ break_imm(address, th);
+}
+
+static int is_pc(csh handle, cs_insn *insn) {
+ cs_regs read, write;
+ uint8_t read_size, write_size;
+ cs_regs_access(handle, insn, read, &read_size, write, &write_size);
+
+ for (uint8_t i = 0; i < write_size; i++) {
+ if (write[i] == ARM_REG_PC) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static uint8_t pc_op(cs_arm_op *ops, uint8_t ops_size) {
+ uint8_t i;
+ for (i = 0; i < ops_size; i++) {
+ if (ops[i].type == ARM_OP_REG && ops[i].reg == ARM_REG_PC) {
+ break;
+ }
+ }
+ return i;
+}
+
+int arm_singlestep(struct thread *th) {
+ struct archinfo archinfo;
+ struct iovec regs = { &th->state->regs, th->state->regsize };
+ architecture_info(&archinfo, &regs);
+
+ int ret = 0;
+ csh handle;
+
+ if (cs_open(archinfo.cs_arch, archinfo.cs_mode, &handle) != CS_ERR_OK) {
+ /* todo thread error */
+ return -1;
+ }
+
+ cs_option(handle, CS_OPT_DETAIL, CS_OPT_ON);
+
+ uint64_t address = archinfo.progmctr;
+ size_t codez = 128;
+ const uint8_t *code = deref(th, address, codez);
+ cs_insn *insn = cs_malloc(handle);
+
+ if (cs_disasm_iter(handle, &code, &codez, &address, insn)) {
+ if (is_pc(handle, insn)) {
+ cs_arm_op *ops = insn->detail->arm.operands;
+ uint8_t ops_size = insn->detail->arm.op_count;
+ uint8_t pci;
+
+ switch (insn->id) {
+ case ARM_INS_B:
+ case ARM_INS_BL:
+ case ARM_INS_BX:
+ case ARM_INS_BLX:
+ if (ops_size == 1) {
+ switch (ops[0].type) {
+ case ARM_OP_REG: break_reg(ops[0].reg, th); break;
+ case ARM_OP_IMM: break_imm(ops[0].imm, th); break;
+ default: ret = -1; /* todo thread error */ break;
+ }
+ } else {
+ ret = -1;
+ /* todo thread error */
+ }
+ break;
+
+ case ARM_INS_POP:
+ if ((pci = pc_op(ops, ops_size)) < ops_size) {
+ unsigned long saddr = archinfo.stackptr + (pci * archinfo.wordsize);
+ unsigned long *sval = deref(th, saddr, archinfo.wordsize);
+ break_imm(*sval, th);
+ } else {
+ ret = -1;
+ /* todo thread error */
+ }
+ break;
+
+ case ARM_INS_MOV:
+ if (pc_op(ops, ops_size) == 0) {
+ if (ops_size == 2) {
+ switch (ops[1].type) {
+ case ARM_OP_REG: break_reg(ops[1].reg, th); break;
+ case ARM_OP_IMM: break_imm(ops[1].imm, th); break;
+ default: ret = -1; /* todo thread error */ break;
+ }
+ } else {
+ ret = -1;
+ /* tr */
+ }
+ } else {
+ ret = -1;
+ /* tr */
+ }
+ break;
+
+ default:
+ ret = -1;
+ /* todo thread error */
+ break;
+ }
+ }
+
+ /* default case - next sequential instruction */
+ break_imm(address, th);
+ } else {
+ ret = -1;
+ /* todo thread error */
+ }
+
+ cs_free(insn, 1);
+ cs_close(&handle);
+ return ret;
+}
+
+#endif
diff --git a/arch/arm-singlestep.h b/arch/arm-singlestep.h
new file mode 100644
index 0000000..263b188
--- /dev/null
+++ b/arch/arm-singlestep.h
@@ -0,0 +1,5 @@
+#pragma once
+
+#include "debugger.h"
+
+extern int arm_singlestep(struct thread *th);
diff --git a/architecture.h b/architecture.h
index af98ce3..a433cfd 100644
--- a/architecture.h
+++ b/architecture.h
@@ -55,7 +55,7 @@ typedef union {
#define CAPSTONE_CALL_32 X86_INS_CALL
#define WORDSIZE_32 4
-#elif defined(__aarch64__) || defined(_M_ARM64)
+#elif defined(__aarch64__) || defined(_M_ARM64) || defined(__arm__)
typedef union {
struct user_regs_64 {
@@ -64,10 +64,13 @@ typedef union {
} arm64;
struct user_regs_32 {
- unsigned int x;
+ unsigned int regs[18];
+ //unsigned int regs[14];
+ //unsigned int sp, pc, p0, vr;
} arm32;
} user_regs_t;
+/* todo - rename this arch constant */
#define ARCH_AARCH64
#define PROGMCTR_64 arm64.pc
@@ -80,14 +83,14 @@ typedef union {
#define CAPSTONE_CALL_64 ARM64_INS_BL
#define WORDSIZE_64 8
-#define PROGMCTR_32 arm32.x
-#define STACKPTR_32 arm32.x
-#define BREAKPOINT_INSN_32 0
-#define BREAKPOINT_MASK_32 0
-#define BREAKPOINT_ADJS_32 0
-#define CAPSTONE_ARCH_32 0
-#define CAPSTONE_MODE_32 0
-#define CAPSTONE_CALL_32 0
+#define PROGMCTR_32 arm32.regs[15]
+#define STACKPTR_32 arm32.regs[13]
+#define BREAKPOINT_INSN_32 0xe7f001f0ul
+#define BREAKPOINT_MASK_32 0xfffffffful
+#define BREAKPOINT_ADJS_32 0x0
+#define CAPSTONE_ARCH_32 CS_ARCH_ARM
+#define CAPSTONE_MODE_32 CS_MODE_ARM
+#define CAPSTONE_CALL_32 ARM_INS_BL
#define WORDSIZE_32 4
#else
diff --git a/console.c b/console.c
index 6c1ae53..4ab263d 100644
--- a/console.c
+++ b/console.c
@@ -55,11 +55,12 @@ void console_leave(struct console *cons, PANEL *pan) {
cbreak();
}
-void console_update(struct console *cons, PANEL *pan) {
- ssize_t nb;
+int console_update(struct console *cons, PANEL *pan) {
char c;
+ int did_read = 0;
- while ((nb = read(cons->master, &c, 1)) > 0) {
+ while (read(cons->master, &c, 1) > 0) {
+ did_read = 1;
if (!cons->isesc) {
if (c == 0x1b) {
cons->isesc = 1;
@@ -72,6 +73,8 @@ void console_update(struct console *cons, PANEL *pan) {
}
}
}
+
+ return did_read;
}
void console_input(struct console *cons, int ch) {
diff --git a/console.h b/console.h
index 054b768..23a3eb4 100644
--- a/console.h
+++ b/console.h
@@ -11,7 +11,7 @@ extern int console_init(struct console *cons);
extern int console_deinit(struct console *cons);
extern void console_enter(struct console *cons, PANEL *pan);
extern void console_leave(struct console *cons, PANEL *pan);
-extern void console_update(struct console *cons, PANEL *pan);
+extern int console_update(struct console *cons, PANEL *pan);
extern void console_input(struct console *cons, int ch);
extern int console_configslave(struct console *cons);
diff --git a/debugger.c b/debugger.c
index 401f6dd..c3c9fa1 100644
--- a/debugger.c
+++ b/debugger.c
@@ -10,6 +10,7 @@
#include <capstone/capstone.h>
+#include "arch/arm-singlestep.h"
#include "debugger.h"
#include "helpers.h"
@@ -67,14 +68,14 @@ static void free_breakpoints(struct process *proc) {
}
}
-static void install_breakpoints(struct thread *th) {
+static void install_breakpoints(struct thread *th, int step) {
struct archinfo archinfo;
struct iovec regs = { &th->state->regs, th->state->regsize };
architecture_info(&archinfo, &regs);
struct list *breaks = &th->proc->breakpoints;
for (struct breakpoint *b = breaks->head; b != breaks->end; b = b->next) {
- if (!b->installed) {
+ if (!b->installed && (b->step == step)) {
unsigned long word;
word = ptrace(PTRACE_PEEKTEXT, th->id, b->address, NULL);
b->text = word;
@@ -95,7 +96,12 @@ static void uninstall_breakpoints(struct thread *th) {
b->installed = 0;
}
- if (b->previously_installed && b->enabled < 0) {
+ if (b->step) {
+ struct breakpoint *del = b;
+ b = b->next;
+ list_remove(del);
+ free(del);
+ } else if (b->enabled < 0 && b->previously_installed) {
struct thread *t = NULL;
if (b->tid == 0 || ((t = thread_by_id(th->proc, b->tid)) && !t->doing)) {
struct breakpoint *del = b;
@@ -107,9 +113,19 @@ static void uninstall_breakpoints(struct thread *th) {
}
}
+static void clean_fork_breakpoints(struct thread *th, const struct process *parent) {
+ const struct list *breaks = &parent->breakpoints;
+ for (struct breakpoint *b = breaks->tail; b != breaks->end; b = b->prev) {
+ if (b->installed) {
+ ptrace(PTRACE_POKETEXT, th->id, b->address, b->text);
+ }
+ }
+}
+
static int detect_breakpoint(struct thread *th, int *restart) {
- int ret = 0;
- *restart = 0;
+ int is_bp = 0; /* at least 1 effective breakpoint at this PC */
+ int is_user = 0; /* at least 1 user-defined bp at this PC */
+ *restart = 0; /* at least 1 bp which must stop at this PC */
/* Hack: Need to manually fetch registers here, since capture_state() has
* not yet run for this stop. It is not guaranteed that we even want to
@@ -122,8 +138,27 @@ static int detect_breakpoint(struct thread *th, int *restart) {
architecture_info(&archinfo, &ivregs);
unsigned long breakpt_address = archinfo.progmctr - archinfo.bp_adjust;
- struct breakpoint *b = get_breakpoint(th->proc, breakpt_address);
- if (b && b->installed && th->doing != PTRACE_SINGLESTEP) {
+
+ struct list *breaks = &th->proc->breakpoints;
+ for (struct breakpoint *b = breaks->head; b != breaks->end; b = b->next) {
+ if (b->address == breakpt_address) {
+ if (b->installed /* && th->doing != PTRACE_SINGLESTEP*/) {
+ /* todo - issues with singlestep? */
+ /* todo - put conditional guard around `hits++` */
+ is_bp = 1;
+ b->hits++;
+ is_user |= b->user;
+
+ if ((b->stack == 0 || b->stack == archinfo.stackptr)
+ && (b->tid == 0 || b->tid == th->id)
+ && (b->enabled)) {
+ *restart = 1;
+ }
+ }
+ }
+ }
+
+ if (is_bp) {
/* restore actual program counter to breakpoint address */
if (ivregs.iov_len == sizeof(struct user_regs_32)) {
regs.PROGMCTR_32 = breakpt_address;
@@ -131,20 +166,10 @@ static int detect_breakpoint(struct thread *th, int *restart) {
regs.PROGMCTR_64 = breakpt_address;
}
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 != archinfo.stackptr) {
- *restart = 1;
- } else if (b->tid != 0 && b->tid != th->id) {
- *restart = 1;
- } else if (!b->enabled) {
- *restart = 1;
- }
}
- return ret;
+ *restart = (is_bp ? !*restart : 0);
+ return is_user;
}
static void free_states(struct thread *th) {
@@ -272,13 +297,34 @@ static void continue_all_threads(struct process *proc) {
}
}
+static void compute_step_breakpoints(struct thread *th) {
+#ifdef ARCH_AARCH64
+ if (th->state->regsize == sizeof(struct user_regs_32)) {
+ arm_singlestep(th);
+ }
+#endif
+}
+
+static void do_singlestep(struct thread *th) {
+#ifdef ARCH_AARCH64
+ if (th->state->regsize == sizeof(struct user_regs_32)) {
+ ptrace(PTRACE_CONT, th->id, NULL, th->signal);
+ return;
+ }
+#endif
+
+ ptrace(PTRACE_SINGLESTEP, th->id, NULL, th->signal);
+}
+
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->stopped && th->doing == PTRACE_SINGLESTEP) {
- ptrace(PTRACE_SINGLESTEP, th->id, NULL, th->signal);
+ compute_step_breakpoints(th);
+ install_breakpoints(th, 1);
+ do_singlestep(th);
th->stopped = 0;
th->signal = 0;
strcpy(th->status, "RUNNING");
@@ -291,7 +337,7 @@ static void resume_threads(struct process *proc) {
usleep(SCHEDULER_DELAY);
once = 1;
}
- install_breakpoints(th);
+ install_breakpoints(th, 0);
ptrace(th->doing, th->id, NULL, th->signal);
th->stopped = 0;
th->signal = 0;
@@ -356,6 +402,8 @@ static int wait_thread(struct thread *th) {
//while (!wait_thread(eventth)) {}
dbg_sync(eventproc);
+ clean_fork_breakpoints(eventth, th->proc);
+
th->stopped = 1;
th->signal = 0;
th->doing = 0;
@@ -375,6 +423,8 @@ static int wait_thread(struct thread *th) {
strcpy(eventth->status, "EXITED");
}
+ free_breakpoints(th->proc);
+
th->stopped = 1;
th->signal = 0;
th->doing = 0;
@@ -419,8 +469,10 @@ static int wait_thread(struct thread *th) {
strcpy(th->status, (bp ? "BREAKPOINT" : "STEP"));
if (restart) {
- th->donext = th->doing;
- th->doing = (th->doing ? PTRACE_SINGLESTEP : 0);
+ if (th->doing != PTRACE_SINGLESTEP) {
+ th->donext = th->doing;
+ th->doing = (th->doing ? PTRACE_SINGLESTEP : 0);
+ }
} else {
th->doing = th->donext;
th->donext = 0;
@@ -446,7 +498,7 @@ static int wait_thread(struct thread *th) {
return -1;
}
-struct breakpoint *add_breakpoint(struct process *proc, unsigned long address) {
+struct breakpoint *add_breakpoint(struct process *proc, unsigned long address, int step) {
struct breakpoint *b = xmalloc(sizeof(*b));
b->address = address;
b->text = 0;
@@ -454,10 +506,24 @@ struct breakpoint *add_breakpoint(struct process *proc, unsigned long address) {
b->previously_installed = 0;
b->hits = 0;
b->user = 1;
+ b->step = step;
b->stack = 0;
b->tid = 0;
b->enabled = 1;
- list_insert(proc->breakpoints.end, b);
+
+ if (step) {
+ struct breakpoint *p;
+ struct list *breaks = &proc->breakpoints;
+ for (p = breaks->head; p != breaks->end; p = p->next) {
+ if (p->step == 0) {
+ break;
+ }
+ }
+ list_insert(p, b);
+ } else {
+ list_insert(proc->breakpoints.end, b);
+ }
+
return b;
}
@@ -532,7 +598,9 @@ void dbg_detach(struct process *proc) {
}
free_breakpoints(proc);
- list_remove(proc);
+ if (proc->next) { /* workaround: its invalid to list_remove() if dbg_attach fails */
+ list_remove(proc);
+ }
free(proc);
}
@@ -546,7 +614,7 @@ int dbg_free(struct thread *th) {
return -1;
}
-void dbg_sync(struct process *proc) {
+int dbg_sync(struct process *proc) {
struct thread *acted = NULL;
struct list *threads = &proc->threads;
@@ -566,6 +634,8 @@ void dbg_sync(struct process *proc) {
capture_state(proc);
resume_threads(proc);
}
+
+ return acted != NULL;
}
int dbg_intr(struct thread *th) {
@@ -652,7 +722,7 @@ int dbg_stepover(struct thread *th) {
cs_disasm_iter(handle, &code, &size, &address, insn);
if (insn->id == archinfo.cs_call) {
- struct breakpoint *b = add_breakpoint(th->proc, address);
+ struct breakpoint *b = add_breakpoint(th->proc, address, 0);
b->user = 0;
b->stack = archinfo.stackptr;
b->tid = th->id;
diff --git a/debugger.h b/debugger.h
index b26adf8..093e8dd 100644
--- a/debugger.h
+++ b/debugger.h
@@ -14,6 +14,7 @@ struct breakpoint {
int previously_installed;
int hits;
int user;
+ int step;
unsigned long stack;
pid_t tid;
@@ -60,14 +61,14 @@ struct thread {
char status[128];
};
-extern struct breakpoint*add_breakpoint(struct process*proc,unsigned long address);
+extern struct breakpoint*add_breakpoint(struct process*proc,unsigned long address, int step);
extern struct breakpoint*get_breakpoint(struct process*proc,unsigned long address);
extern struct process *dbg_attach(pid_t pid, int child);
extern void dbg_detach(struct process *proc);
extern int dbg_free(struct thread *th);
-extern void dbg_sync(struct process *proc);
+extern int dbg_sync(struct process *proc);
extern int dbg_intr(struct thread *th);
extern int dbg_cont(struct thread *th);
diff --git a/misplays.c b/misplays.c
index a2535c3..25182e3 100644
--- a/misplays.c
+++ b/misplays.c
@@ -193,10 +193,12 @@ static void info_update(struct thread *th, PANEL *pan) {
disasm(th, pan);
}
-static void wait_all_threads(struct list *processes) {
+static int wait_all_threads(struct list *processes) {
+ int action = 0;
for (struct process *proc = processes->head; proc != processes->end; proc = proc->next) {
- dbg_sync(proc);
+ action |= dbg_sync(proc);
}
+ return action;
}
static void layout(struct list *processes, struct thread *th) {
@@ -249,6 +251,7 @@ int main(int argc, char **argv) {
return EXIT_FAILURE;
}
+ printf("Pausing for optional gdb attach. Press [Enter] to continue...\n");
getchar();
struct console cons = {0};
@@ -286,14 +289,11 @@ int main(int argc, char **argv) {
int quit = 0;
int mode = 0;
+ int dirty = 1;
+ int pressed = 0;
while (!quit) {
- wait_all_threads(&processes);
- layout(&processes, th);
- console_update(&cons, right);
- info_update(th, left);
- cursupdate();
-
int ch = getch();
+ pressed = 1;
if (mode == 0) {
switch (ch) {
@@ -391,21 +391,28 @@ int main(int argc, char **argv) {
char *t = cmd;
int en = 1;
pid_t tid = 0;
- if (t[0] == '!') {
- en = -1;
- t++;
- } else if (t[0] == '#') {
- en = 0;
- t++;
- } else if (t[0] == '@') {
- tid = th->id;
- t++;
+ while (1) {
+ if (t[0] == '!') {
+ en = -1;
+ t++;
+ } else if (t[0] == '#') {
+ en = 0;
+ t++;
+ } else if (t[0] == '@') {
+ tid = th->id;
+ t++;
+ } else {
+ break;
+ }
}
unsigned long address = strtoul(t, NULL, 0);
- struct breakpoint *b = add_breakpoint(th->proc, address);
+ struct breakpoint *b = add_breakpoint(th->proc, address, 0);
b->enabled = en;
b->tid = tid;
break;
+ case ERR:
+ pressed = 0;
+ break;
}
} else {
switch (ch) {
@@ -417,12 +424,23 @@ int main(int argc, char **argv) {
console_leave(&cons, right);
break;
case ERR:
+ pressed = 0;
break;
default:
console_input(&cons, ch);
break;
}
}
+
+ dirty |= wait_all_threads(&processes);
+ dirty |= console_update(&cons, right);
+
+ if (dirty || pressed) {
+ layout(&processes, th);
+ info_update(th, left);
+ cursupdate();
+ dirty = 0;
+ }
}
for (struct process *p = processes.head; p != processes.end; p = p->next) {