From 0b91cab00a97da7a2c57719718524e893aa2dbfd Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Tue, 29 Sep 2026 12:28:05 -0600 Subject: [PATCH] linux_demon: PTRACE_EVENT_STOPs can sometimes be reported even if a thread has already hit a trap (e.g. a trap net or breakpoint trap). this would sometimes cause spurious SIGTRAPs after issuing a PTRACE_INTERRUPT - so we need to flag halted threads to not treat that as a 'real' trap. furthermore, even though that trap was hit, it does not guarantee that the debugger would next see a SIGTRAP. that would cause spurious SIGILLs, because we'd resume threads *after* the trap, despite not seeing the trap, so they'd just see a malformed instruction (the bytes subsequent the initial trap). so in those cases, we need to roll such threads back, so that they re-execute the trap, and so that we see a regular SIGTRAP, and handle the trap correctly. --- src/linux/demon/linux_demon.c | 108 +++++++++++++++++++++------------- src/linux/demon/linux_demon.h | 1 + src/raddbg/raddbg_main.c | 13 ++-- 3 files changed, 75 insertions(+), 47 deletions(-) diff --git a/src/linux/demon/linux_demon.c b/src/linux/demon/linux_demon.c index 10e9efa9..46cce896 100644 --- a/src/linux/demon/linux_demon.c +++ b/src/linux/demon/linux_demon.c @@ -1770,6 +1770,7 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) { if(n->v != thread && n->v->state == LNX_DMN_ThreadState_Running) { + n->v->ghost_trap = 1; if(LNX_RETRY_ON_EINTR(ptrace(PTRACE_INTERRUPT, n->v->tid, 0, 0)) < 0) { log_infof("ptrace error: Couldn't send PTRACE_INTERRUPT to TID %I64u.", n->v->tid); @@ -2029,16 +2030,23 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) if(hit_user_trap) { U64 ip = lnx_dmn_ip_from_thread(thread); - lnx_dmn_thread_write_ip(thread, ip - 1); + lnx_dmn_thread_write_ip(thread, ip - arch_info->trap_instruction.size); } - // rjf: generate event - DMN_Event *e = dmn_event_list_push(arena, &events); - e->kind = hit_user_trap ? DMN_EventKind_Breakpoint : DMN_EventKind_Trap; - e->process = lnx_dmn_handle_from_process(process); - e->thread = lnx_dmn_handle_from_thread(thread); - e->instruction_pointer = lnx_dmn_ip_from_thread(thread); - e->user_data = hit_user_trap ? hit_user_trap->id : 0; + // rjf: generate event, if this isn't a ghost trap + if(thread->ghost_trap) + { + thread->ghost_trap = 0; + } + else + { + DMN_Event *e = dmn_event_list_push(arena, &events); + e->kind = hit_user_trap ? DMN_EventKind_Breakpoint : DMN_EventKind_Trap; + e->process = lnx_dmn_handle_from_process(process); + e->thread = lnx_dmn_handle_from_thread(thread); + e->instruction_pointer = lnx_dmn_ip_from_thread(thread); + e->user_data = hit_user_trap ? hit_user_trap->id : 0; + } } }break; @@ -2223,6 +2231,26 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) e->tls_root_vaddr = thread->dtv_base_vaddr; } + // rjf: stop event for other threads we're attached to, due to a stop in + // another thread - check if this hit a trap and roll back + else if(thread != 0 && thread->state == LNX_DMN_ThreadState_Stopped) + { + //- rjf: unpack thread IP / process + U64 ip = lnx_dmn_ip_from_thread(thread); + DMN_Handle process_handle = lnx_dmn_handle_from_process(process); + + //- rjf: check previous bytes against trap instruction. if this thread + // executed a trap, we need to roll back before continuing this thread. + // we CANNOT RELY on there being a subsequently-queued SIGTRAP. + U8 *hit_trap_bytes_maybe = push_array(scratch.arena, U8, arch_info->trap_instruction.size); + dmn_process_read(process_handle, r1u64(ip - arch_info->trap_instruction.size, ip), hit_trap_bytes_maybe); + String8 hit_trap_data_maybe = str8(hit_trap_bytes_maybe, arch_info->trap_instruction.size); + if(str8_match(hit_trap_data_maybe, arch_info->trap_instruction, 0)) + { + lnx_dmn_thread_write_ip(thread, ip - arch_info->trap_instruction.size); + } + } + // rjf: stop event for unrecognized thread -> this references a thread we // haven't seen cloned yet (we cannot rely on the order of clone/stop). so, // we will gather the ID, and increase the pending threads count, and stop @@ -2263,44 +2291,42 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) // else { - LNX_DMN_Thread *thread = lnx_dmn_thread_from_pid(wait_id); thread->pass_through_signal = 1; thread->pass_through_signo = wstopsig; local_persist B8 is_repeatable[] = { 0, // null - 1, // 1 SIGHUP - 1, // 2 SIGINT - 1, // 3 SIGQUIT - 1, // 4 SIGTRAP - 1, // 5 SIGABRT - 1, // 6 SIGIOT - 1, // 7 SIGBUS - 1, // 8 SIGFPE - 1, // 9 SIGKILL - 1, // 10 SIGUSR1 - 1, // 11 SIGSEGV - 1, // 12 SIGUSR2 - 1, // 13 SIGPIPE - 1, // 14 SIGALRM - 1, // 15 SIGTERM - 1, // 16 SIGTKFLT - 0, // 17 SIGCHLD - 0, // 18 SIGCONT - 1, // 19 SIGSTOP - 1, // 20 SIGTSP - 1, // 21 SIGTTIN - 1, // 22 SIGTTOU - 0, // 23 SIGURG - 1, // 24 SIGXCPU - 1, // 25 SIGXFSZ - 1, // 26 SIGVTALRM - 1, // 27 SIGPROF - 0, // 28 SIGWINCH - 1, // 29 SIGIO - 1, // 30 SIGPWR - 1, // 31 SIGSYS - 1, // 32 SIGUNUSED + 1, // SIGHUP 1 + 1, // SIGINT 2 + 1, // SIGQUIT 3 + 1, // SIGILL 4 + 1, // SIGTRAP 5 + 1, // SIGABRT/SIGIOT 6 + 1, // SIGBUS 7 + 1, // SIGFPE 8 + 1, // SIGKILL 9 + 1, // SIGUSR1 10 + 1, // SIGSEGV 11 + 1, // SIGUSR2 12 + 1, // SIGPIPE 13 + 1, // SIGALRM 14 + 1, // SIGTERM 15 + 1, // SIGSTKFLT 16 + 0, // SIGCHLD 17 + 0, // SIGCONT 18 + 1, // SIGSTOP 19 + 1, // SIGTSTP 20 + 1, // SIGTTIN 21 + 1, // SIGTTOU 22 + 0, // SIGURG 23 + 1, // SIGXCPU 24 + 1, // SIGXFSZ 25 + 1, // SIGVTALRM 26 + 1, // SIGPROF 27 + 0, // SIGWINCH 28 + 1, // SIGIO 29 + 1, // SIGPWR 30 + 1, // SIGSYS/SIGUNUSED31 }; DMN_Event *e = dmn_event_list_push(arena, &events); e->kind = DMN_EventKind_Exception; diff --git a/src/linux/demon/linux_demon.h b/src/linux/demon/linux_demon.h index 0e95a32f..ae81b419 100644 --- a/src/linux/demon/linux_demon.h +++ b/src/linux/demon/linux_demon.h @@ -178,6 +178,7 @@ struct LNX_DMN_Thread void *reg_block; B32 is_reg_block_dirty; B32 pass_through_signal; + B32 ghost_trap; U64 pass_through_signo; U64 orig_rax; U64 dtv_base_vaddr; diff --git a/src/raddbg/raddbg_main.c b/src/raddbg/raddbg_main.c index b30d7849..c17b9203 100644 --- a/src/raddbg/raddbg_main.c +++ b/src/raddbg/raddbg_main.c @@ -7,23 +7,24 @@ // initial alpha: // [x] .so loading/debugging - missing debug info // [x] missing disasm +// [ ] recycling semaphore files - or alt strat? // [ ] control -// [ ] multithreaded stepping - trap hits reported on other threads. -// [ ] stale memory - things evaluating to stale values that are definitely not zero -// [ ] support -// [ ] .eh_frame unwinding without .eh_frame_hdr -// [ ] asan +// [ ] stale memory - things evaluating to stale values that are definitely not zero // [ ] rendering -// [ ] bitmaps / geometry +// [ ] bitmaps / geometry // // post initial alpha: // [ ] windowing // [ ] wayland +// [ ] support +// [ ] .eh_frame unwinding without .eh_frame_hdr // // [x] clipboard // [x] jai exes - busted call stacks / unwind info / paths? // [x] statically linked EXEs - busted unwinding // [x] statically linked EXEs, not parsing maps file correctly +// [x] asan +// [x] multithreaded stepping - trap hits reported on other threads. //////////////////////////////// //~ rjf: evaluation system pass TODO notes