clean up instrumentation inherited during fork

This commit is contained in:
Nikita Smith
2025-12-09 16:08:19 -08:00
parent 094370c028
commit 5b6ffd5a49
+94 -99
View File
@@ -628,6 +628,7 @@ dmn_lnx_dl_path_from_pid(Arena *arena, pid_t pid, U64 auxv_base)
String8List parts = {0}; String8List parts = {0};
for EachIndex(cursor, line.size) for EachIndex(cursor, line.size)
{ {
if(parts.node_count > 5) { break; }
if(line.str[cursor] == ' ') { continue; } if(line.str[cursor] == ' ') { continue; }
// scan forward to the closing delimiter // scan forward to the closing delimiter
@@ -647,7 +648,7 @@ dmn_lnx_dl_path_from_pid(Arena *arena, pid_t pid, U64 auxv_base)
} }
// was line parsed correctly? // was line parsed correctly?
if(parts.node_count > 6 || parts.node_count < 5) { Assert(0 && "failed to parse map line"); continue; } if(parts.node_count < 5) { Assert(0 && "failed to parse map line"); continue; }
// parse map virtual range // parse map virtual range
String8List vaddr_list = str8_split_by_string_chars(scratch.arena, parts.first->string, str8_lit("-"), 0); String8List vaddr_list = str8_split_by_string_chars(scratch.arena, parts.first->string, str8_lit("-"), 0);
@@ -925,39 +926,6 @@ dmn_lnx_process_from_pid(pid_t pid)
return process; return process;
} }
internal void
dmn_lnx_process_trap_probes(DMN_LNX_Entity *process)
{
Assert(process->kind == DMN_LNX_EntityKind_Process);
for EachIndex(i, DMN_LNX_ProbeType_Count)
{
if(process->probes[i] == 0) { continue; }
DMN_Trap *trap = push_array(process->arena, DMN_Trap, 1);
trap->process = dmn_lnx_handle_from_entity(process);
trap->vaddr = process->probes[i]->pc;
trap->id = i;
DMN_ActiveTrap *active_trap = dmn_set_trap(process->arena, trap);
SLLQueuePush(process->first_probe_trap, process->last_probe_trap, active_trap);
if(BUILD_DEBUG && process->arch == Arch_x64)
{
Assert(active_trap->swap_bytes.size == 1 && active_trap->swap_bytes.str[0] == 0x90);
}
}
}
internal void
dmn_lnx_process_untrap_probes(DMN_LNX_Entity *process)
{
Assert(process->kind == DMN_LNX_EntityKind_Process);
for EachNode(active_trap, DMN_ActiveTrap, process->first_probe_trap)
{
dmn_remove_trap(active_trap);
}
}
internal U64 internal U64
dmn_lnx_thread_read_ip(DMN_LNX_Entity *thread) dmn_lnx_thread_read_ip(DMN_LNX_Entity *thread)
{ {
@@ -1591,6 +1559,25 @@ dmn_lnx_handle_create_process(Arena *arena, DMN_EventList *events, B32 debug_sub
process->xsave_layout = xsave_layout; process->xsave_layout = xsave_layout;
hash_table_push_u64_raw(dmn_lnx_state->arena, dmn_lnx_state->pid_ht, pid, process); hash_table_push_u64_raw(dmn_lnx_state->arena, dmn_lnx_state->pid_ht, pid, process);
// trap probes
for EachIndex(i, DMN_LNX_ProbeType_Count)
{
if(known_probes[i] == 0) { continue; }
DMN_Trap *trap = push_array(process_arena, DMN_Trap, 1);
trap->process = dmn_lnx_handle_from_entity(process);
trap->vaddr = known_probes[i]->pc;
trap->id = i;
DMN_ActiveTrap *active_trap = dmn_set_trap(process_arena, trap);
SLLQueuePush(process->first_probe_trap, process->last_probe_trap, active_trap);
if(BUILD_DEBUG && arch == Arch_x64)
{
Assert(active_trap->swap_bytes.size == 1 && active_trap->swap_bytes.str[0] == 0x90);
}
}
// push create process event // push create process event
DMN_Event *e = dmn_event_list_push(arena, events); DMN_Event *e = dmn_event_list_push(arena, events);
e->kind = DMN_EventKind_CreateProcess; e->kind = DMN_EventKind_CreateProcess;
@@ -1850,31 +1837,40 @@ dmn_lnx_hanlde_unload_module(Arena *arena, DMN_EventList *events, DMN_LNX_Entity
} }
internal void internal void
dmn_lnx_handle_breakpoint(Arena *arena, DMN_EventList *events, HashTable *active_trap_ht, pid_t tid) dmn_lnx_handle_breakpoint(Arena *arena, DMN_EventList *events, DMN_ActiveTrap *user_traps, pid_t tid)
{ {
DMN_LNX_Entity *thread = dmn_lnx_thread_from_pid(tid); DMN_LNX_Entity *thread = dmn_lnx_thread_from_pid(tid);
DMN_LNX_Entity *process = thread->parent; DMN_LNX_Entity *process = thread->parent;
U64 ip = dmn_lnx_thread_read_ip(thread); U64 ip = dmn_lnx_thread_read_ip(thread);
// is this user trap? // is this user trap?
DMN_Trap *hit_user_trap = hash_table_search_u64_raw(active_trap_ht, ip-1); DMN_ActiveTrap *hit_user_trap = 0;
// is this a probe trap?
DMN_LNX_ProbeType probe_type = DMN_LNX_ProbeType_Null;
for EachNode(active_trap, DMN_ActiveTrap, process->first_probe_trap)
{ {
if(active_trap->trap->vaddr == ip-1) DMN_Handle process_handle = dmn_lnx_handle_from_entity(process);
for EachNode(active_trap, DMN_ActiveTrap, user_traps)
{ {
probe_type = active_trap->trap->id; if(MemoryCompare(&active_trap->trap->process, &process_handle, sizeof(DMN_Handle)) == 0)
break; {
if(active_trap->trap->vaddr == ip-1)
{
hit_user_trap = active_trap;
break;
}
}
} }
} }
for(U64 i = 0; i < DMN_LNX_ProbeType_Count; ++i) // is this a probe trap?
DMN_LNX_ProbeType probe_type = DMN_LNX_ProbeType_Null;
if(hit_user_trap == 0)
{ {
if(process->probes[i] != 0 && process->probes[i]->pc == ip-1) for EachNode(active_trap, DMN_ActiveTrap, process->first_probe_trap)
{ {
break; if(active_trap->trap->vaddr == ip-1)
{
probe_type = active_trap->trap->id;
break;
}
} }
} }
@@ -2063,7 +2059,6 @@ dmn_lnx_handle_attach(Arena *arena, DMN_EventList *events, pid_t pid)
{ {
// create process // create process
process = dmn_lnx_handle_create_process(arena, events, 1, 1, pid); process = dmn_lnx_handle_create_process(arena, events, 1, 1, pid);
dmn_lnx_process_trap_probes(process);
// extract threads from /proc/pid/task // extract threads from /proc/pid/task
for(;;) for(;;)
@@ -2330,8 +2325,8 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls)
// write traps to memory // write traps to memory
DMN_ActiveTrap *active_trap_first = 0, *active_trap_last = 0; DMN_ActiveTrap *active_trap_first = 0, *active_trap_last = 0;
HashTable *active_trap_ht = hash_table_init(scratch.arena, ctrls->traps.trap_count);
{ {
HashTable *process_ht = hash_table_init(scratch.arena, dmn_lnx_state->active_process_count);
for EachNode(n, DMN_TrapChunkNode, ctrls->traps.first) for EachNode(n, DMN_TrapChunkNode, ctrls->traps.first)
{ {
for EachIndex(n_idx, n->count) for EachIndex(n_idx, n->count)
@@ -2340,6 +2335,13 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls)
DMN_Trap *trap = n->v+n_idx; DMN_Trap *trap = n->v+n_idx;
if(trap->flags) { continue; } if(trap->flags) { continue; }
HashTable *active_trap_ht = hash_table_search_u64_raw(process_ht, trap->process.u64[0]);
if(active_trap_ht == 0)
{
active_trap_ht = hash_table_init(scratch.arena, ctrls->traps.trap_count);
hash_table_push_u64_raw(scratch.arena, process_ht, trap->process.u64[0], active_trap_ht);
}
// TODO: ctrl sends down duplicate traps // TODO: ctrl sends down duplicate traps
DMN_ActiveTrap *is_set = hash_table_search_u64_raw(active_trap_ht, trap->vaddr); DMN_ActiveTrap *is_set = hash_table_search_u64_raw(active_trap_ht, trap->vaddr);
if(is_set) { continue; } if(is_set) { continue; }
@@ -2483,7 +2485,6 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls)
// wait for signals from the running threads // wait for signals from the running threads
{ {
printf("----------------------------------------------\n");
B32 is_halt_done = 0; B32 is_halt_done = 0;
for(U64 stopped_threads = 0;;) for(U64 stopped_threads = 0;;)
{ {
@@ -2515,8 +2516,6 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls)
int wstopsig = WSTOPSIG(status); int wstopsig = WSTOPSIG(status);
int event_code = (status >> 16); int event_code = (status >> 16);
printf("wstopsig %d, event_code %d\n", wstopsig, event_code);
// intercept signals meant for the process launch sequence // intercept signals meant for the process launch sequence
{ {
// pid -> pending process launch // pid -> pending process launch
@@ -2538,7 +2537,6 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls)
// push create process events // push create process events
DMN_LNX_Entity *process = dmn_lnx_handle_create_process(arena, &events, pending_proc->debug_subprocesses, 0, wait_id); DMN_LNX_Entity *process = dmn_lnx_handle_create_process(arena, &events, pending_proc->debug_subprocesses, 0, wait_id);
dmn_lnx_process_trap_probes(process);
// override event code so the main code path does not create another process // override event code so the main code path does not create another process
event_code = PTRACE_EVENT_STOP; event_code = PTRACE_EVENT_STOP;
@@ -2648,10 +2646,10 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls)
{ {
switch(siginfo.si_code) switch(siginfo.si_code)
{ {
case SI_KERNEL: { dmn_lnx_handle_breakpoint(arena, &events, active_trap_ht, wait_id); } break; case SI_KERNEL: { dmn_lnx_handle_breakpoint(arena, &events, active_trap_first, wait_id); } break;
case TRAP_BRKPT: { dmn_lnx_handle_breakpoint(arena, &events, active_trap_ht, wait_id); } break; case TRAP_BRKPT: { dmn_lnx_handle_breakpoint(arena, &events, active_trap_first, wait_id); } break;
case TRAP_TRACE: { dmn_lnx_handle_single_step(arena, &events, wait_id); } break; case TRAP_TRACE: { dmn_lnx_handle_single_step(arena, &events, wait_id); } break;
case TRAP_HWBKPT: { dmn_lnx_handle_data_breakpoint(arena, &events, wait_id); } break; case TRAP_HWBKPT: { dmn_lnx_handle_data_breakpoint(arena, &events, wait_id); } break;
case TRAP_BRANCH: { NotImplemented; }break; case TRAP_BRANCH: { NotImplemented; }break;
case TRAP_UNK: { NotImplemented; }break; case TRAP_UNK: { NotImplemented; }break;
default: { InvalidPath; } break; default: { InvalidPath; } break;
@@ -2662,70 +2660,68 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls)
{ {
// grab child pid // grab child pid
pid_t child_pid; pid_t child_pid;
if(OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_GETEVENTMSG, wait_id, 0, &child_pid)) < 0) if(OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_GETEVENTMSG, wait_id, 0, &child_pid)) < 0) { AssertAlways(0 && "failed to grab child pid"); }
{
AssertAlways(0 && "failed to grab child pid");
}
// at this point kernel cloned memory pages and writes in the child process trigger COW
Temp temp = temp_begin(scratch.arena);
int child_memory_fd = open((char*)str8f(temp.arena, "/proc/%d/mem", child_pid).str, O_RDWR);
// grab parent process // grab parent process
DMN_LNX_Entity *parent_thread = dmn_lnx_thread_from_pid(wait_id); DMN_LNX_Entity *parent_thread = dmn_lnx_thread_from_pid(wait_id);
DMN_LNX_Entity *parent_process = parent_thread->parent; DMN_LNX_Entity *parent_process = parent_thread->parent;
DMN_Handle parent_process_handle = dmn_lnx_handle_from_entity(parent_process);
DMN_Handle parent_thread_handle = dmn_lnx_handle_from_entity(parent_thread);
// if debugger is steps over a line with fork we have to undo the return address spoof // clean up in the child process instrumentation inherited during fork
if(MemoryCompare(&parent_process_handle, &ctrls->spoof.process, sizeof(DMN_Handle)) == 0 &&
MemoryCompare(&parent_thread_handle, &ctrls->spoof.thread, sizeof(DMN_Handle)) == 0)
{ {
if(!dmn_lnx_write(child_memory_fd, r1u64(ctrls->spoof.vaddr, ctrls->spoof.vaddr + ctrls->spoof.size), &ctrls->spoof.old_ip)) DMN_Handle parent_process_handle = dmn_lnx_handle_from_entity(parent_process);
DMN_Handle parent_thread_handle = dmn_lnx_handle_from_entity(parent_thread);
// at this point kernel cloned memory pages and writes in the child process trigger COW
Temp temp = temp_begin(scratch.arena);
int child_memory_fd = open((char*)str8f(temp.arena, "/proc/%d/mem", child_pid).str, O_RDWR);
// if debugger is stepping over a line with fork we have to undo the return address spoof
if(MemoryCompare(&parent_process_handle, &ctrls->spoof.process, sizeof(DMN_Handle)) == 0 &&
MemoryCompare(&parent_thread_handle, &ctrls->spoof.thread, sizeof(DMN_Handle)) == 0)
{ {
AssertAlways(0 && "failed to undo spoof"); B32 was_spoof_removed = dmn_lnx_write(child_memory_fd, r1u64(ctrls->spoof.vaddr, ctrls->spoof.vaddr + ctrls->spoof.size), &ctrls->spoof.old_ip);
AssertAlways(was_spoof_removed);
} }
}
if(parent_process->debug_subprocesses) // remove probe traps from the child process
{ for EachNode(n, DMN_ActiveTrap, parent_process->first_probe_trap)
// create child process entity
dmn_lnx_process_untrap_probes(parent_process);
DMN_LNX_Entity *child_process = dmn_lnx_handle_create_process(arena, &events, 1, 1, child_pid);
dmn_lnx_process_trap_probes(child_process);
// copy modules from the parent process
for(DMN_LNX_Entity *module = parent_process->first; module != dmn_lnx_nil_entity; module = module->next)
{ {
if(module->kind != DMN_LNX_EntityKind_Module) { continue; } B32 is_written = dmn_lnx_write(child_memory_fd, r1u64(n->trap->vaddr, n->trap->vaddr + n->swap_bytes.size), n->swap_bytes.str);
if(module->is_main) { continue; } AssertAlways(is_written);
dmn_lnx_load_module(arena, &events, child_process, module->name_space_id, module->module_name_vaddr, module->base_vaddr);
} }
}
else
{
// take out probes traps from the child probess
dmn_lnx_process_untrap_probes(parent_process);
// take out traps from the child process // remove traps from the child process
for EachNode(n, DMN_ActiveTrap, active_trap_first) for EachNode(n, DMN_ActiveTrap, active_trap_first)
{ {
if(MemoryCompare(&n->trap->process, &parent_process_handle, sizeof(DMN_Handle))) if(MemoryCompare(&n->trap->process, &parent_process_handle, sizeof(DMN_Handle)) == 0)
{ {
B32 is_written = dmn_lnx_write(child_memory_fd, r1u64(n->trap->vaddr, n->trap->vaddr + n->swap_bytes.size), n->swap_bytes.str); B32 is_written = dmn_lnx_write(child_memory_fd, r1u64(n->trap->vaddr, n->trap->vaddr + n->swap_bytes.size), n->swap_bytes.str);
AssertAlways(is_written); AssertAlways(is_written);
} }
} }
// detach and continue the child process close(child_memory_fd);
if(OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_DETACH, child_pid, 0, 0) < 0)) temp_end(temp);
{
Assert(0 && "unsuccessful detach");
}
} }
close(child_memory_fd); if(parent_process->debug_subprocesses)
temp_end(temp); {
// create child process
DMN_LNX_Entity *child_process = dmn_lnx_handle_create_process(arena, &events, 1, 1, child_pid);
// copy modules from the parent process
for(DMN_LNX_Entity *mod = parent_process->first; mod != dmn_lnx_nil_entity; mod = mod->next)
{
if(mod->kind != DMN_LNX_EntityKind_Module) { continue; }
if(mod->is_main) { continue; }
dmn_lnx_load_module(arena, &events, child_process, mod->name_space_id, mod->module_name_vaddr, mod->base_vaddr);
}
}
else
{
// user asked to detach from the child process
if(OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_DETACH, child_pid, 0, 0) < 0)) { Assert(0 && "unsuccessful detach"); }
}
}break; }break;
case PTRACE_EVENT_VFORK: case PTRACE_EVENT_VFORK:
{ {
@@ -2750,7 +2746,6 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls)
{ {
dmn_lnx_handle_exit_thread(arena, &events, wait_id, 0); dmn_lnx_handle_exit_thread(arena, &events, wait_id, 0);
DMN_LNX_Entity *process = dmn_lnx_handle_create_process(arena, &events, 1, 0, wait_id); DMN_LNX_Entity *process = dmn_lnx_handle_create_process(arena, &events, 1, 0, wait_id);
dmn_lnx_process_trap_probes(process);
} }
else else
{ {