diff options
author | Matt Hunter <m@lfurio.us> | 2025-09-07 06:43:22 -0400 |
---|---|---|
committer | Matt Hunter <m@lfurio.us> | 2025-09-07 06:43:22 -0400 |
commit | 1001499baec9e8d6fb25c641b2a0577ccd419d6f (patch) | |
tree | 91bd8d29e34b2780a57e85623988be40babe2c85 | |
parent | 3b03d95a22a99df7d84647179a86f8c7f534868a (diff) | |
parent | b2d6f5a4c75e8f68cb27edad38455375b500323b (diff) | |
download | misplays-1001499baec9e8d6fb25c641b2a0577ccd419d6f.tar.gz misplays-1001499baec9e8d6fb25c641b2a0577ccd419d6f.zip |
Merge branch 'arm32'
Add initial 32-bit ARM support and additionally build out internal
breakpoint design to allow the use of single-step oriented breakpoints.
* arm32:
Always prune step breakpoints when uninstalling from memory
Update detect_breakpoint() to better handle single stepping
Add architecture-specific single step support
Add 32-bit ARM architecture params
-rw-r--r-- | CMakeLists.txt | 3 | ||||
-rw-r--r-- | arch/arm-singlestep.c | 154 | ||||
-rw-r--r-- | arch/arm-singlestep.h | 5 | ||||
-rw-r--r-- | architecture.h | 23 | ||||
-rw-r--r-- | debugger.c | 105 | ||||
-rw-r--r-- | debugger.h | 3 | ||||
-rw-r--r-- | misplays.c | 2 |
7 files changed, 257 insertions, 38 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, ®s); + + 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 @@ -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, ®s); 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; @@ -108,8 +114,9 @@ static void uninstall_breakpoints(struct thread *th) { } 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 +129,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 +157,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 +288,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 +328,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; @@ -419,8 +456,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 +485,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 +493,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; } @@ -656,7 +709,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; @@ -14,6 +14,7 @@ struct breakpoint { int previously_installed; int hits; int user; + int step; unsigned long stack; pid_t tid; @@ -60,7 +61,7 @@ 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); @@ -402,7 +402,7 @@ int main(int argc, char **argv) { t++; } 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; |