This commit is contained in:
Nikita Smith
2025-12-09 09:56:43 -08:00
parent c221286ab8
commit 962f0e30ae
5 changed files with 271 additions and 124 deletions
+17 -12
View File
@@ -1323,7 +1323,7 @@ ctrl_entity_store_apply_events(CTRL_EntityCtxRWStore *store, CTRL_EventList *lis
}
}
thread->stack_base = event->stack_base;
ctrl_rip_from_thread(&store->ctx, event->entity);
//ctrl_rip_from_thread(&store->ctx, event->entity);
}break;
case CTRL_EventKind_EndThread:
{
@@ -4451,6 +4451,20 @@ ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg,
dmn_process_write(spoof->process, r1u64(spoof->vaddr, spoof->vaddr+size_of_spoof), &spoof->new_ip_value);
}
// copy spoof info
if(do_spoof)
{
run_ctrls->spoof.process = spoof->process;
run_ctrls->spoof.thread = spoof->thread;
run_ctrls->spoof.vaddr = spoof->vaddr;
run_ctrls->spoof.old_ip = spoof_old_ip_value;
run_ctrls->spoof.size = size_of_spoof;
}
else
{
MemoryZeroStruct(&run_ctrls->spoof);
}
// rjf: run for new events
ProfScope("run for new events")
{
@@ -4474,6 +4488,7 @@ ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg,
}
}
}
DMN_EventList events = dmn_ctrl_run(scratch.arena, ctrl_ctx, run_ctrls);
ins_atomic_u64_inc_eval(&ctrl_state->mem_gen);
ins_atomic_u64_inc_eval(&ctrl_state->reg_gen);
@@ -5956,17 +5971,7 @@ ctrl_thread__run(DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg)
CTRL_Entity *process = ctrl_entity_from_handle(entity_ctx, ctrl_handle_make(CTRL_MachineID_Local, event->process));
Arch arch = thread->arch;
U64 thread_rip_vaddr = dmn_rip_from_thread(event->thread);
CTRL_Entity *module = &ctrl_entity_nil;
{
for(CTRL_Entity *m = process->first; m != &ctrl_entity_nil; m = m->next)
{
if(m->kind == CTRL_EntityKind_Module && contains_1u64(m->vaddr_range, thread_rip_vaddr))
{
module = m;
break;
}
}
}
CTRL_Entity *module = ctrl_module_from_process_vaddr(process, thread_rip_vaddr);
//////////////////////////
//- rjf: extract module-dependent info
+34
View File
@@ -218,3 +218,37 @@ dmn_get_trap_inst(void)
return str8_array_fixed(inst);
}
internal DMN_ActiveTrap *
dmn_set_trap(Arena *arena, DMN_Trap *trap)
{
DMN_ActiveTrap *result = 0;
String8 trap_inst = dmn_get_trap_inst();
U8 *swap_bytes = push_array(arena, U8, trap_inst.size);
if(dmn_process_read(trap->process, r1u64(trap->vaddr, trap->vaddr + trap_inst.size), swap_bytes) == trap_inst.size)
{
if(dmn_process_write(trap->process, r1u64(trap->vaddr, trap->vaddr + trap_inst.size), trap_inst.str) == trap_inst.size)
{
result = push_array(arena, DMN_ActiveTrap, 1);
result->trap = trap;
result->swap_bytes = str8(swap_bytes, trap_inst.size);
}
else
{
Assert(0 && "failed to write trap instruction");
}
}
else
{
Assert(0 && "failed to read original byte");
}
return result;
}
internal B32
dmn_remove_trap(DMN_ActiveTrap *active_trap)
{
B32 is_removed = dmn_process_write(active_trap->trap->process, r1u64(active_trap->trap->vaddr, active_trap->trap->vaddr + active_trap->swap_bytes.size), active_trap->swap_bytes.str);
Assert(is_removed);
return is_removed;
}
+14 -3
View File
@@ -165,12 +165,20 @@ struct DMN_RunCtrls
{
DMN_Handle priority_thread;
DMN_Handle single_step_thread;
B8 ignore_previous_exception;
B8 run_entities_are_unfrozen;
B8 run_entities_are_processes;
DMN_Handle *run_entities;
U64 run_entity_count;
DMN_TrapChunkList traps;
B8 ignore_previous_exception;
B8 run_entities_are_unfrozen;
B8 run_entities_are_processes;
struct
{
DMN_Handle process;
DMN_Handle thread;
U64 vaddr;
U64 old_ip;
U64 size;
} spoof;
};
////////////////////////////////
@@ -279,4 +287,7 @@ internal void dmn_process_iter_end(DMN_ProcessIter *iter);
//- arch trap
internal String8 dmn_get_trap_inst(void);
internal DMN_ActiveTrap * dmn_set_trap(Arena *arena, DMN_Trap *trap);
internal B32 dmn_remove_trap(DMN_ActiveTrap *active_trap);
#endif // DEMON_CORE_H
+161 -72
View File
@@ -125,7 +125,7 @@ dmn_lnx_ptrace_seize(pid_t pid)
AssertAlways(dmn_lnx_state->tracer_tid == gettid());
// TODO: PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK | PTRACE_O_TRACEVFORKDONE
return OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_SEIZE, pid, 0, PTRACE_O_TRACEEXEC | PTRACE_O_EXITKILL | PTRACE_O_TRACECLONE));
return OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_SEIZE, pid, 0, PTRACE_O_TRACEEXEC | PTRACE_O_EXITKILL | PTRACE_O_TRACEFORK | PTRACE_O_TRACECLONE));
}
////////////////////////////////
@@ -925,6 +925,39 @@ dmn_lnx_process_from_pid(pid_t pid)
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
dmn_lnx_thread_read_ip(DMN_LNX_Entity *thread)
{
@@ -1419,6 +1452,8 @@ dmn_lnx_set_single_step_flag(DMN_LNX_Entity *thread, B32 is_on)
return is_flag_set;
}
////////////////////////////////
internal void
dmn_lnx_handle_not_attached(Arena *arena, DMN_EventList *events)
{
@@ -1427,8 +1462,6 @@ dmn_lnx_handle_not_attached(Arena *arena, DMN_EventList *events)
e->error_kind = DMN_ErrorKind_NotAttached;
}
////////////////////////////////
internal DMN_LNX_Entity *
dmn_lnx_handle_create_thread(Arena *arena, DMN_EventList *events, DMN_LNX_Entity *process, pid_t tid)
{
@@ -1537,24 +1570,6 @@ dmn_lnx_handle_create_process(Arena *arena, DMN_EventList *events, B32 debug_sub
}
}
//
// install DL probes
//
U64 probe_vaddrs[DMN_LNX_ProbeType_Count] = {0};
for EachIndex(i, DMN_LNX_ProbeType_Count)
{
if(known_probes[i] == 0) { continue; }
U8 og_byte = 0;
if(!dmn_lnx_read_struct(memory_fd, known_probes[i]->pc, &og_byte)) { Assert(0 && "failed to read original byte"); }
Assert(og_byte == 0x90);
U8 trap = 0xcc;
if(!dmn_lnx_write_struct(memory_fd, known_probes[i]->pc, &trap)) { Assert(0 && "failed to install probe"); }
probe_vaddrs[i] = known_probes[i]->pc;
}
//
// init process
//
@@ -1574,17 +1589,15 @@ dmn_lnx_handle_create_process(Arena *arena, DMN_EventList *events, B32 debug_sub
process->xcr0 = xcr0;
process->xsave_size = Max(xsave_size, sizeof(X64_XSave));
process->xsave_layout = xsave_layout;
MemoryCopyTyped(&process->probe_vaddrs[0], &probe_vaddrs[0], DMN_LNX_ProbeType_Count);
hash_table_push_u64_raw(dmn_lnx_state->arena, dmn_lnx_state->pid_ht, pid, process);
// push create process event
{
DMN_Event *e = dmn_event_list_push(arena, events);
e->kind = DMN_EventKind_CreateProcess;
e->process = dmn_lnx_handle_from_entity(process);
e->arch = process->arch;
e->code = pid;
e->tls_model = DMN_TlsModel_Gnu; // TODO: use dynamic linker path to figure out correct enum here
}
//
// init main thread
@@ -1598,6 +1611,8 @@ dmn_lnx_handle_create_process(Arena *arena, DMN_EventList *events, B32 debug_sub
DMN_LNX_Entity *module = dmn_lnx_entity_alloc(process, DMN_LNX_EntityKind_Module);
module->id = auxv.execfn;
module->base_vaddr = base_vaddr;
module->module_name_vaddr = auxv.execfn;
module->is_main = 1;
hash_table_push_u64_raw(process->arena, process->loaded_modules_ht, 0, module);
hash_table_push_u64_raw(process->arena, process->loaded_modules_ht, base_vaddr, module);
@@ -1707,28 +1722,20 @@ dmn_lnx_handle_exit_thread(Arena *arena, DMN_EventList *events, pid_t tid, U64 e
}
}
internal void
dmn_lnx_handle_load_module(Arena *arena, DMN_EventList *events, DMN_LNX_Entity *process, U64 name_space_id, U64 new_link_map_vaddr)
internal DMN_LNX_Entity *
dmn_lnx_load_module(Arena *arena, DMN_EventList *events, DMN_LNX_Entity *process, U64 name_space_id, U64 module_name_vaddr, U64 base_vaddr)
{
GNU_LinkMap64 map = {0};
for(U64 map_vaddr = new_link_map_vaddr; map_vaddr != 0; map_vaddr = map.next_vaddr)
{
// read out new link map item
if(!dmn_lnx_read_linkmap(process->fd, map_vaddr, process->dl_class, &map)) { goto exit; }
// was module with this base already registered?
DMN_LNX_Entity *module = hash_table_search_u64_raw(process->loaded_modules_ht, map.addr_vaddr);
if(module) { continue; }
DMN_LNX_Entity *module = 0;
// parse out module's ELF header
ELF_Hdr64 module_ehdr = {0};
if(!dmn_lnx_read_ehdr(process->fd, map.addr_vaddr, &module_ehdr)) { goto exit; }
if(!dmn_lnx_read_ehdr(process->fd, base_vaddr, &module_ehdr)) { goto exit; }
// gather info about module
U64 module_rebase = module_ehdr.e_type == ELF_Type_Dyn ? map.addr_vaddr : 0;
U64 module_rebase = module_ehdr.e_type == ELF_Type_Dyn ? base_vaddr : 0;
U64 module_phdr_vaddr = module_rebase + module_ehdr.e_phoff;
DMN_LNX_PhdrInfo module_phdr_info = dmn_lnx_phdr_info_from_memory(process->fd, module_ehdr.e_ident[ELF_Identifier_Class], module_rebase, module_phdr_vaddr, module_ehdr.e_phentsize, module_ehdr.e_phnum);
String8 module_name = dmn_lnx_read_string(process->arena, process->fd, map.name_vaddr);
String8 module_name = dmn_lnx_read_string(process->arena, process->fd, module_name_vaddr);
// read TLS index and TLS offset
U64 tls_index = max_U64;
@@ -1737,39 +1744,58 @@ dmn_lnx_handle_load_module(Arena *arena, DMN_EventList *events, DMN_LNX_Entity *
{
Rng1U64 tls_modid_range = r1u64(dmn_lnx_state->tls_modid_desc.offset, dmn_lnx_state->tls_modid_desc.offset + dmn_lnx_state->tls_modid_desc.bit_size / 8);
Rng1U64 tls_offset_range = r1u64(dmn_lnx_state->tls_offset_desc.offset, dmn_lnx_state->tls_offset_desc.offset + dmn_lnx_state->tls_offset_desc.bit_size / 8);
tls_modid_range = shift_1u64(tls_modid_range, map_vaddr);
tls_offset_range = shift_1u64(tls_offset_range, map_vaddr);
tls_modid_range = shift_1u64(tls_modid_range, base_vaddr);
tls_offset_range = shift_1u64(tls_offset_range, base_vaddr);
if(!dmn_lnx_read(process->fd, tls_modid_range, &tls_index)) { Assert(0 && "failed to read TLS index"); }
if(!dmn_lnx_read(process->fd, tls_offset_range, &tls_offset)) { Assert(0 && "failed to read TLS offset"); }
}
// fill out module
module = dmn_lnx_entity_alloc(process, DMN_LNX_EntityKind_Module);
module->id = map.name_vaddr;
module->base_vaddr = map.addr_vaddr;
module->id = module_name_vaddr;
module->base_vaddr = base_vaddr;
module->module_name_vaddr = module_name_vaddr;
if(str8_match(module_name, str8_lit("linux-vdso.so.1"), 0)) { goto exit; }
if(!str8_match(module_name, str8_lit("linux-vdso.so.1"), 0))
{
// push load event
DMN_Event *e = dmn_event_list_push(arena, events);
e->kind = DMN_EventKind_LoadModule;
e->process = dmn_lnx_handle_from_entity(process);
e->module = dmn_lnx_handle_from_entity(module);
e->arch = arch_from_elf_machine(module_ehdr.e_machine);
e->address = map.addr_vaddr;
e->address = base_vaddr;
e->size = dim_1u64(module_phdr_info.range);
e->string = module_name;
e->elf_phdr_vrange = r1u64(module_phdr_vaddr, module_phdr_vaddr + module_ehdr.e_phentsize * module_ehdr.e_phnum);
e->elf_phdr_entsize = module_ehdr.e_phentsize;
e->tls_index = tls_index;
e->tls_offset = tls_offset;
}
exit:;
return module;
}
internal void
dmn_lnx_handle_load_module(Arena *arena, DMN_EventList *events, DMN_LNX_Entity *process, U64 name_space_id, U64 new_link_map_vaddr)
{
GNU_LinkMap64 map = {0};
for(U64 map_vaddr = new_link_map_vaddr; map_vaddr != 0; map_vaddr = map.next_vaddr)
{
// read out new link map item
if(!dmn_lnx_read_linkmap(process->fd, map_vaddr, process->dl_class, &map)) { break; }
// was module with this base already registered?
DMN_LNX_Entity *module = hash_table_search_u64_raw(process->loaded_modules_ht, map.addr_vaddr);
if(module == 0)
{
// load module
module = dmn_lnx_load_module(arena, events, process, name_space_id, map.name_vaddr, map.addr_vaddr);
// create mapping for base -> module
hash_table_push_u64_raw(process->arena, process->loaded_modules_ht, map.addr_vaddr, module);
}
exit:;
}
}
internal void
@@ -1835,11 +1861,19 @@ dmn_lnx_handle_breakpoint(Arena *arena, DMN_EventList *events, HashTable *active
// is this a probe trap?
DMN_LNX_ProbeType probe_type = DMN_LNX_ProbeType_Null;
for EachIndex(i, ArrayCount(process->probe_vaddrs))
for EachNode(active_trap, DMN_ActiveTrap, process->first_probe_trap)
{
if(process->probe_vaddrs[i] == ip-1)
if(active_trap->trap->vaddr == ip-1)
{
probe_type = active_trap->trap->id;
break;
}
}
for(U64 i = 0; i < DMN_LNX_ProbeType_Count; ++i)
{
if(process->probes[i] != 0 && process->probes[i]->pc == ip-1)
{
probe_type = i;
break;
}
}
@@ -2029,6 +2063,7 @@ dmn_lnx_handle_attach(Arena *arena, DMN_EventList *events, pid_t pid)
{
// create process
process = dmn_lnx_handle_create_process(arena, events, 1, 1, pid);
dmn_lnx_process_trap_probes(process);
// extract threads from /proc/pid/task
for(;;)
@@ -2313,26 +2348,10 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls)
DMN_LNX_Entity *process = dmn_lnx_entity_from_handle(trap->process);
if(process == dmn_lnx_nil_entity) { continue; }
// read original instruction bytes
String8 trap_inst = dmn_get_trap_inst();
U8 *swap_bytes = push_array(scratch.arena, U8, trap_inst.size);
if(dmn_process_read(trap->process, r1u64(trap->vaddr, trap->vaddr+trap_inst.size), swap_bytes) != trap_inst.size)
{
Assert(0 && "failed to read original byte");
continue;
}
// replace with trap instruction bytes
if(dmn_process_write(trap->process, r1u64(trap->vaddr, trap->vaddr+trap_inst.size), trap_inst.str) != trap_inst.size)
{
Assert(0 && "failed to write trap");
continue;
}
// trap instruction
DMN_ActiveTrap *active_trap = dmn_set_trap(scratch.arena, trap);
// add trap to the active list
DMN_ActiveTrap *active_trap = push_array(scratch.arena, DMN_ActiveTrap, 1);
active_trap->trap = trap;
active_trap->swap_bytes = str8(swap_bytes, trap_inst.size);
SLLQueuePush(active_trap_first, active_trap_last, active_trap);
// add (address -> trap)
@@ -2464,6 +2483,7 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls)
// wait for signals from the running threads
{
printf("----------------------------------------------\n");
B32 is_halt_done = 0;
for(U64 stopped_threads = 0;;)
{
@@ -2495,6 +2515,8 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls)
int wstopsig = WSTOPSIG(status);
int event_code = (status >> 16);
printf("wstopsig %d, event_code %d\n", wstopsig, event_code);
// intercept signals meant for the process launch sequence
{
// pid -> pending process launch
@@ -2515,7 +2537,8 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls)
dmn_lnx_process_launch_list_push_node(&dmn_lnx_state->free_pids, pending_proc);
// push create process events
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
event_code = PTRACE_EVENT_STOP;
@@ -2637,7 +2660,72 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls)
}break;
case PTRACE_EVENT_FORK:
{
NotImplemented;
// grab child pid
pid_t child_pid;
if(OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_GETEVENTMSG, wait_id, 0, &child_pid)) < 0)
{
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
DMN_LNX_Entity *parent_thread = dmn_lnx_thread_from_pid(wait_id);
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
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))
{
AssertAlways(0 && "failed to undo spoof");
}
}
if(parent_process->debug_subprocesses)
{
// 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; }
if(module->is_main) { continue; }
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
for EachNode(n, DMN_ActiveTrap, active_trap_first)
{
if(MemoryCompare(&n->trap->process, &parent_process_handle, sizeof(DMN_Handle)))
{
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);
}
}
// detach and continue the child process
if(OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_DETACH, child_pid, 0, 0) < 0))
{
Assert(0 && "unsuccessful detach");
}
}
close(child_memory_fd);
temp_end(temp);
}break;
case PTRACE_EVENT_VFORK:
{
@@ -2661,7 +2749,8 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls)
if(process->debug_subprocesses)
{
dmn_lnx_handle_exit_thread(arena, &events, wait_id, 0);
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
{
+9 -1
View File
@@ -203,7 +203,8 @@ struct DMN_LNX_Entity
ELF_Class dl_class;
HashTable *loaded_modules_ht;
DMN_LNX_Probe **probes;
U64 probe_vaddrs[DMN_LNX_ProbeType_Count];
DMN_ActiveTrap *first_probe_trap;
DMN_ActiveTrap *last_probe_trap;
U64 main_thread_exit_code;
U64 thread_count;
@@ -222,11 +223,14 @@ struct DMN_LNX_Entity
U64 thread_local_base;
// module
U64 module_name_vaddr;
U64 base_vaddr;
U64 phvaddr;
U64 phentsize;
U64 phcount;
U64 name_space_id;
B8 is_live;
B8 is_main;
};
typedef struct DMN_LNX_EntityNode DMN_LNX_EntityNode;
@@ -372,6 +376,10 @@ internal DMN_Handle dmn_lnx_handle_from_entity(DMN_LNX_Entity *entity);
internal DMN_LNX_Entity *dmn_lnx_entity_from_handle(DMN_Handle handle);
internal DMN_LNX_Entity *dmn_lnx_thread_from_pid(pid_t pid);
//- Process
internal void dmn_lnx_process_install_probes();
//- Thread
internal U64 dmn_lnx_thread_read_ip(DMN_LNX_Entity *thread);
internal U64 dmn_lnx_thread_read_sp(DMN_LNX_Entity *thread);
internal void dmn_lnx_thread_write_ip(DMN_LNX_Entity *thread, U64 ip);