summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatt Hunter <m@lfurio.us>2025-08-13 01:04:57 -0400
committerMatt Hunter <m@lfurio.us>2025-09-07 06:41:16 -0400
commit4ea8ea650a1d81cf6362e1485d2fdce2617d8d8e (patch)
tree65cddd4d36a4264141f6c8dca1a8a9e91076cc17
parentf9c7b14383a99ecc0a1e8266467804647acfaa3e (diff)
downloadmisplays-4ea8ea650a1d81cf6362e1485d2fdce2617d8d8e.tar.gz
misplays-4ea8ea650a1d81cf6362e1485d2fdce2617d8d8e.zip
Add architecture-specific single step support
ARM 32-bit is the first platform added to misplays which lacks underlying hardware support for single step traps - so the kernel does not implement PTRACE_SINGLESTEP in this case. We will work around this in a similar way as gdb does and how the kernel used to do it until 2011. arm_singlestep() implements logic which disassembles the program's current instruction and analyzes it to determine all possible next locations - eg: the next instruction in memory, or the jump target of a branch instruction, etc. This logic is dynamically dispatched by the debugger core if an ARM build is running in 32-bit mode. arm_singlestep() uses breakpoints to stop execution at it's computed next locations. However, misplays is currently very careful about controling the use of breakpoints in order to avoid issues with thread single steps - so a new flag (called "step") is added to breakpoints to enable the debugger to selectively install this subset of breakpoints for each thread's single step action, and more or less keep treating thread free-run as normal. install_breakpoints() is updated to take a "step" parameter to control which set of breakpoints is installed at any given time. resume_threads() is updated to perform this new single step dynamic dispatch, and manage the installation of step breakpoints. add_breakpoint() is also given a "step" parameter. This initializes the flag for the new breakpoint, but crucially is used to sort the new breakpoint into the process breakpoint list. Since step breakpoints will always be installed first, prioritize them in the list so that uninstall_breakpoints() doesn't corrupt memory when it runs the list backward to remove them. Signed-off-by: Matt Hunter <m@lfurio.us>
-rw-r--r--CMakeLists.txt3
-rw-r--r--arch/arm-singlestep.c154
-rw-r--r--arch/arm-singlestep.h5
-rw-r--r--debugger.c50
-rw-r--r--debugger.h3
-rw-r--r--misplays.c2
6 files changed, 208 insertions, 9 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/debugger.c b/debugger.c
index 2bbf866..06447c3 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;
@@ -272,13 +273,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 +313,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;
@@ -446,7 +468,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 +476,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 +692,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 6a3fba7..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,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);
diff --git a/misplays.c b/misplays.c
index 6c9a189..20da6bb 100644
--- a/misplays.c
+++ b/misplays.c
@@ -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;