diff --git a/src/ctrl/ctrl_core.c b/src/ctrl/ctrl_core.c index 9477b835..bc8cd65c 100644 --- a/src/ctrl/ctrl_core.c +++ b/src/ctrl/ctrl_core.c @@ -4400,20 +4400,6 @@ 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") { diff --git a/src/demon/demon_core.h b/src/demon/demon_core.h index 8043b98e..53aebfbd 100644 --- a/src/demon/demon_core.h +++ b/src/demon/demon_core.h @@ -156,7 +156,7 @@ typedef struct DMN_ActiveTrap DMN_ActiveTrap; struct DMN_ActiveTrap { DMN_Trap *trap; - String8 swap_bytes; + String8 swap_bytes; DMN_ActiveTrap *next; }; @@ -171,14 +171,6 @@ struct DMN_RunCtrls 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; }; //////////////////////////////// diff --git a/src/demon/linux/demon_core_linux.c b/src/demon/linux/demon_core_linux.c index 544a49d8..e1fa1d63 100644 --- a/src/demon/linux/demon_core_linux.c +++ b/src/demon/linux/demon_core_linux.c @@ -2,43 +2,13 @@ // Licensed under the MIT license (https://opensource.org/license/mit/) //////////////////////////////// -//~ rjf: Helpers +//~ Includes -internal DMN_LNX_EntityNode * -dmn_lnx_entity_list_push(Arena *arena, DMN_LNX_EntityList *list, DMN_LNX_Entity *v) -{ - DMN_LNX_EntityNode *n = push_array(arena, DMN_LNX_EntityNode, 1); - n->v = v; - SLLQueuePush(list->first, list->last, n); - list->count += 1; - return n; -} +#include +#include +#include -internal void -dmn_lnx_process_launch_list_push_node(DMN_LNX_ProcessLaunchList *list, DMN_LNX_ProcessLaunch *n) -{ - DLLPushBack(list->first, list->last, n); - list->count += 1; -} - -internal DMN_LNX_ProcessLaunch * -dmn_lnx_process_launch_list_push(Arena *arena, DMN_LNX_ProcessLaunchList *list, pid_t pid) -{ - DMN_LNX_ProcessLaunch *n = push_array(arena, DMN_LNX_ProcessLaunch, 1); - n->pid = pid; - dmn_lnx_process_launch_list_push_node(list, n); - return n; -} - -internal void -dmn_lnx_process_launch_list_remove(DMN_LNX_ProcessLaunchList *list, DMN_LNX_ProcessLaunch *n) -{ - Assert(list->count > 0); - DLLRemove(list->first, list->last, n); - list->count -= 1; -} - -//- rjf: file descriptor memory reading/writing helpers +//////////////////////////////// internal U64 dmn_lnx_size_from_fd(int memory_fd, U64 cap) @@ -127,16 +97,10 @@ MACHINE_OP_MEM_READ(dmn_lnx_machine_op_mem_read) internal int dmn_lnx_ptrace_seize(pid_t pid) { - // ensure tracer ops are issued on thread that sizes processes - if(dmn_lnx_state->tracer_tid == 0) { dmn_lnx_state->tracer_tid = gettid(); } - 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_TRACEFORK | PTRACE_O_TRACECLONE)); + // TODO: PTRACE_O_TRACEVFORK | PTRACE_O_TRACEVFORKDONE | PTRACE_O_TRACEFORK + return OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_SEIZE, pid, 0, PTRACE_O_TRACEEXEC | PTRACE_O_EXITKILL | PTRACE_O_TRACECLONE)); } -//- rjf: pid => info extraction - internal String8 dmn_lnx_exe_path_from_pid(Arena *arena, pid_t pid) { @@ -261,11 +225,11 @@ dmn_lnx_ehdr_from_pid(pid_t pid) return exe; } -internal DMN_LNX_ProcessAuxv +internal DMN_LNX_Auxv dmn_lnx_auxv_from_pid(pid_t pid, ELF_Class elf_class) { Temp scratch = scratch_begin(0, 0); - DMN_LNX_ProcessAuxv result = {0}; + DMN_LNX_Auxv result = {0}; // rjf: open aux data String8 auxv_path = push_str8f(scratch.arena, "/proc/%d/auxv", pid); @@ -298,13 +262,13 @@ dmn_lnx_auxv_from_pid(pid_t pid, ELF_Class elf_class) switch(auxv.a_type) { default:{}break; - case ELF_AuxType_Null: goto brkloop; break; - case ELF_AuxType_Base: result.base = auxv.a_val; break; - case ELF_AuxType_Phnum: result.phnum = auxv.a_val; break; - case ELF_AuxType_Phent: result.phent = auxv.a_val; break; - case ELF_AuxType_Phdr: result.phdr = auxv.a_val; break; - case ELF_AuxType_ExecFn: result.execfn = auxv.a_val; break; - case ELF_AuxType_Pagesz: result.pagesz = auxv.a_val; break; + case ELF_AuxType_Null: goto brkloop; break; + case ELF_AuxType_Base: result.base = auxv.a_val; break; + case ELF_AuxType_Phnum: result.phnum = auxv.a_val; break; + case ELF_AuxType_Phent: result.phent = auxv.a_val; break; + case ELF_AuxType_Phdr: result.phdr = auxv.a_val; break; + case ELF_AuxType_ExecFn: result.execfn = auxv.a_val; break; + case ELF_AuxType_Pagesz: result.pagesz = auxv.a_val; break; } } brkloop:; @@ -315,6 +279,18 @@ dmn_lnx_auxv_from_pid(pid_t pid, ELF_Class elf_class) return result; } +internal DMN_LNX_Thread * +dmn_lnx_thread_from_pid(pid_t tid) +{ + return hash_table_search_u64_raw(dmn_lnx_state->tid_ht, tid); +} + +internal DMN_LNX_Process * +dmn_lnx_process_from_pid(pid_t pid) +{ + return hash_table_search_u64_raw(dmn_lnx_state->pid_ht, pid); +} + internal Rng1U64 dmn_lnx_compute_image_vrange(int memory_fd, ELF_Class elf_class, U64 rebase, U64 e_phaddr, U64 e_phentsize, U64 e_phnum) { @@ -340,29 +316,6 @@ dmn_lnx_compute_image_vrange(int memory_fd, ELF_Class elf_class, U64 rebase, U64 return result; } -internal U64 -dmn_lnx_find_dynamic_phdr(int memory_fd, ELF_Class elf_class, U64 rebase, U64 e_phaddr, U64 e_phentsize, U64 e_phnum) -{ - U64 result = max_U64; - - for(U64 ph_cursor = e_phaddr, ph_opl = (e_phaddr + e_phentsize * e_phnum); ph_cursor < ph_opl; ph_cursor += e_phentsize) - { - ELF_Phdr64 phdr = {0}; - if(elf_read_phdr(dmn_lnx_machine_op_mem_read, &memory_fd, ph_cursor, elf_class, &phdr) != MachineOpResult_Ok) - { - Assert(0 && "unable to read a program header"); - } - - if(phdr.p_type == ELF_PType_Dynamic) - { - result = rebase + phdr.p_vaddr; - break; - } - } - - return result; -} - internal DMN_LNX_DynamicInfo dmn_lnx_dynamic_info_from_memory(int memory_fd, ELF_Class elf_class, U64 rebase, U64 dynamic_vaddr) { @@ -405,6 +358,29 @@ dmn_lnx_dynamic_info_from_memory(int memory_fd, ELF_Class elf_class, U64 rebase, return dynamic_info; } +internal U64 +dmn_lnx_find_dynamic_phdr(int memory_fd, ELF_Class elf_class, U64 rebase, U64 e_phaddr, U64 e_phentsize, U64 e_phnum) +{ + U64 result = max_U64; + + for(U64 ph_cursor = e_phaddr, ph_opl = (e_phaddr + e_phentsize * e_phnum); ph_cursor < ph_opl; ph_cursor += e_phentsize) + { + ELF_Phdr64 phdr = {0}; + if(elf_read_phdr(dmn_lnx_machine_op_mem_read, &memory_fd, ph_cursor, elf_class, &phdr) != MachineOpResult_Ok) + { + Assert(0 && "unable to read a program header"); + } + + if(phdr.p_type == ELF_PType_Dynamic) + { + result = rebase + phdr.p_vaddr; + break; + } + } + + return result; +} + internal U64 dmn_lnx_rdebug_vaddr_from_memory(int memory_fd, U64 loader_vbase, B32 is_rebased) { @@ -493,9 +469,6 @@ exit:; return rdebug_vaddr; } -//////////////////////////////// -//~ SDT Probes - internal DMN_LNX_ProbeList dmn_lnx_read_probes(Arena *arena, int fd, U64 offset, U64 image_base) { @@ -622,22 +595,16 @@ exit:; return probes; } -//////////////////////////////// -//~ STAP - internal STAP_MEMORY_READ(dmn_lnx_stap_memory_read) { - DMN_LNX_Entity *process = raw_ctx; + DMN_LNX_Process *process = raw_ctx; U64 bytes_read = dmn_lnx_read(process->fd, r1u64(addr, addr + read_size), buffer); return bytes_read == read_size; } -//////////////////////////////// -//~ rjf: Entity Functions - internal DMN_LNX_Entity * -dmn_lnx_entity_alloc(DMN_LNX_Entity *parent, DMN_LNX_EntityKind kind) +dmn_lnx_entity_alloc(DMN_LNX_EntityKind kind) { DMN_LNX_Entity *entity = dmn_lnx_state->free_entity; if(entity != 0) @@ -650,189 +617,592 @@ dmn_lnx_entity_alloc(DMN_LNX_Entity *parent, DMN_LNX_EntityKind kind) dmn_lnx_state->entities_count += 1; } U32 gen = entity->gen; - MemoryCopyStruct(entity, dmn_lnx_nil_entity); entity->gen += 1; - if(parent != dmn_lnx_nil_entity) - { - DLLPushBack_NPZ(dmn_lnx_nil_entity, parent->first, parent->last, entity, next, prev); - entity->parent = parent; - } entity->kind = kind; return entity; } +internal DMN_LNX_Process * +dmn_lnx_process_alloc(pid_t pid, DMN_LNX_ProcessState state, DMN_LNX_Process *parent_process, B32 debug_subprocesses, B32 is_cow) +{ + Temp scratch = scratch_begin(0, 0); + + DMN_LNX_Process *process = &dmn_lnx_entity_alloc(DMN_LNX_EntityKind_Process)->process; + process->pid = pid; + process->fd = open((char*)str8f(scratch.arena, "/proc/%d/mem", pid).str, O_RDWR); + process->state = state; + process->debug_subprocesses = debug_subprocesses; + process->is_cow = is_cow; + process->parent_process = parent_process; + + // update pending process tracker + if(state != DMN_LNX_ProcessState_Normal) + { + dmn_lnx_state->process_pending_creation += 1; + } + + // add process to the list + DLLPushBack(dmn_lnx_state->first_process, dmn_lnx_state->last_process, process); + dmn_lnx_state->process_count += 1; + + // push pid -> DMN_LNX_Process mapping + hash_table_push_u64_raw(dmn_lnx_state->arena, dmn_lnx_state->pid_ht, pid, process); + + scratch_end(scratch); + return process; +} + +internal DMN_LNX_ProcessCtx * +dmn_lnx_process_ctx_alloc(DMN_LNX_Process *process, B32 is_rebased) +{ + DMN_LNX_ProcessCtx *ctx = &dmn_lnx_entity_alloc(DMN_LNX_EntityKind_ProcessCtx)->process_ctx; + + ELF_Hdr64 exe_ehdr = dmn_lnx_ehdr_from_pid(process->pid); + DMN_LNX_Auxv auxv = dmn_lnx_auxv_from_pid(process->pid, exe_ehdr.e_ident[ELF_Identifier_Class]); + Arch arch = arch_from_elf_machine(exe_ehdr.e_machine); + U64 rdebug_vaddr = dmn_lnx_rdebug_vaddr_from_memory(process->fd, auxv.base, is_rebased); + U64 base_vaddr = (auxv.phdr & ~(auxv.pagesz-1)); + U64 rebase = exe_ehdr.e_type == ELF_Type_Dyn ? base_vaddr : 0; + Rng1U64 image_vrange = dmn_lnx_compute_image_vrange(process->fd, exe_ehdr.e_ident[ELF_Identifier_Class], rebase, auxv.phdr, auxv.phent, auxv.phnum); + Arena *ctx_arena = arena_alloc(); + + ELF_Class dl_class; + { + ELF_Hdr64 ehdr = {0}; + if(elf_read_ehdr(dmn_lnx_machine_op_mem_read, &process->fd, auxv.base, &ehdr) != MachineOpResult_Ok) { Assert(0 && "failed to read interp's header"); } + dl_class = ehdr.e_ident[ELF_Identifier_Class]; + } + + // query xsave layout + U64 xcr0 = 0; + U64 xsave_size = 0; + X64_XSaveLayout xsave_layout = {0}; + if(arch == Arch_x64) + { + X64_XSave xsave = {0}; + if(OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_GETREGSET, process->pid, (void *)NT_X86_XSTATE, &(struct iovec){.iov_base = &xsave, .iov_len = sizeof(xsave) }) >= 0)) + { + // Linux stores xcr0 bits in fxstate padding, + // see https://github.com/torvalds/linux/blob/6548d364a3e850326831799d7e3ea2d7bb97ba08/arch/x86/include/asm/user.h#L25 + xcr0 = *(U64 *)((U8 *)&xsave + 464); + xsave_size = x64_get_xsave_size(); + xsave_layout = x64_get_xsave_layout(xcr0); + } + else { Assert(0 && "failed to get xstate"); } + } + + // gather probes + DMN_LNX_Probe **known_probes = push_array(ctx_arena, DMN_LNX_Probe *, DMN_LNX_ProbeType_Count); + { + Temp scratch = scratch_begin(0, 0); + + String8 dl_path = dmn_lnx_dl_path_from_pid(scratch.arena, process->pid, auxv.base); + int dl_fd = open((char *)dl_path.str, O_RDONLY); + + DMN_LNX_ProbeList probes = {0}; + if(dl_fd >= 0) + { + probes = dmn_lnx_read_probes(ctx_arena, dl_fd, 0, auxv.base); + close(dl_fd); + } + + for EachNode(n, DMN_LNX_ProbeNode, probes.first) + { + DMN_LNX_Probe *p = &n->v; + if(str8_match(p->provider, str8_lit("rtld"), 0)) + { +#define X(_N,_A,_S) if(str8_match(p->name, str8_lit(_S), 0)) { AssertAlways(p->args.count == _A); known_probes[DMN_LNX_ProbeType_##_N] = p; continue ; } + DMN_LNX_Probe_XList +#undef X + } + } + + scratch_end(scratch); + } + + ctx = &dmn_lnx_entity_alloc(DMN_LNX_EntityKind_ProcessCtx)->process_ctx; + ctx->arena = arena_alloc(); + ctx->arch = arch; + ctx->rdebug_vaddr = rdebug_vaddr; + ctx->dl_class = dl_class; + ctx->loaded_modules_ht = hash_table_init(ctx_arena, 0x1000); + ctx->probes = known_probes; + ctx->xcr0 = xcr0; + ctx->xsave_size = Max(xsave_size, sizeof(X64_XSave)); + ctx->xsave_layout = xsave_layout; + + // create main module + DMN_LNX_Module *main_module = dmn_lnx_module_alloc(ctx, process->fd, base_vaddr, auxv.execfn, 1, 1); + + // glibc has a shortcut mapping for the main module + hash_table_push_u64_raw(ctx->arena, ctx->loaded_modules_ht, 0, main_module); + + return ctx; +} + +internal DMN_LNX_Thread * +dmn_lnx_thread_alloc(DMN_LNX_Process *process, DMN_LNX_ThreadState thread_state, pid_t tid) +{ + void *reg_block; + if(process->ctx->free_reg_blocks.node_count) + { + String8Node *n = str8_list_pop_front(&process->ctx->free_reg_blocks); + reg_block = n->string.str; + str8_list_push_node(&process->ctx->free_reg_block_nodes, n); + } + else + { + U64 reg_block_size = regs_block_size_from_arch(process->ctx->arch); + reg_block = push_array(process->ctx->arena, U8, reg_block_size); + } + + DMN_LNX_Thread *thread = &dmn_lnx_entity_alloc(DMN_LNX_EntityKind_Thread)->thread; + thread->tid = tid; + thread->state = thread_state; + thread->process = process; + thread->reg_block = reg_block; + if(thread_state == DMN_LNX_ThreadState_Stopped) + { + thread->is_reg_block_dirty = !dmn_lnx_thread_read_reg_block(thread); + } + + // add thread to the list + DLLPushBack(process->first_thread, process->last_thread, thread); + process->thread_count += 1; + + // push tid -> thread mapping + hash_table_push_u64_raw(dmn_lnx_state->arena, dmn_lnx_state->tid_ht, thread->tid, thread); + + // update global thread counter + if(thread_state == DMN_LNX_ThreadState_PendingCreation) + { + dmn_lnx_state->threads_pending_creation += 1; + } + + return thread; +} + +internal DMN_LNX_Module * +dmn_lnx_module_alloc(DMN_LNX_ProcessCtx *ctx, int memory_fd, U64 base_vaddr, U64 name_vaddr, U64 name_space_id, B32 is_main) +{ + DMN_LNX_Module *module = hash_table_search_u64_raw(ctx->loaded_modules_ht, base_vaddr); + if(module) { goto exit; } + + // parse out module's ELF header + ELF_Hdr64 module_ehdr = {0}; + if(elf_read_ehdr(dmn_lnx_machine_op_mem_read, &memory_fd, base_vaddr, &module_ehdr) != MachineOpResult_Ok) { goto exit; } + + // gather info about module + U64 module_rebase = module_ehdr.e_type == ELF_Type_Dyn ? base_vaddr : 0; + U64 module_phdr_vaddr = module_rebase + module_ehdr.e_phoff; + Rng1U64 module_vrange = dmn_lnx_compute_image_vrange(memory_fd, module_ehdr.e_ident[ELF_Identifier_Class], module_rebase, module_phdr_vaddr, module_ehdr.e_phentsize, module_ehdr.e_phnum); + + // read TLS index and TLS offset + U64 tls_index = max_U64; + U64 tls_offset = max_U64; + if(is_main) + { + tls_index = 1; + tls_offset = 0; + } + else + { + if(dmn_lnx_state->is_tls_detected) + { + 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, base_vaddr); + tls_offset_range = shift_1u64(tls_offset_range, base_vaddr); + if(!dmn_lnx_read(memory_fd, tls_modid_range, &tls_index)) { Assert(0 && "failed to read TLS index"); } + if(!dmn_lnx_read(memory_fd, tls_offset_range, &tls_offset)) { Assert(0 && "failed to read TLS offset"); } + } + } + + module = &dmn_lnx_entity_alloc(DMN_LNX_EntityKind_Module)->module; + module->base_vaddr = base_vaddr; + module->name_vaddr = name_vaddr; + module->name_space_id = name_space_id; + module->size = dim_1u64(module_vrange); + module->phvaddr = base_vaddr + module_ehdr.e_phoff; + module->phcount = module_ehdr.e_phnum; + module->phentsize = module_ehdr.e_phentsize; + module->tls_index = tls_index; + module->tls_offset = tls_offset; + module->is_main = is_main; + + // add module to the list + DLLPushBack(ctx->first_module, ctx->last_module, module); + ctx->module_count += 1; + + // push base address -> module mapping + hash_table_push_u64_raw(ctx->arena, ctx->loaded_modules_ht, base_vaddr, module); + +exit:; + return module; +} + internal void dmn_lnx_entity_release(DMN_LNX_Entity *entity) { - if(entity->parent != dmn_lnx_nil_entity) - { - DLLRemove_NPZ(dmn_lnx_nil_entity, entity->parent->first, entity->parent->last, entity, next, prev); - entity->parent = dmn_lnx_nil_entity; - } - { - Temp scratch = scratch_begin(0, 0); - for EachNode(t, DMN_LNX_EntityNode, &(DMN_LNX_EntityNode) { .v = entity }) - { - // put children nodes on the task list - for(DMN_LNX_Entity *child = t->v->first; child != dmn_lnx_nil_entity; child = child->next) - { - DMN_LNX_EntityNode *task = push_array(scratch.arena, DMN_LNX_EntityNode, 1); - task->next = t->next; - t->next = task; - task->v = child; - } + U32 gen = entity->gen + 1; + MemoryZeroStruct(entity); + entity->gen = gen; + SLLStackPush(dmn_lnx_state->free_entity, entity); +} - // free entity - U64 gen = t->v->gen + 1; - MemoryZeroStruct(t->v); - t->v->gen = gen; - SLLStackPush(dmn_lnx_state->free_entity, t->v); - } - scratch_end(scratch); +internal void +dmn_lnx_process_release(DMN_LNX_Process *process) +{ + // update global state + AssertAlways(dmn_lnx_state->process_count > 0); + DLLRemove(dmn_lnx_state->first_process, dmn_lnx_state->last_process, process); + dmn_lnx_state->process_count -= 1; + + // update pending process tracker + if(process->state != DMN_LNX_ProcessState_Normal) + { + Assert(dmn_lnx_state->process_pending_creation > 0); + dmn_lnx_state->process_pending_creation -= 1; } + + // close memory handle + if(close(process->fd) < 0) { Assert(0 && "failed to close memory descriptor"); } + + // remove pid mapping + hash_table_purge_u64(dmn_lnx_state->pid_ht, process->pid); + + // release the context + if(process->ctx) + { + dmn_lnx_process_ctx_release(process->ctx); + } + + // release process entity + dmn_lnx_entity_release((DMN_LNX_Entity *)process); +} + +internal void +dmn_lnx_process_ctx_release(DMN_LNX_ProcessCtx *ctx) +{ + Assert(ctx->ref_count > 0); + ctx->ref_count -= 1; + + if(ctx->ref_count == 0) + { + arena_release(ctx->arena); + dmn_lnx_entity_release((DMN_LNX_Entity *)ctx); + } +} + +internal void +dmn_lnx_thread_release(DMN_LNX_Thread *thread) +{ + DMN_LNX_Process *process = thread->process; + + // purge tid mapping + hash_table_purge_u64(dmn_lnx_state->tid_ht, thread->tid); + + // update global thread counter + if(thread->state == DMN_LNX_ThreadState_PendingCreation) + { + AssertAlways(dmn_lnx_state->threads_pending_creation > 0); + dmn_lnx_state->threads_pending_creation -= 1; + } + + // remove thread from the list + Assert(process->thread_count > 0); + DLLRemove(process->first_thread, process->last_thread, thread); + process->thread_count -= 1; + + // push reg block to the free list + String8Node *reg_block_node; + if(process->ctx->free_reg_block_nodes.node_count) + { + reg_block_node = str8_list_pop_front(&process->ctx->free_reg_block_nodes); + } + else + { + reg_block_node = push_array(process->ctx->arena, String8Node, 1); + } + reg_block_node->string = str8(thread->reg_block, 0); + str8_list_push_node(&process->ctx->free_reg_blocks, reg_block_node); + + dmn_lnx_entity_release((DMN_LNX_Entity *)thread); +} + +internal void +dmn_lnx_module_release(DMN_LNX_ProcessCtx *ctx, DMN_LNX_Module *module) +{ + // remove module from the list + Assert(ctx->module_count > 0); + DLLRemove(ctx->first_module, ctx->last_module, module); + ctx->module_count -= 1; + + // purge base addr -> module mapping + hash_table_purge_u64(ctx->loaded_modules_ht, module->base_vaddr); + + dmn_lnx_entity_release((DMN_LNX_Entity *)module); +} + +internal DMN_LNX_ProcessCtx * +dmn_lnx_process_ctx_clone(DMN_LNX_Process *new_owner, DMN_LNX_ProcessCtx *ctx) +{ + DMN_LNX_ProcessCtx *result = &dmn_lnx_entity_alloc(DMN_LNX_EntityKind_ProcessCtx)->process_ctx; + + result->arena = arena_alloc(); + result->arch = ctx->arch; + result->rdebug_vaddr = ctx->rdebug_vaddr; + result->dl_class = ctx->dl_class; + result->loaded_modules_ht = hash_table_init(result->arena, ctx->loaded_modules_ht->cap); + result->xcr0 = ctx->xcr0; + result->xsave_size = ctx->xsave_size; + result->xsave_layout = ctx->xsave_layout; + + // clone probes + result->probes = push_array(result->arena, DMN_LNX_Probe *, DMN_LNX_ProbeType_Count); + for EachIndex(probe_idx, DMN_LNX_ProbeType_Count) + { + DMN_LNX_Probe *dst = result->probes[probe_idx]; + DMN_LNX_Probe *src = ctx->probes[probe_idx]; + + dst->provider = str8_copy(result->arena, src->provider); + dst->name = str8_copy(result->arena, src->name); + dst->args_string = str8_copy(result->arena, src->args_string); + dst->args = stap_arg_array_copy(result->arena, src->args); + dst->pc = src->pc; + dst->semaphore = src->semaphore; + } + + // clone probe traps + for EachNode(src, DMN_ActiveTrap, ctx->first_probe_trap) + { + DMN_Trap *src_trap = src->trap; + DMN_Trap *dst_trap = push_array(result->arena, DMN_Trap, 1); + dst_trap->process = dmn_lnx_handle_from_process(new_owner); + dst_trap->vaddr = src_trap->vaddr; + dst_trap->id = src_trap->id; + dst_trap->flags = src_trap->flags; + dst_trap->size = src_trap->size; + + DMN_ActiveTrap *dst = push_array(result->arena, DMN_ActiveTrap, 1); + dst->trap = dst_trap; + dst->swap_bytes = str8_copy(result->arena, src->swap_bytes); + + SLLQueuePush(result->first_probe_trap, result->last_probe_trap, dst); + } + + // clone modules + for EachNode(module, DMN_LNX_Module, ctx->first_module) + { + dmn_lnx_module_clone(result, module); + } + + return result; +} + +internal DMN_LNX_Module * +dmn_lnx_module_clone(DMN_LNX_ProcessCtx *process_ctx, DMN_LNX_Module *module) +{ + DMN_LNX_Module *result = &dmn_lnx_entity_alloc(DMN_LNX_EntityKind_Module)->module; + *result = *module; + result->next = result->prev = 0; + + // clone base addr mapping + hash_table_push_u64_raw(process_ctx->arena, process_ctx->loaded_modules_ht, result->base_vaddr, result); + + // push module to the list + DLLPushBack(process_ctx->first_module, process_ctx->last_module, module); + process_ctx->module_count += 1; + + return result; } internal DMN_Handle dmn_lnx_handle_from_entity(DMN_LNX_Entity *entity) { DMN_Handle handle = {0}; - U64 index = (U64)(entity - dmn_lnx_state->entities_base); - if(index <= 0xffffffffu) + U64 index = IntFromPtr(entity - dmn_lnx_state->entities_base); + if(index <= max_U32) { handle.u32[0] = index; handle.u32[1] = entity->gen; } + else + { + Assert(0 && "failed to make a handle for the entity"); + } return handle; } -internal DMN_LNX_Entity * -dmn_lnx_entity_from_handle(DMN_Handle handle) +internal DMN_Handle +dmn_lnx_handle_from_process(DMN_LNX_Process *process) { - DMN_LNX_Entity *result = dmn_lnx_nil_entity; - U64 index = (U64)handle.u32[0]; - if(index < dmn_lnx_state->entities_count && - dmn_lnx_state->entities_base[index].gen == handle.u32[1]) + return dmn_lnx_handle_from_entity((DMN_LNX_Entity *)process); +} + +internal DMN_Handle +dmn_lnx_handle_from_process_ctx(DMN_LNX_ProcessCtx *process_ctx) +{ + return dmn_lnx_handle_from_entity((DMN_LNX_Entity *)process_ctx); +} + +internal DMN_Handle +dmn_lnx_handle_from_thread(DMN_LNX_Thread *thread) +{ + return dmn_lnx_handle_from_entity((DMN_LNX_Entity *)thread); +} + +internal DMN_Handle +dmn_lnx_handle_from_module(DMN_LNX_Module *module) +{ + return dmn_lnx_handle_from_entity((DMN_LNX_Entity *)module); +} + +internal DMN_LNX_Entity * +dmn_lnx_entity_from_handle(DMN_Handle handle, DMN_LNX_EntityKind expected_kind) +{ + DMN_LNX_Entity *result = 0; + U32 index = handle.u32[0]; + U32 gen = handle.u32[1]; + if(index < dmn_lnx_state->entities_count && dmn_lnx_state->entities_base[index].gen == gen) { - result = &dmn_lnx_state->entities_base[index]; + if(dmn_lnx_state->entities_base[index].kind == expected_kind) + { + result = &dmn_lnx_state->entities_base[index]; + } } return result; } -internal DMN_LNX_Entity * -dmn_lnx_thread_from_pid(pid_t pid) +internal DMN_LNX_Process * +dmn_lnx_process_from_handle(DMN_Handle process_handle) { - DMN_LNX_Entity *thread = hash_table_search_u64_raw(dmn_lnx_state->tid_ht, pid); - if(thread == 0) - { - thread = dmn_lnx_nil_entity; - } - return thread; + return (DMN_LNX_Process *)dmn_lnx_entity_from_handle(process_handle, DMN_LNX_EntityKind_Process); } -internal DMN_LNX_Entity * -dmn_lnx_process_from_pid(pid_t pid) +internal DMN_LNX_ProcessCtx * +dmn_lnx_process_ctx_from_handle(DMN_Handle process_ctx_handle) { - DMN_LNX_Entity *process = hash_table_search_u64_raw(dmn_lnx_state->pid_ht, pid); - if(process == 0) - { - process = dmn_lnx_nil_entity; - } - return process; + return (DMN_LNX_ProcessCtx *)dmn_lnx_entity_from_handle(process_ctx_handle, DMN_LNX_EntityKind_ProcessCtx); } -internal U64 -dmn_lnx_thread_read_ip(DMN_LNX_Entity *thread) +internal DMN_LNX_Thread * +dmn_lnx_thread_from_handle(DMN_Handle thread_handle) { - return regs_rip_from_arch_block(thread->arch, thread->reg_block); + return (DMN_LNX_Thread *)dmn_lnx_entity_from_handle(thread_handle, DMN_LNX_EntityKind_Thread); } -internal U64 -dmn_lnx_thread_read_sp(DMN_LNX_Entity *thread) +internal DMN_LNX_Module * +dmn_lnx_module_from_handle(DMN_Handle module_handle) { - return regs_rsp_from_arch_block(thread->arch, thread->reg_block); + return (DMN_LNX_Module *)dmn_lnx_entity_from_handle(module_handle, DMN_LNX_EntityKind_Module); } internal void -dmn_lnx_thread_write_ip(DMN_LNX_Entity *thread, U64 ip) +dmn_lnx_process_trap_probes(DMN_LNX_Process *process) { - regs_arch_block_write_rip(thread->arch, thread->reg_block, ip); + for EachIndex(i, DMN_LNX_ProbeType_Count) + { + if(process->ctx->probes[i] == 0) { continue; } + + DMN_Trap *trap = push_array(process->ctx->arena, DMN_Trap, 1); + trap->process = dmn_lnx_handle_from_process(process); + trap->vaddr = process->ctx->probes[i]->pc; + trap->id = i; + + DMN_ActiveTrap *active_trap = dmn_set_trap(process->ctx->arena, trap); + SLLQueuePush(process->ctx->first_probe_trap, process->ctx->last_probe_trap, active_trap); + + if(BUILD_DEBUG && process->ctx->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_Process *process) +{ + NotImplemented; +} + +internal U64 +dmn_lnx_thread_read_ip(DMN_LNX_Thread *thread) +{ + return regs_rip_from_arch_block(thread->process->ctx->arch, thread->reg_block); +} + +internal U64 +dmn_lnx_thread_read_sp(DMN_LNX_Thread *thread) +{ + return regs_rsp_from_arch_block(thread->process->ctx->arch, thread->reg_block); +} + +internal void +dmn_lnx_thread_write_ip(DMN_LNX_Thread *thread, U64 ip) +{ + regs_arch_block_write_rip(thread->process->ctx->arch, thread->reg_block, ip); thread->is_reg_block_dirty = 1; } internal void -dmn_lnx_thread_write_sp(DMN_LNX_Entity *thread, U64 sp) +dmn_lnx_thread_write_sp(DMN_LNX_Thread *thread, U64 sp) { - regs_arch_block_write_rsp(thread->arch, thread->reg_block, sp); + regs_arch_block_write_rsp(thread->process->ctx->arch, thread->reg_block, sp); thread->is_reg_block_dirty = 1; } internal B32 -dmn_lnx_thread_read_reg_block(DMN_LNX_Entity *thread) +dmn_lnx_thread_read_reg_block(DMN_LNX_Thread *thread) { - AssertAlways(dmn_lnx_state->tracer_tid == gettid()); - AssertAlways(thread->thread_state == DMN_LNX_ThreadState_Stopped); + AssertAlways(thread->state == DMN_LNX_ThreadState_Stopped); - B32 result = 0; - switch(thread->arch) + B32 is_reg_block_read = 0; + + switch(thread->process->ctx->arch) { - case Arch_Null: - case Arch_COUNT:{}break; - case Arch_x86: - case Arch_arm64: - case Arch_arm32: - {NotImplemented;}break; - - //////////////////////////// - //- rjf: [x64] - // + case Arch_Null: {} break; case Arch_x64: { - DMN_LNX_Entity *process = thread->parent; - pid_t tid = (pid_t)thread->id; - REGS_RegBlockX64 *dst = thread->reg_block; + DMN_LNX_ProcessCtx *process_ctx = thread->process->ctx; + REGS_RegBlockX64 *dst = thread->reg_block; - //- rjf: read GPR - B32 got_gpr = 0; + // general purpose registers { OS_LNX_GprsX64 src; - int ptrace_result = OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_GETREGSET, tid, (void *)NT_PRSTATUS, &(struct iovec){ .iov_len = sizeof(src), .iov_base = &src })); - if(ptrace_result >= 0) - { - got_gpr = 1; - dst->r15.u64 = src.r15; - dst->r14.u64 = src.r14; - dst->r13.u64 = src.r13; - dst->r12.u64 = src.r12; - dst->rbp.u64 = src.rbp; - dst->rbx.u64 = src.rbx; - dst->r11.u64 = src.r11; - dst->r10.u64 = src.r10; - dst->r9.u64 = src.r9; - dst->r8.u64 = src.r8; - dst->rax.u64 = src.rax; - dst->rcx.u64 = src.rcx; - dst->rdx.u64 = src.rdx; - dst->rsi.u64 = src.rsi; - dst->rdi.u64 = src.rdi; - dst->rip.u64 = src.rip; - dst->cs.u16 = src.cs; - dst->rflags.u64 = src.rflags; - dst->rsp.u64 = src.rsp; - dst->ss.u16 = src.ss; - dst->fsbase.u64 = src.fsbase; - dst->gsbase.u64 = src.gsbase; - dst->ds.u16 = src.ds; - dst->es.u16 = src.es; - dst->fs.u16 = src.fs; - dst->gs.u16 = src.gs; - thread->orig_rax = src.orig_rax; - } - else { Assert(0 && "failed to get gprs"); } + int ptrace_result = OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_GETREGSET, thread->tid, (void *)NT_PRSTATUS, &(struct iovec){ .iov_len = sizeof(src), .iov_base = &src })); + if(ptrace_result < 0) { goto exit; } + + dst->r15.u64 = src.r15; + dst->r14.u64 = src.r14; + dst->r13.u64 = src.r13; + dst->r12.u64 = src.r12; + dst->rbp.u64 = src.rbp; + dst->rbx.u64 = src.rbx; + dst->r11.u64 = src.r11; + dst->r10.u64 = src.r10; + dst->r9.u64 = src.r9; + dst->r8.u64 = src.r8; + dst->rax.u64 = src.rax; + dst->rcx.u64 = src.rcx; + dst->rdx.u64 = src.rdx; + dst->rsi.u64 = src.rsi; + dst->rdi.u64 = src.rdi; + dst->rip.u64 = src.rip; + dst->cs.u16 = src.cs; + dst->rflags.u64 = src.rflags; + dst->rsp.u64 = src.rsp; + dst->ss.u16 = src.ss; + dst->fsbase.u64 = src.fsbase; + dst->gsbase.u64 = src.gsbase; + dst->ds.u16 = src.ds; + dst->es.u16 = src.es; + dst->fs.u16 = src.fs; + dst->gs.u16 = src.gs; + thread->orig_rax = src.orig_rax; } - //- rjf: read FPR - B32 got_fpr = 0; - if(got_gpr) + // xsave { Temp scratch = scratch_begin(0, 0); @@ -842,26 +1212,22 @@ dmn_lnx_thread_read_reg_block(DMN_LNX_Entity *thread) // get xsave if(x64_is_xsave_supported()) { - void *xsave_raw = push_array(scratch.arena, U8, process->xsave_size); - int ptrace_result = OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_GETREGSET, tid, (void *)NT_X86_XSTATE, &(struct iovec){ .iov_len = process->xsave_size, .iov_base = xsave_raw })); - if(ptrace_result != -1) - { - xsave = xsave_raw; - fxsave = &xsave->fxsave; - } - else { Assert(0 && "failed to get xsave"); } + void *xsave_raw = push_array(scratch.arena, U8, process_ctx->xsave_size); + int ptrace_result = OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_GETREGSET, thread->tid, (void *)NT_X86_XSTATE, &(struct iovec){ .iov_len = process_ctx->xsave_size, .iov_base = xsave_raw })); + if(ptrace_result < 0) { goto exit; } + + xsave = xsave_raw; + fxsave = &xsave->fxsave; } // get fxsave - if (fxsave == 0) + if(fxsave == 0) { fxsave = push_array(scratch.arena, X64_FXSave, 1); - int ptrace_result = OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_GETREGSET, tid, (void *)NT_FPREGSET, &(struct iovec){ .iov_len = sizeof(*fxsave), .iov_base = fxsave })); - if(ptrace_result != -1) - { - fxsave = 0; - } - else { Assert(0 && "failed to get fxsave"); } + int ptrace_result = OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_GETREGSET, thread->tid, (void *)NT_FPREGSET, &(struct iovec){ .iov_len = sizeof(*fxsave), .iov_base = fxsave })); + if(ptrace_result < 0) { goto exit; } + + fxsave = 0; } // copy fxsave registers @@ -902,8 +1268,8 @@ dmn_lnx_thread_read_reg_block(DMN_LNX_Entity *thread) if(xsave->header.xstate_bv & X64_XStateComponentFlag_AVX) { - AssertAlways(process->xsave_layout.avx_offset + 16*sizeof(REGS_Reg128) <= process->xsave_size); - REGS_Reg128 *avx_s = (REGS_Reg128 *)((U8 *)xsave + process->xsave_layout.avx_offset); + AssertAlways(process_ctx->xsave_layout.avx_offset + 16*sizeof(REGS_Reg128) <= process_ctx->xsave_size); + REGS_Reg128 *avx_s = (REGS_Reg128 *)((U8 *)xsave + process_ctx->xsave_layout.avx_offset); REGS_Reg512 *zmm_d = &dst->zmm0; for EachIndex(n, 16) { @@ -913,8 +1279,8 @@ dmn_lnx_thread_read_reg_block(DMN_LNX_Entity *thread) if(xsave->header.xstate_bv & X64_XStateComponentFlag_OPMASK) { - AssertAlways(process->xsave_layout.opmask_offset + sizeof(REGS_Reg64) * 8 <= process->xsave_size); - REGS_Reg64 *kmask_s = (REGS_Reg64 *)((U8 *)xsave + process->xsave_layout.opmask_offset); + AssertAlways(process_ctx->xsave_layout.opmask_offset + sizeof(REGS_Reg64) * 8 <= process_ctx->xsave_size); + REGS_Reg64 *kmask_s = (REGS_Reg64 *)((U8 *)xsave + process_ctx->xsave_layout.opmask_offset); REGS_Reg64 *kmask_d = &dst->k0; for EachIndex(n, 8) { @@ -924,8 +1290,8 @@ dmn_lnx_thread_read_reg_block(DMN_LNX_Entity *thread) if(xsave->header.xstate_bv & X64_XStateComponentFlag_ZMM_H) { - AssertAlways(process->xsave_layout.zmm_h_offset + sizeof(REGS_Reg256) * 16 <= process->xsave_size); - REGS_Reg256 *avx512h_s = (REGS_Reg256 *)((U8 *)xsave + process->xsave_layout.zmm_h_offset); + AssertAlways(process_ctx->xsave_layout.zmm_h_offset + sizeof(REGS_Reg256) * 16 <= process_ctx->xsave_size); + REGS_Reg256 *avx512h_s = (REGS_Reg256 *)((U8 *)xsave + process_ctx->xsave_layout.zmm_h_offset); REGS_Reg512 *zmmh_d = &dst->zmm0; for EachIndex(n, 16) { @@ -935,8 +1301,8 @@ dmn_lnx_thread_read_reg_block(DMN_LNX_Entity *thread) if(xsave->header.xstate_bv & X64_XStateComponentFlag_ZMM) { - AssertAlways(process->xsave_layout.zmm_offset + sizeof(REGS_Reg512) * 16 <= process->xsave_size); - REGS_Reg512 *avx512_s = (REGS_Reg512 *)((U8 *)xsave + process->xsave_layout.zmm_offset); + AssertAlways(process_ctx->xsave_layout.zmm_offset + sizeof(REGS_Reg512) * 16 <= process_ctx->xsave_size); + REGS_Reg512 *avx512_s = (REGS_Reg512 *)((U8 *)xsave + process_ctx->xsave_layout.zmm_offset); REGS_Reg512 *zmm_d = &dst->zmm16; for EachIndex(n, 16) { @@ -946,22 +1312,18 @@ dmn_lnx_thread_read_reg_block(DMN_LNX_Entity *thread) if(xsave->header.xstate_bv & X64_XStateComponentFlag_CETU) { - AssertAlways(process->xsave_layout.cet_u_offset + sizeof(U64)*2 <= process->xsave_size); - U64 *cet_u = (U64 *)((U8 *)xsave + process->xsave_layout.cet_u_offset); + AssertAlways(process_ctx->xsave_layout.cet_u_offset + sizeof(U64)*2 <= process_ctx->xsave_size); + U64 *cet_u = (U64 *)((U8 *)xsave + process_ctx->xsave_layout.cet_u_offset); dst->cetmsr.u64 = cet_u[0]; dst->cetssp.u64 = cet_u[1]; } } - got_fpr = (xsave || fxsave); scratch_end(scratch); } - //- rjf: read debug registers - B32 got_debug = 0; - if(got_fpr) + // debug registers { - got_debug = 1; REGS_Reg64 *dr_d = &dst->dr0; for EachIndex(n, 8) { @@ -969,53 +1331,41 @@ dmn_lnx_thread_read_reg_block(DMN_LNX_Entity *thread) { U64 offset = OffsetOf(OS_LNX_UserX64, u_debugreg[n]); errno = 0; - long peek_result = OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_PEEKUSER, tid, PtrFromInt(offset), 0)); - if(errno == 0) - { - dr_d[n].u64 = (U64)peek_result; - } - else - { - got_debug = 0; - } + long peek_result = OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_PEEKUSER, thread->tid, PtrFromInt(offset), 0)); + if(errno != 0) { goto exit; } + dr_d[n].u64 = peek_result; } } } - - result = got_debug; - }break; + + is_reg_block_read = 1; + } break; + case Arch_x86: + case Arch_arm64: + case Arch_arm32: { NotImplemented; } break; + default: { InvalidPath; } break; } - return result; +exit:; + return is_reg_block_read; } internal B32 -dmn_lnx_thread_write_reg_block(DMN_LNX_Entity *thread) +dmn_lnx_thread_write_reg_block(DMN_LNX_Thread *thread) { - AssertAlways(dmn_lnx_state->tracer_tid == thread->parent->tracer_tid); - AssertAlways(thread->thread_state == DMN_LNX_ThreadState_Stopped); + AssertAlways(thread->state == DMN_LNX_ThreadState_Stopped); - B32 result = 0; - switch(thread->arch) + B32 is_reg_block_written = 0; + + switch(thread->process->ctx->arch) { - case Arch_Null: - case Arch_COUNT:{}break; - case Arch_arm64: - case Arch_arm32: - case Arch_x86: - {NotImplemented;}break; - - //////////////////////////// - //- rjf: [x64] - // + case Arch_Null: {} break; case Arch_x64: { - DMN_LNX_Entity *process = thread->parent; - pid_t tid = (pid_t)thread->id; - REGS_RegBlockX64 *src = thread->reg_block; + DMN_LNX_ProcessCtx *process_ctx = thread->process->ctx; + REGS_RegBlockX64 *src = thread->reg_block; - //- rjf: write GPR - B32 did_gpr = 0; + // general purpose registers { OS_LNX_GprsX64 dst; dst.r15 = src->r15.u64; @@ -1045,17 +1395,14 @@ dmn_lnx_thread_write_reg_block(DMN_LNX_Entity *thread) dst.es = src->es.u16; dst.fs = src->fs.u16; dst.gs = src->gs.u16; - did_gpr = OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_SETREGSET, tid, (void *)NT_PRSTATUS, &(struct iovec){ .iov_base = &dst, .iov_len = sizeof(dst) }) >= 0); + int ptrace_result = OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_SETREGSET, thread->tid, (void *)NT_PRSTATUS, &(struct iovec){ .iov_base = &dst, .iov_len = sizeof(dst) })); + if(ptrace_result < 0) { goto exit; } } - B32 did_fpr = 0; - if(did_gpr) + // xsave { Temp scratch = scratch_begin(0, 0); - int xsave_result = -1; - int fxsave_result = -1; - X64_FXSave dst_fxsave = {0}; { dst_fxsave.fcw = src->fcw.u16; @@ -1084,17 +1431,17 @@ dmn_lnx_thread_write_reg_block(DMN_LNX_Entity *thread) if(x64_is_xsave_supported()) { - U8 *xsave_raw = push_array(scratch.arena, U8, process->xsave_size); + U8 *xsave_raw = push_array(scratch.arena, U8, process_ctx->xsave_size); X64_XSave *dst = (X64_XSave *)xsave_raw; dst->fxsave = dst_fxsave; dst->header.xstate_bv |= X64_XStateComponentFlag_FP; dst->header.xstate_bv |= X64_XStateComponentFlag_SSE; - if(process->xsave_layout.avx_offset) + if(process_ctx->xsave_layout.avx_offset) { - if(process->xsave_layout.avx_offset + sizeof(REGS_Reg128) * 16 <= process->xsave_size) + if(process_ctx->xsave_layout.avx_offset + sizeof(REGS_Reg128) * 16 <= process_ctx->xsave_size) { - REGS_Reg128 *avx_d = (REGS_Reg128 *)(xsave_raw + process->xsave_layout.avx_offset); + REGS_Reg128 *avx_d = (REGS_Reg128 *)(xsave_raw + process_ctx->xsave_layout.avx_offset); REGS_Reg512 *zmm_s = &src->zmm0; for EachIndex(n, 16) { @@ -1104,11 +1451,11 @@ dmn_lnx_thread_write_reg_block(DMN_LNX_Entity *thread) } } - if(process->xsave_layout.opmask_offset) + if(process_ctx->xsave_layout.opmask_offset) { - if(process->xsave_layout.opmask_offset + sizeof(REGS_Reg64) * 8 <= process->xsave_size) + if(process_ctx->xsave_layout.opmask_offset + sizeof(REGS_Reg64) * 8 <= process_ctx->xsave_size) { - REGS_Reg64 *kmask_d = (REGS_Reg64 *)(xsave_raw + process->xsave_layout.opmask_offset); + REGS_Reg64 *kmask_d = (REGS_Reg64 *)(xsave_raw + process_ctx->xsave_layout.opmask_offset); REGS_Reg64 *kmask_s = &src->k0; for EachIndex(n, 8) { @@ -1116,14 +1463,14 @@ dmn_lnx_thread_write_reg_block(DMN_LNX_Entity *thread) } dst->header.xstate_bv |= X64_XStateComponentFlag_OPMASK; } - else { Assert(0 && "invalid xsave size"); } + else { Assert(0 && "invalid xsave size"); goto exit; } } - if(process->xsave_layout.zmm_h_offset) + if(process_ctx->xsave_layout.zmm_h_offset) { - if(process->xsave_layout.zmm_h_offset + sizeof(REGS_Reg256) * 16 <= process->xsave_size) + if(process_ctx->xsave_layout.zmm_h_offset + sizeof(REGS_Reg256) * 16 <= process_ctx->xsave_size) { - REGS_Reg256 *avx512h_d = (REGS_Reg256 *)(xsave_raw + process->xsave_layout.zmm_h_offset); + REGS_Reg256 *avx512h_d = (REGS_Reg256 *)(xsave_raw + process_ctx->xsave_layout.zmm_h_offset); REGS_Reg512 *zmmh_s = &src->zmm0; for EachIndex(n, 16) { @@ -1131,14 +1478,14 @@ dmn_lnx_thread_write_reg_block(DMN_LNX_Entity *thread) } dst->header.xstate_bv |= X64_XStateComponentFlag_ZMM_H; } - else { Assert(0 && "invalid xsave size"); } + else { Assert(0 && "invalid xsave size"); goto exit; } } - if(process->xsave_layout.zmm_offset) + if(process_ctx->xsave_layout.zmm_offset) { - if(process->xsave_layout.zmm_offset + sizeof(REGS_Reg512) * 16 <= process->xsave_size) + if(process_ctx->xsave_layout.zmm_offset + sizeof(REGS_Reg512) * 16 <= process_ctx->xsave_size) { - REGS_Reg512 *avx512_d = (REGS_Reg512 *)(xsave_raw + process->xsave_layout.zmm_offset); + REGS_Reg512 *avx512_d = (REGS_Reg512 *)(xsave_raw + process_ctx->xsave_layout.zmm_offset); REGS_Reg512 *zmm_s = &src->zmm16; for EachIndex(n, 16) { @@ -1146,46 +1493,32 @@ dmn_lnx_thread_write_reg_block(DMN_LNX_Entity *thread) } dst->header.xstate_bv |= X64_XStateComponentFlag_ZMM; } - else { Assert(0 && "invalid xsave size"); } + else { Assert(0 && "invalid xsave size"); goto exit; } } - if(process->xsave_layout.cet_u_offset) + if(process_ctx->xsave_layout.cet_u_offset) { - if(process->xsave_layout.cet_u_offset + sizeof(REGS_Reg64) * 2 <= process->xsave_size) + if(process_ctx->xsave_layout.cet_u_offset + sizeof(REGS_Reg64) * 2 <= process_ctx->xsave_size) { - REGS_Reg64 *cet_u = (REGS_Reg64 *)(xsave_raw + process->xsave_layout.cet_u_offset); + REGS_Reg64 *cet_u = (REGS_Reg64 *)(xsave_raw + process_ctx->xsave_layout.cet_u_offset); cet_u[0] = src->cetmsr; cet_u[1] = src->cetssp; dst->header.xstate_bv |= X64_XStateComponentFlag_CETU; } - else { Assert(0 && "invalid xsave size"); } + else { Assert(0 && "invalid xsave size"); goto exit; } } // xsave Assert(dst->header.xcomp_bv == 0); // must always be zero - xsave_result = OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_SETREGSET, tid, (void *)NT_X86_XSTATE, &(struct iovec){ .iov_base = dst, .iov_len = process->xsave_size })); - Assert(xsave_result >= 0); + int ptrace_result = OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_SETREGSET, thread->tid, (void *)NT_X86_XSTATE, &(struct iovec){ .iov_base = dst, .iov_len = process_ctx->xsave_size })); + if(ptrace_result < 0) { goto exit; } } - // fallback to fxsave - if(xsave_result < 0) - { - fxsave_result = OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_SETREGSET, tid, (void *)NT_FPREGSET, &(struct iovec){ .iov_base = &dst_fxsave, sizeof(dst_fxsave) })); - Assert(fxsave_result >= 0); - } - - // rjf: good finish requires xsave or fxsave - did_fpr = (xsave_result >= 0 || fxsave_result >= 0); - scratch_end(scratch); } - //- rjf: write debug registers - B32 did_dbg = 0; - if(did_fpr) + // debug registers { - did_dbg = 1; - src->dr7.u64 |= (1 << 10); REGS_Reg64 *dr_s = &src->dr0; @@ -1194,478 +1527,454 @@ dmn_lnx_thread_write_reg_block(DMN_LNX_Entity *thread) if(n != 4 && n != 5) { U64 offset = OffsetOf(OS_LNX_UserX64, u_debugreg[n]); - int poke_result = OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_POKEUSER, tid, PtrFromInt(offset), (void*)(uintptr_t)dr_s[n].u64)); - if(poke_result < 0) - { - did_dbg = 0; - break; - } + int ptrace_result = OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_POKEUSER, thread->tid, PtrFromInt(offset), (void*)(uintptr_t)dr_s[n].u64)); + if(ptrace_result < 0) { goto exit; } } } } - result = (did_dbg); - }break; + is_reg_block_written = 1; + } break; + case Arch_arm64: + case Arch_arm32: + case Arch_x86: { NotImplemented; }break; + default: { InvalidPath; } break; } - return result; + +exit:; + return is_reg_block_written; } internal B32 -dmn_lnx_set_single_step_flag(DMN_LNX_Entity *thread, B32 is_on) +dmn_lnx_set_single_step_flag(DMN_LNX_Thread *thread, B32 is_on) { B32 is_flag_set = 0; - switch(thread->arch) + switch(thread->process->ctx->arch) { - case Arch_COUNT: - case Arch_Null: {} break; - case Arch_x64: - { - REGS_RegBlockX64 *reg_block = thread->reg_block; - if(is_on) { reg_block->rflags.u64 |= X64_RFlag_Trap; } - else { reg_block->rflags.u64 &= ~X64_RFlag_Trap; } - thread->is_reg_block_dirty = 1; - is_flag_set = 1; - }break; - case Arch_x86: - case Arch_arm32: - case Arch_arm64: - { - NotImplemented; - }break; + case Arch_Null: {} break; + case Arch_x64: + { + REGS_RegBlockX64 *reg_block = thread->reg_block; + if(is_on) { reg_block->rflags.u64 |= X64_RFlag_Trap; } + else { reg_block->rflags.u64 &= ~X64_RFlag_Trap; } + thread->is_reg_block_dirty = 1; + is_flag_set = 1; + } break; + case Arch_x86: + case Arch_arm32: + case Arch_arm64: { NotImplemented; }break; + default: { InvalidPath; } break; } Assert(is_flag_set); return is_flag_set; } -//////////////////////////////// +internal void +dmn_lnx_thread_ptr_list_push_node(DMN_LNX_ThreadPtrList *list, DMN_LNX_ThreadPtrNode *n) +{ + DLLPushBack(list->first, list->last, n); + list->count += 1; +} + +internal DMN_LNX_ThreadPtrNode * +dmn_lnx_thread_ptr_list_push(Arena *arena, DMN_LNX_ThreadPtrList *list, DMN_LNX_Thread *v) +{ + DMN_LNX_ThreadPtrNode *n = push_array(arena, DMN_LNX_ThreadPtrNode, 1); + n->v = v; + dmn_lnx_thread_ptr_list_push_node(list, n); + return n; +} internal void -dmn_lnx_handle_not_attached(Arena *arena, DMN_EventList *events) +dmn_lnx_thread_ptr_list_remove(DMN_LNX_ThreadPtrList *list, DMN_LNX_ThreadPtrNode *n) +{ + DLLRemove(list->first, list->last, n); + list->count -= 1; +} + +internal DMN_LNX_ModulePtrNode * +dmn_lnx_module_ptr_list_push(Arena *arena, DMN_LNX_ModulePtrList *list, DMN_LNX_Module *v) +{ + DMN_LNX_ModulePtrNode *n = push_array(arena, DMN_LNX_ModulePtrNode, 1); + n->v = v; + SLLQueuePush(list->first, list->last, n); + list->count += 1; + return n; +} + +internal DMN_LNX_ProcessPtrNode * +dmn_lnx_process_ptr_list_push(Arena *arena, DMN_LNX_ProcessPtrList *list, DMN_LNX_Process *v) +{ + DMN_LNX_ProcessPtrNode *n = push_array(arena, DMN_LNX_ProcessPtrNode, 1); + n->v = v; + SLLQueuePush(list->first, list->last, n); + list->count += 1; + return n; +} + +internal void +dmn_lnx_push_event_create_process(Arena *arena, DMN_EventList *events, DMN_LNX_Process *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_process(process); + e->arch = process->ctx->arch; + e->code = process->pid; + e->tls_model = DMN_TlsModel_Gnu; // TODO: use dynamic linker path to figure out correct enum here +} + +internal void +dmn_lnx_push_event_exit_process(Arena *arena, DMN_EventList *events, DMN_LNX_Process *process) +{ + DMN_Event *e = dmn_event_list_push(arena, events); + e->kind = DMN_EventKind_ExitProcess; + e->process = dmn_lnx_handle_from_process(process); + e->code = process->main_thread_exit_code; +} + +internal void +dmn_lnx_push_event_create_thread(Arena *arena, DMN_EventList *events, DMN_LNX_Thread *thread) +{ + DMN_Event *e = dmn_event_list_push(arena, events); + e->kind = DMN_EventKind_CreateThread; + e->process = dmn_lnx_handle_from_process(thread->process); + e->thread = dmn_lnx_handle_from_thread(thread); + e->arch = thread->process->ctx->arch; + e->code = thread->tid; +} + +internal void +dmn_lnx_push_event_exit_thread(Arena *arena, DMN_EventList *events, DMN_LNX_Thread *thread, U64 exit_code) +{ + DMN_Event *e = dmn_event_list_push(arena, events); + e->kind = DMN_EventKind_ExitThread; + e->process = dmn_lnx_handle_from_process(thread->process); + e->thread = dmn_lnx_handle_from_thread(thread); + e->code = exit_code; +} + +internal void +dmn_lnx_push_event_load_module(Arena *arena, DMN_EventList *events, DMN_LNX_Thread *thread, DMN_LNX_Module *module) +{ + // TODO: reporting this module breaks ctrl thread + String8 module_name = dmn_lnx_read_string(arena, thread->process->fd, module->name_vaddr); + if(!str8_match(module_name, str8_lit("linux-vdso.so.1"), 0)) + { + DMN_Event *e = dmn_event_list_push(arena, events); + e->kind = DMN_EventKind_LoadModule; + e->process = dmn_lnx_handle_from_process(thread->process); + e->thread = dmn_lnx_handle_from_thread(thread); + e->module = dmn_lnx_handle_from_module(module); + e->arch = thread->process->ctx->arch; + e->address = module->base_vaddr; + e->size = module->size; + e->string = module_name; + e->elf_phdr_vrange = r1u64(module->phvaddr, module->phvaddr + module->phentsize * module->phcount); + e->elf_phdr_entsize = module->phentsize; + e->tls_index = module->tls_index; + e->tls_offset = module->tls_offset; + } +} + +internal void +dmn_lnx_push_event_unload_module(Arena *arena, DMN_EventList *events, DMN_LNX_Process *process, DMN_LNX_Module *module) +{ + DMN_Event *e = dmn_event_list_push(arena, events); + e->kind = DMN_EventKind_UnloadModule; + e->process = dmn_lnx_handle_from_process(process); + e->module = dmn_lnx_handle_from_module(module); +} + +internal void +dmn_lnx_push_event_handshake_complete(Arena *arena, DMN_EventList *events, DMN_LNX_Process *process) +{ + DMN_Event *e = dmn_event_list_push(arena, events); + e->kind = DMN_EventKind_HandshakeComplete; + e->process = dmn_lnx_handle_from_process(process); + e->thread = dmn_lnx_handle_from_thread(process->first_thread); + e->arch = process->ctx->arch; +} + +internal void +dmn_lnx_push_event_breakpoint(Arena *arena, DMN_EventList *events, DMN_LNX_Thread *thread, U64 address) +{ + DMN_Event *e = dmn_event_list_push(arena, events); + e->kind = DMN_EventKind_Breakpoint; + e->process = dmn_lnx_handle_from_process(thread->process); + e->thread = dmn_lnx_handle_from_thread(thread); + e->instruction_pointer = address; +} + +internal void +dmn_lnx_push_event_single_step(Arena *arena, DMN_EventList *events, DMN_LNX_Thread *thread) +{ + DMN_Event *e = dmn_event_list_push(arena, events); + e->kind = DMN_EventKind_SingleStep; + e->process = dmn_lnx_handle_from_process(thread->process); + e->thread = dmn_lnx_handle_from_thread(thread); + e->instruction_pointer = dmn_lnx_thread_read_ip(thread); +} + +internal void +dmn_lnx_push_event_exception(Arena *arena, DMN_EventList *events, DMN_LNX_Thread *thread, U64 signo) +{ + 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 + }; + + DMN_Event *e = dmn_event_list_push(arena, events); + e->kind = DMN_EventKind_Exception; + e->process = dmn_lnx_handle_from_process(thread->process); + e->thread = dmn_lnx_handle_from_thread(thread); + e->instruction_pointer = dmn_lnx_thread_read_ip(thread); + e->signo = signo; + e->exception_repeated = signo < ArrayCount(is_repeatable) ? is_repeatable[signo] : 0; + + if(signo == SIGSEGV) + { + siginfo_t si = {0}; + OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_GETSIGINFO, thread->tid, 0, &si)); + e->address = (U64)si.si_addr; + } +} + +internal void +dmn_lnx_push_event_halt(Arena *arena, DMN_EventList *events) +{ + DMN_Event *e = dmn_event_list_push(arena, events); + e->kind = DMN_EventKind_Halt; +} + +internal void +dmn_lnx_push_event_not_attached(Arena *arena, DMN_EventList *events) { DMN_Event *e = dmn_event_list_push(arena, events); e->kind = DMN_EventKind_Error; e->error_kind = DMN_ErrorKind_NotAttached; } -internal void -dmn_lnx_init_create_thread(Arena *arena, DMN_EventList *events, DMN_LNX_Entity *process, DMN_LNX_Entity *thread) +internal DMN_LNX_Thread * +dmn_lnx_event_create_thread(Arena *arena, DMN_EventList *events, DMN_LNX_Process *process, pid_t tid) { - thread->arch = process->arch; - thread->reg_block = push_array(process->arena, U8, regs_block_size_from_arch(process->arch)); - thread->thread_state = DMN_LNX_ThreadState_Stopped; - thread->is_reg_block_dirty = !dmn_lnx_thread_read_reg_block(thread); - hash_table_push_u64_raw(dmn_lnx_state->arena, dmn_lnx_state->tid_ht, thread->id, thread); - - if(!thread->is_reg_block_dirty) - { - switch(thread->arch) - { - case Arch_Null: break; - case Arch_x64: thread->thread_local_base = ((REGS_RegBlockX64 *)thread->reg_block)->fsbase.u64; break; - case Arch_x86: - case Arch_arm32: - case Arch_arm64: { NotImplemented; } break; - default: { InvalidPath; } break; - } - } - - DMN_Event *e = dmn_event_list_push(arena, events); - e->kind = DMN_EventKind_CreateThread; - e->process = dmn_lnx_handle_from_entity(process); - e->thread = dmn_lnx_handle_from_entity(thread); - e->arch = thread->arch; - e->code = thread->id; - - process->thread_count += 1; -} - -internal DMN_LNX_Entity * -dmn_lnx_handle_create_thread(Arena *arena, DMN_EventList *events, DMN_LNX_Entity *process, pid_t tid) -{ - DMN_LNX_Entity *thread = dmn_lnx_entity_alloc(process, DMN_LNX_EntityKind_Thread); - thread->id = tid; - dmn_lnx_init_create_thread(arena, events, process, thread); + DMN_LNX_Thread *thread = dmn_lnx_thread_alloc(process, DMN_LNX_ThreadState_Stopped, tid); + dmn_lnx_push_event_create_thread(arena, events, thread); return thread; } -internal DMN_LNX_Entity * -dmn_lnx_handle_create_process(Arena *arena, DMN_EventList *events, B32 debug_subprocesses, B32 is_rebased, pid_t pid) +internal void +dmn_lnx_event_exit_thread(Arena *arena, DMN_EventList *events, pid_t tid, U64 exit_code) { - Temp scratch = scratch_begin(&arena, 1); + DMN_LNX_Thread *thread = dmn_lnx_thread_from_pid(tid); + DMN_LNX_Process *process = thread->process; - DMN_LNX_Entity *process = 0; - - ELF_Hdr64 exe_ehdr = dmn_lnx_ehdr_from_pid(pid); - int memory_fd = open((char*)str8f(scratch.arena, "/proc/%d/mem", pid).str, O_RDWR); - DMN_LNX_ProcessAuxv auxv = dmn_lnx_auxv_from_pid(pid, exe_ehdr.e_ident[ELF_Identifier_Class]); - Arch arch = arch_from_elf_machine(exe_ehdr.e_machine); - U64 rdebug_vaddr = dmn_lnx_rdebug_vaddr_from_memory(memory_fd, auxv.base, is_rebased); - U64 base_vaddr = (auxv.phdr & ~(auxv.pagesz-1)); - U64 rebase = exe_ehdr.e_type == ELF_Type_Dyn ? base_vaddr : 0; - Rng1U64 image_vrange = dmn_lnx_compute_image_vrange(memory_fd, exe_ehdr.e_ident[ELF_Identifier_Class], rebase, auxv.phdr, auxv.phent, auxv.phnum); - Arena *process_arena = arena_alloc(); - - ELF_Class dl_class; + // store main thread's exit code + if(thread->tid == thread->process->pid) { - ELF_Hdr64 ehdr = {0}; - if(elf_read_ehdr(dmn_lnx_machine_op_mem_read, &memory_fd, auxv.base, &ehdr) != MachineOpResult_Ok) { Assert(0 && "failed to read interp's header"); } - dl_class = ehdr.e_ident[ELF_Identifier_Class]; + thread->process->main_thread_exit_code = exit_code; } - // - // query xsave layout - // - U64 xcr0 = 0; - U64 xsave_size = 0; - X64_XSaveLayout xsave_layout = {0}; - if(arch == Arch_x64) + // push exit event + dmn_lnx_push_event_exit_thread(arena, events, thread, exit_code); + + // release entity + dmn_lnx_thread_release(thread); + + // auto exit process on last thread + if(process->thread_count == 0) { - X64_XSave xsave = {0}; - if(OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_GETREGSET, pid, (void*)NT_X86_XSTATE, &(struct iovec){.iov_base = &xsave, .iov_len = sizeof(xsave) }) >= 0)) - { - // Linux stores xcr0 bits in fxstate padding, - // see https://github.com/torvalds/linux/blob/6548d364a3e850326831799d7e3ea2d7bb97ba08/arch/x86/include/asm/user.h#L25 - xcr0 = *(U64 *)((U8 *)&xsave + 464); - xsave_size = x64_get_xsave_size(); - xsave_layout = x64_get_xsave_layout(xcr0); - } - else { Assert(0 && "failed to get xstate"); } + dmn_lnx_event_exit_process(arena, events, process->pid); + } +} + +internal DMN_LNX_Process * +dmn_lnx_event_create_process(Arena *arena, DMN_EventList *events, pid_t pid, DMN_LNX_Process *parent_process, DMN_LNX_CreateProcessFlags flags) +{ + DMN_LNX_Process *process = dmn_lnx_process_alloc(pid, DMN_LNX_ProcessState_Normal, parent_process, !!(flags & DMN_LNX_CreateProcessFlag_DebugSubprocesses), !!(flags & DMN_LNX_CreateProcessFlag_Cow)); + + if(flags & DMN_LNX_CreateProcessFlag_ClonedMemory) + { + process->ctx = parent_process->ctx; + } + else + { + process->ctx = dmn_lnx_process_ctx_alloc(process, !!(flags & DMN_LNX_CreateProcessFlag_Rebased)); + } + process->ctx->ref_count += 1; + + // install probes in a process that does not have a cloned memory + if(!(flags & DMN_LNX_CreateProcessFlag_ClonedMemory)) + { + dmn_lnx_process_trap_probes(process); } - // - // gather probes - // - DMN_LNX_Probe **known_probes = push_array(process_arena, DMN_LNX_Probe *, DMN_LNX_ProbeType_Count); + // create main thread + dmn_lnx_thread_alloc(process, DMN_LNX_ThreadState_Stopped, pid); + + // push events + dmn_lnx_push_event_create_process(arena, events, process); + for EachNode(thread, DMN_LNX_Thread, process->first_thread) { - String8 dl_path = dmn_lnx_dl_path_from_pid(scratch.arena, pid, auxv.base); - int dl_fd = open((char *)dl_path.str, O_RDONLY); - - DMN_LNX_ProbeList probes = {0}; - if(dl_fd >= 0) - { - probes = dmn_lnx_read_probes(process_arena, dl_fd, 0, auxv.base); - close(dl_fd); - } - - for EachNode(n, DMN_LNX_ProbeNode, probes.first) - { - DMN_LNX_Probe *p = &n->v; - if(str8_match(p->provider, str8_lit("rtld"), 0)) - { -#define X(_N,_A,_S) if(str8_match(p->name, str8_lit(_S), 0)) { AssertAlways(p->args.count == _A); known_probes[DMN_LNX_ProbeType_##_N] = p; continue ; } - DMN_LNX_Probe_XList -#undef X - } - } + dmn_lnx_push_event_create_thread(arena, events, thread); } - - // - // init process - // - process = dmn_lnx_entity_alloc(dmn_lnx_state->entities_base, DMN_LNX_EntityKind_Process); - process->id = pid; - process->arch = arch; - process->fd = memory_fd; - process->tracer_tid = gettid(); - process->debug_subprocesses = debug_subprocesses; - process->rdebug_vaddr = rdebug_vaddr; - process->dl_class = dl_class; - process->arena = process_arena; - process->loaded_modules_ht = hash_table_init(process_arena, 0x1000); - process->probes = known_probes; - process->xcr0 = xcr0; - process->xsave_size = Max(xsave_size, sizeof(X64_XSave)); - process->xsave_layout = xsave_layout; - 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) + for EachNode(module, DMN_LNX_Module, process->ctx->first_module) { - 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); - } + dmn_lnx_push_event_load_module(arena, events, process->first_thread, module); } + dmn_lnx_push_event_handshake_complete(arena, events, 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 - // - DMN_LNX_Entity *thread = dmn_lnx_handle_create_thread(arena, events, process, pid); - - // - // init main module - // - { - 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); - - // push load module event - { - DMN_Event *e = dmn_event_list_push(arena, events); - e->kind = DMN_EventKind_LoadModule; - e->process = dmn_lnx_handle_from_entity(process); - e->thread = dmn_lnx_handle_from_entity(thread); - e->module = dmn_lnx_handle_from_entity(module); - e->arch = process->arch; - e->address = base_vaddr; - e->size = dim_1u64(image_vrange); - e->string = dmn_lnx_read_string(arena, process->fd, auxv.execfn); - e->elf_phdr_vrange = r1u64(auxv.phdr, auxv.phdr + auxv.phent * auxv.phnum); - e->elf_phdr_entsize = auxv.phent; - e->tls_index = 1; - e->tls_offset = 0; - } - } - - // - // handshake complete - // - { - DMN_Event *e = dmn_event_list_push(arena, events); - e->kind = DMN_EventKind_HandshakeComplete; - e->process = dmn_lnx_handle_from_entity(process); - e->thread = dmn_lnx_handle_from_entity(thread); - e->arch = process->arch; - } - - // - // update global state - // - dmn_lnx_state->active_process_count += 1; - - scratch_end(scratch); return process; } internal void -dmn_lnx_handle_exit_process(Arena *arena, DMN_EventList *events, pid_t pid) +dmn_lnx_event_exit_process(Arena *arena, DMN_EventList *events, pid_t pid) { - DMN_LNX_Entity *process = dmn_lnx_process_from_pid(pid); + DMN_LNX_Process *process = dmn_lnx_process_from_pid(pid); + AssertAlways(process->thread_count == 0); - // rjf: generate unload-module events - for(DMN_LNX_Entity *child = process->first; child != dmn_lnx_nil_entity; child = child->next) + // push module events + for EachNode(module, DMN_LNX_Module, process->ctx->first_module) { - if(child->kind == DMN_LNX_EntityKind_Module) - { - DMN_Event *e = dmn_event_list_push(arena, events); - e->kind = DMN_EventKind_UnloadModule; - e->process = dmn_lnx_handle_from_entity(process); - e->module = dmn_lnx_handle_from_entity(child); - // TODO(rjf): e->string = ...; - } + dmn_lnx_push_event_unload_module(arena, events, process, module); } - // rjf: generate exit process event - { - DMN_Event *e = dmn_event_list_push(arena, events); - e->kind = DMN_EventKind_ExitProcess; - e->process = dmn_lnx_handle_from_entity(process); - e->code = process->main_thread_exit_code; - } + // push process exit event + dmn_lnx_push_event_exit_process(arena, events, process); - // free process - if(close(process->fd) < 0) { Assert(0 && "failed to close memory descriptor"); } - arena_release(process->arena); - - // rjf: eliminate entity tree - hash_table_purge_u64(dmn_lnx_state->pid_ht, process->id); - dmn_lnx_entity_release(process); - - // - // update global state - // - AssertAlways(dmn_lnx_state->active_process_count > 0); - dmn_lnx_state->active_process_count -= 1; + // release process + dmn_lnx_process_release(process); } internal void -dmn_lnx_handle_exit_thread(Arena *arena, DMN_EventList *events, pid_t tid, U64 exit_code) +dmn_lnx_event_load_module(Arena *arena, DMN_EventList *events, DMN_LNX_Thread *thread, U64 name_space_id, U64 new_link_map_vaddr) { - DMN_LNX_Entity *thread = dmn_lnx_thread_from_pid(tid); - DMN_LNX_Entity *process = thread->parent; + DMN_LNX_Process *process = thread->process; - // store main thread's exit code - if(thread->id == process->id) - { - process->main_thread_exit_code = exit_code; - } - - DMN_Event *e = dmn_event_list_push(arena, events); - e->kind = DMN_EventKind_ExitThread; - e->process = dmn_lnx_handle_from_entity(process); - e->thread = dmn_lnx_handle_from_entity(thread); - hash_table_purge_u64(dmn_lnx_state->tid_ht, thread->id); - dmn_lnx_entity_release(thread); - process->thread_count -= 1; - - if(process->thread_count == 0) - { - dmn_lnx_handle_exit_process(arena, events, process->id); - } -} - -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) -{ - DMN_LNX_Entity *module = 0; - - // parse out module's ELF header - ELF_Hdr64 module_ehdr = {0}; - if(elf_read_ehdr(dmn_lnx_machine_op_mem_read, &process->fd, base_vaddr, &module_ehdr) != MachineOpResult_Ok) { goto exit; } - - // gather info about module - U64 module_rebase = module_ehdr.e_type == ELF_Type_Dyn ? base_vaddr : 0; - U64 module_phdr_vaddr = module_rebase + module_ehdr.e_phoff; - Rng1U64 module_vrange = dmn_lnx_compute_image_vrange(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, module_name_vaddr); - - // read TLS index and TLS offset - U64 tls_index = max_U64; - U64 tls_offset = max_U64; - if(dmn_lnx_state->is_tls_detected) - { - 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, 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 = 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; } - - // 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 = base_vaddr; - e->size = dim_1u64(module_vrange); - 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(gnu_read_link_map(dmn_lnx_machine_op_mem_read, &process->fd, map_vaddr, process->dl_class, &map) != MachineOpResult_Ok) { break; } + if(gnu_read_link_map(dmn_lnx_machine_op_mem_read, &process->fd, map_vaddr, process->ctx->dl_class, &map) != MachineOpResult_Ok) { 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) + // was module already loaded? + DMN_LNX_Module *module = hash_table_search_u64_raw(process->ctx->loaded_modules_ht, map.addr_vaddr); + if(module) { continue; } + + // clone process ctx + if(process->is_cow) { - // 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); + process->is_cow = 0; + process->ctx = dmn_lnx_process_ctx_clone(process, process->ctx); } + + // alloc module + module = dmn_lnx_module_alloc(process->ctx, process->fd, map.addr_vaddr, map.name_vaddr, name_space_id, 0); + + // push load module event + dmn_lnx_push_event_load_module(arena, events, thread, module); } } internal void -dmn_lnx_handle_unload_module(Arena *arena, DMN_EventList *events, DMN_LNX_Entity *process, U64 name_space_id, U64 rdebug_vaddr) +dmn_lnx_event_unload_module(Arena *arena, DMN_EventList *events, DMN_LNX_Process *process, U64 rdebug_vaddr) { Temp scratch = scratch_begin(&arena, 1); + DMN_LNX_ProcessCtx *ctx = process->ctx; + // flag every module as inactive - for(DMN_LNX_Entity *module = process->first; module != dmn_lnx_nil_entity; module = module->next) + for EachNode(module, DMN_LNX_Module, ctx->first_module) { - if(module->kind != DMN_LNX_EntityKind_Module) { continue; } module->is_live = 0; } // mark live modules - B32 is_64bit = process->dl_class == ELF_Class_64; + B32 is_64bit = ctx->dl_class == ELF_Class_64; GNU_RDebugInfoList rdebug_list = gnu_parse_rdebug(scratch.arena, is_64bit, rdebug_vaddr, dmn_lnx_machine_op_mem_read, &process->fd); - for EachNode(rdebug_n, GNU_RDebugInfoNode, rdebug_list.first) { + for EachNode(rdebug_n, GNU_RDebugInfoNode, rdebug_list.first) + { GNU_LinkMapList link_map_list = gnu_parse_link_map_list(scratch.arena, is_64bit, rdebug_n->v.r_map, dmn_lnx_machine_op_mem_read, &process->fd); - for EachNode(link_map_n, GNU_LinkMapNode, link_map_list.first) { - DMN_LNX_Entity *module = hash_table_search_u64_raw(process->loaded_modules_ht, link_map_n->v.addr_vaddr); + for EachNode(link_map_n, GNU_LinkMapNode, link_map_list.first) + { + DMN_LNX_Module *module = hash_table_search_u64_raw(ctx->loaded_modules_ht, link_map_n->v.addr_vaddr); module->is_live = 1; } } // collect unloaded modules - DMN_HandleList to_release = {0}; - for(DMN_LNX_Entity *module = process->first; module != dmn_lnx_nil_entity; module = module->next) + DMN_LNX_ModulePtrList to_release = {0}; + for EachNode(module, DMN_LNX_Module, ctx->first_module) { - if(module->kind != DMN_LNX_EntityKind_Module) { continue; } - if(module->is_live) { continue; } - dmn_handle_list_push(scratch.arena, &to_release, dmn_lnx_handle_from_entity(module)); + if(!module->is_live) + { + dmn_lnx_module_ptr_list_push(scratch.arena, &to_release, module); + } } - // push events and clean up internal structures - for EachNode(n, DMN_HandleNode, to_release.first) + // clone process context + if(to_release.count > 0) { - DMN_LNX_Entity *module = dmn_lnx_entity_from_handle(n->v); + if(process->is_cow) + { + process->is_cow = 0; + process->ctx = dmn_lnx_process_ctx_clone(process, process->ctx); + } + } - DMN_Event *e = dmn_event_list_push(arena, events); - e->kind = DMN_EventKind_UnloadModule; - e->process = dmn_lnx_handle_from_entity(process); - e->module = dmn_lnx_handle_from_entity(module); - e->string = dmn_lnx_read_string(arena, process->fd, module->id); - - hash_table_purge_u64(process->loaded_modules_ht, module->base_vaddr); - - dmn_lnx_entity_release(module); + // push events and clean up unloaded modules + for EachNode(module_n, DMN_LNX_ModulePtrNode, to_release.first) + { + dmn_lnx_push_event_unload_module(arena, events, process, module_n->v); + dmn_lnx_module_release(process->ctx, module_n->v); } scratch_end(scratch); } internal void -dmn_lnx_handle_breakpoint(Arena *arena, DMN_EventList *events, DMN_ActiveTrap *user_traps, pid_t tid) +dmn_lnx_event_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 *process = thread->parent; - U64 ip = dmn_lnx_thread_read_ip(thread); + DMN_LNX_Thread *thread = dmn_lnx_thread_from_pid(tid); + DMN_LNX_Process *process = thread->process; + U64 ip = dmn_lnx_thread_read_ip(thread); // is this user trap? DMN_ActiveTrap *hit_user_trap = 0; { - DMN_Handle process_handle = dmn_lnx_handle_from_entity(process); + DMN_Handle process_handle = dmn_lnx_handle_from_process(process); for EachNode(active_trap, DMN_ActiveTrap, user_traps) { if(MemoryCompare(&active_trap->trap->process, &process_handle, sizeof(DMN_Handle)) == 0) @@ -1679,11 +1988,11 @@ dmn_lnx_handle_breakpoint(Arena *arena, DMN_EventList *events, DMN_ActiveTrap *u } } - // is this a probe trap? + // is this a probe trap? DMN_LNX_ProbeType probe_type = DMN_LNX_ProbeType_Null; if(hit_user_trap == 0) { - for EachNode(active_trap, DMN_ActiveTrap, process->first_probe_trap) + for EachNode(active_trap, DMN_ActiveTrap, process->ctx->first_probe_trap) { if(active_trap->trap->vaddr == ip-1) { @@ -1697,16 +2006,16 @@ dmn_lnx_handle_breakpoint(Arena *arena, DMN_EventList *events, DMN_ActiveTrap *u { B32 is_init_completed = 0; - DMN_LNX_Probe *probe = process->probes[DMN_LNX_ProbeType_InitComplete]; + DMN_LNX_Probe *probe = process->ctx->probes[DMN_LNX_ProbeType_InitComplete]; U64 name_space_id = 0, rdebug_addr = 0; - if(!stap_read_arg_u(probe->args.v[0], process->arch, thread->reg_block, dmn_lnx_stap_memory_read, process, &name_space_id)) { goto init_complete_exit; } - if(!stap_read_arg_u(probe->args.v[1], process->arch, thread->reg_block, dmn_lnx_stap_memory_read, process, &rdebug_addr)) { goto init_complete_exit; } + if(!stap_read_arg_u(probe->args.v[0], process->ctx->arch, thread->reg_block, dmn_lnx_stap_memory_read, process, &name_space_id)) { goto init_complete_exit; } + if(!stap_read_arg_u(probe->args.v[1], process->ctx->arch, thread->reg_block, dmn_lnx_stap_memory_read, process, &rdebug_addr)) { goto init_complete_exit; } GNU_RDebugInfo64 rdebug = {0}; - if(gnu_read_r_debug(dmn_lnx_machine_op_mem_read, &process->fd, rdebug_addr, process->arch, &rdebug) != MachineOpResult_Ok) { goto init_complete_exit; } + if(gnu_read_r_debug(dmn_lnx_machine_op_mem_read, &process->fd, rdebug_addr, process->ctx->arch, &rdebug) != MachineOpResult_Ok) { goto init_complete_exit; } if(rdebug.r_version < 1) { goto init_complete_exit; } - dmn_lnx_handle_load_module(arena, events, process, name_space_id, rdebug.r_map); + dmn_lnx_event_load_module(arena, events, thread, name_space_id, rdebug.r_map); is_init_completed = 1; init_complete_exit:; @@ -1716,12 +2025,12 @@ dmn_lnx_handle_breakpoint(Arena *arena, DMN_EventList *events, DMN_ActiveTrap *u { B32 is_reloc_completed = 0; - DMN_LNX_Probe *probe = process->probes[DMN_LNX_ProbeType_RelocComplete]; + DMN_LNX_Probe *probe = process->ctx->probes[DMN_LNX_ProbeType_RelocComplete]; U64 name_space_id = 0, new_link_map_addr = 0; - if(!stap_read_arg_u(probe->args.v[0], process->arch, thread->reg_block, dmn_lnx_stap_memory_read, process, &name_space_id)) { goto reloc_complete_exit; } - if(!stap_read_arg_u(probe->args.v[2], process->arch, thread->reg_block, dmn_lnx_stap_memory_read, process, &new_link_map_addr)) { goto reloc_complete_exit; } + if(!stap_read_arg_u(probe->args.v[0], process->ctx->arch, thread->reg_block, dmn_lnx_stap_memory_read, process, &name_space_id)) { goto reloc_complete_exit; } + if(!stap_read_arg_u(probe->args.v[2], process->ctx->arch, thread->reg_block, dmn_lnx_stap_memory_read, process, &new_link_map_addr)) { goto reloc_complete_exit; } - dmn_lnx_handle_load_module(arena, events, process, name_space_id, new_link_map_addr); + dmn_lnx_event_load_module(arena, events, thread, name_space_id, new_link_map_addr); is_reloc_completed = 1; reloc_complete_exit:; @@ -1731,12 +2040,12 @@ dmn_lnx_handle_breakpoint(Arena *arena, DMN_EventList *events, DMN_ActiveTrap *u { B32 is_unmap_completed = 0; - DMN_LNX_Probe *probe = process->probes[DMN_LNX_ProbeType_UnmapComplete]; + DMN_LNX_Probe *probe = process->ctx->probes[DMN_LNX_ProbeType_UnmapComplete]; U64 name_space_id = 0, rdebug_vaddr = 0; - if(!stap_read_arg_u(probe->args.v[0], process->arch, thread->reg_block, dmn_lnx_stap_memory_read, process, &name_space_id)) { goto unmap_complete_exit; } - if(!stap_read_arg_u(probe->args.v[1], process->arch, thread->reg_block, dmn_lnx_stap_memory_read, process, &rdebug_vaddr)) { goto unmap_complete_exit; } + if(!stap_read_arg_u(probe->args.v[0], process->ctx->arch, thread->reg_block, dmn_lnx_stap_memory_read, process, &name_space_id)) { goto unmap_complete_exit; } + if(!stap_read_arg_u(probe->args.v[1], process->ctx->arch, thread->reg_block, dmn_lnx_stap_memory_read, process, &rdebug_vaddr)) { goto unmap_complete_exit; } - dmn_lnx_handle_unload_module(arena, events, process, name_space_id, rdebug_vaddr); + dmn_lnx_event_unload_module(arena, events, process, rdebug_vaddr); is_unmap_completed = 1; unmap_complete_exit:; @@ -1754,23 +2063,22 @@ dmn_lnx_handle_breakpoint(Arena *arena, DMN_EventList *events, DMN_ActiveTrap *u DMN_Event *e = dmn_event_list_push(arena, events); e->kind = DMN_EventKind_Breakpoint; - e->process = dmn_lnx_handle_from_entity(process); - e->thread = dmn_lnx_handle_from_entity(thread); + e->process = dmn_lnx_handle_from_process(process); + e->thread = dmn_lnx_handle_from_thread(thread); e->instruction_pointer = dmn_lnx_thread_read_ip(thread); } } internal void -dmn_lnx_handle_data_breakpoint(Arena *arena, DMN_EventList *events, pid_t tid) +dmn_lnx_event_data_breakpoint(Arena *arena, DMN_EventList *events, pid_t tid) { - DMN_LNX_Entity *thread = dmn_lnx_thread_from_pid(tid); - DMN_LNX_Entity *process = thread->parent; + DMN_LNX_Thread *thread = dmn_lnx_thread_from_pid(tid); B32 is_valid = 1; U64 address = 0; - switch(thread->arch) + switch(thread->process->ctx->arch) { - case Arch_Null: break; + case Arch_Null: {} break; case Arch_x64: { REGS_RegBlockX64 *regs_x64 = thread->reg_block; @@ -1794,132 +2102,96 @@ dmn_lnx_handle_data_breakpoint(Arena *arena, DMN_EventList *events, pid_t tid) { is_valid = 0; } - }break; + } break; case Arch_x86: case Arch_arm32: case Arch_arm64: - { NotImplemented; }break; + { NotImplemented; } break; default: { InvalidPath; } break; } if(is_valid) { - DMN_Event *e = dmn_event_list_push(arena, events); - e->kind = DMN_EventKind_Breakpoint; - e->process = dmn_lnx_handle_from_entity(process); - e->thread = dmn_lnx_handle_from_entity(thread); - e->instruction_pointer = address; + dmn_lnx_push_event_breakpoint(arena, events, thread, address); } } internal void -dmn_lnx_handle_halt(Arena *arena, DMN_EventList *events) +dmn_lnx_event_halt(Arena *arena, DMN_EventList *events) { - DMN_Event *e = dmn_event_list_push(arena, events); - e->kind = DMN_EventKind_Halt; + dmn_lnx_push_event_halt(arena, events); } internal void -dmn_lnx_handle_single_step(Arena *arena, DMN_EventList *events, pid_t tid) +dmn_lnx_event_single_step(Arena *arena, DMN_EventList *events, pid_t tid) { - DMN_LNX_Entity *thread = dmn_lnx_thread_from_pid(tid); - DMN_LNX_Entity *process = thread->parent; + DMN_LNX_Thread *thread = dmn_lnx_thread_from_pid(tid); // clear single step flag dmn_lnx_set_single_step_flag(thread, 0); - DMN_Event *e = dmn_event_list_push(arena, events); - e->kind = DMN_EventKind_SingleStep; - e->process = dmn_lnx_handle_from_entity(process); - e->thread = dmn_lnx_handle_from_entity(thread); - e->instruction_pointer = dmn_lnx_thread_read_ip(thread); + // push event + dmn_lnx_push_event_single_step(arena, events, thread); } internal void -dmn_lnx_handle_exception(Arena *arena, DMN_EventList *events, pid_t tid, U64 signo) +dmn_lnx_event_exception(Arena *arena, DMN_EventList *events, pid_t tid, U64 signo) { - // TODO(rjf): possible cases: - // SIGABRT - // SIGFPE - // SIGSEGV - // SIGILL - - DMN_LNX_Entity *thread = dmn_lnx_thread_from_pid(tid); - DMN_LNX_Entity *process = thread->parent; + DMN_LNX_Thread *thread = dmn_lnx_thread_from_pid(tid); thread->pass_through_signal = 1; thread->pass_through_signo = signo; - DMN_Event *e = dmn_event_list_push(arena, events); - e->kind = DMN_EventKind_Exception; - e->process = dmn_lnx_handle_from_entity(process); - e->thread = dmn_lnx_handle_from_entity(thread); - e->instruction_pointer = dmn_lnx_thread_read_ip(thread); - e->signo = signo; - - if(signo == SIGSEGV) - { - siginfo_t si = {0}; - OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_GETSIGINFO, tid, 0, &si)); - e->address = (U64)si.si_addr; - } + dmn_lnx_push_event_exception(arena, events, thread, signo); } -internal DMN_LNX_Entity * -dmn_lnx_handle_attach(Arena *arena, DMN_EventList *events, pid_t pid) +internal DMN_LNX_Process * +dmn_lnx_event_attach(Arena *arena, DMN_EventList *events, pid_t pid) { Temp scratch = scratch_begin(&arena, 1); - DMN_LNX_Entity *process = 0; + // create process + DMN_LNX_Process *process = dmn_lnx_event_create_process(arena, events, pid, 0, DMN_LNX_CreateProcessFlag_DebugSubprocesses|DMN_LNX_CreateProcessFlag_Rebased); - String8 task_path = push_str8f(scratch.arena, "/proc/%d/task", pid); - DIR *task_dirp = opendir((char *)task_path.str); - if(task_dirp) + // extract threads from /proc/pid/task { - // create process - process = dmn_lnx_handle_create_process(arena, events, 1, 1, pid); - - // extract threads from /proc/pid/task - for(;;) + String8 task_path = push_str8f(scratch.arena, "/proc/%d/task", pid); + DIR *task_dirp = opendir((char *)task_path.str); + if(task_dirp) { - struct dirent *dirent = readdir(task_dirp); - if(dirent == 0) { break; } + for(;;) + { + struct dirent *dirent = readdir(task_dirp); + if(dirent == 0) { break; } - String8 tid_str = str8_cstring_capped(dirent->d_name, dirent->d_name + NAME_MAX); - if(str8_match(tid_str, str8_lit(".."), 0) || str8_match(tid_str, str8_lit("."), 0)) { continue; } - U64 tid_64 = u64_from_str8(tid_str, 10); - pid_t tid = (pid_t)tid_64; - AssertAlways(tid == tid_64); + String8 tid_str = str8_cstring_capped(dirent->d_name, dirent->d_name + NAME_MAX); + if(str8_match(tid_str, str8_lit(".."), 0) || str8_match(tid_str, str8_lit("."), 0)) { continue; } + U64 tid_64 = u64_from_str8(tid_str, 10); + pid_t tid = (pid_t)tid_64; + AssertAlways(tid == tid_64); - if(tid == pid) { continue; } // main thread was created during create process sequence - dmn_lnx_handle_create_thread(arena, events, process, tid); + if(tid == pid) { continue; } // main thread was created during create process sequence + dmn_lnx_event_create_thread(arena, events, process, tid); + } + + OS_LNX_RETRY_ON_EINTR(closedir(task_dirp)); } + } - // extract modules from r_debug - B32 is_64bit = process->dl_class == ELF_Class_64; - GNU_RDebugInfoList rdebug_list = gnu_parse_rdebug(scratch.arena, is_64bit, process->rdebug_vaddr, dmn_lnx_machine_op_mem_read, &process->fd); + // extract modules from r_debug + { + B32 is_64bit = process->ctx->dl_class == ELF_Class_64; + GNU_RDebugInfoList rdebug_list = gnu_parse_rdebug(scratch.arena, is_64bit, process->ctx->rdebug_vaddr, dmn_lnx_machine_op_mem_read, &process->fd); U64 name_space_id = 0; for EachNode(rdebug_n, GNU_RDebugInfoNode, rdebug_list.first) { - dmn_lnx_handle_load_module(arena, events, process, name_space_id, rdebug_n->v.r_map); + dmn_lnx_event_load_module(arena, events, process->first_thread, name_space_id, rdebug_n->v.r_map); name_space_id += 1; } - - OS_LNX_RETRY_ON_EINTR(closedir(task_dirp)); } - // // handshake complete - // - { - DMN_LNX_Entity *main_thread = dmn_lnx_thread_from_pid(pid); - - DMN_Event *e = dmn_event_list_push(arena, events); - e->kind = DMN_EventKind_HandshakeComplete; - e->process = dmn_lnx_handle_from_entity(process); - e->thread = dmn_lnx_handle_from_entity(main_thread); - e->arch = process->arch; - } + dmn_lnx_push_event_handshake_complete(arena, events, process); scratch_end(scratch); return process; @@ -1931,11 +2203,8 @@ dmn_lnx_handle_attach(Arena *arena, DMN_EventList *events, pid_t pid) internal void dmn_init(void) { - local_persist B32 was_inited; - if(!was_inited) + if(dmn_lnx_state == 0) { - was_inited = 1; - local_persist DMN_LNX_State state; dmn_lnx_state = &state; @@ -1946,7 +2215,7 @@ dmn_init(void) dmn_lnx_state->tid_ht = hash_table_init(dmn_lnx_state->arena, 0x2000); dmn_lnx_state->pid_ht = hash_table_init(dmn_lnx_state->arena, 0x400); dmn_lnx_state->halter_mutex = mutex_alloc(); - dmn_lnx_entity_alloc(dmn_lnx_nil_entity, DMN_LNX_EntityKind_Root); + dmn_lnx_entity_alloc(DMN_LNX_EntityKind_Null); // find offsets of TLS index and TLS offset in the link_map struct // @@ -2007,9 +2276,9 @@ dmn_ctrl_launch(DMN_CtrlCtx *ctx, OS_ProcessLaunchParams *params) char **argv = push_array(scratch.arena, char *, argc); { U64 idx = 0; - for(String8Node *n = params->cmd_line.first; n != 0; n = n->next, idx += 1) + for EachNode(n, String8Node, params->cmd_line.first) { - argv[idx] = (char *)push_str8_copy(scratch.arena, n->string).str; + argv[idx++] = (char *)push_str8_copy(scratch.arena, n->string).str; } } @@ -2022,9 +2291,9 @@ dmn_ctrl_launch(DMN_CtrlCtx *ctx, OS_ProcessLaunchParams *params) // copy user environment U64 idx = os_lnx_state.default_env_count; - for(String8Node *n = params->env.first; n != 0; n = n->next, idx += 1) + for EachNode(n, String8Node, params->env.first) { - envp[idx] = (char *)push_str8_copy(scratch.arena, n->string).str; + envp[idx++] = (char *)push_str8_copy(scratch.arena, n->string).str; } } @@ -2037,6 +2306,9 @@ dmn_ctrl_launch(DMN_CtrlCtx *ctx, OS_ProcessLaunchParams *params) // child process if(pid == 0) { + // wait for seize + if(OS_LNX_RETRY_ON_EINTR(raise(SIGSTOP)) < 0) { goto child_exit; } + // change work directory to tracee if(OS_LNX_RETRY_ON_EINTR(chdir(work_dir_path)) < 0) { goto child_exit; } @@ -2044,30 +2316,18 @@ dmn_ctrl_launch(DMN_CtrlCtx *ctx, OS_ProcessLaunchParams *params) if(OS_LNX_RETRY_ON_EINTR(execve(argv[0], argv, envp)) < 0) { goto child_exit; } child_exit:; - abort(); + exit(0); } // parent process else if(pid > 0) { - B32 failed_to_seize = 1; - // try to seize child process - if(dmn_lnx_ptrace_seize(pid) < 0) { goto parent_exit; } - - // alloc pending process node - DMN_LNX_ProcessLaunch *pending_proc = dmn_lnx_state->free_pids.first; - if(pending_proc) { dmn_lnx_process_launch_list_remove(&dmn_lnx_state->free_pids, pending_proc); } - else { pending_proc = push_array(dmn_lnx_state->arena, DMN_LNX_ProcessLaunch, 1); } - - // add pending proc node to the list - *pending_proc = (DMN_LNX_ProcessLaunch){ params->debug_subprocesses, pid, DMN_LNX_ProcessLaunchState_Exec }; - dmn_lnx_process_launch_list_push_node(&dmn_lnx_state->pending_procs, pending_proc); - - // tracee was successfully seized - failed_to_seize = 0; - - parent_exit:; - if(failed_to_seize) + if(dmn_lnx_ptrace_seize(pid) >= 0) + { + // tracee was successfully seized, create process + dmn_lnx_process_alloc(pid, DMN_LNX_ProcessState_Launch, 0, params->debug_subprocesses, 0); + } + else { Assert(0 && "failed to ptrace child process"); OS_LNX_RETRY_ON_EINTR(kill(SIGKILL, pid)); @@ -2084,18 +2344,11 @@ internal B32 dmn_ctrl_attach(DMN_CtrlCtx *ctx, U32 pid) { B32 is_attached = 0; - if(dmn_lnx_ptrace_seize(pid) >= 0) { if(OS_LNX_RETRY_ON_EINTR(kill(pid, SIGSTOP)) >= 0) { - DMN_LNX_ProcessLaunch *pending_proc = dmn_lnx_state->free_pids.first; - if(pending_proc) { dmn_lnx_process_launch_list_remove(&dmn_lnx_state->free_pids, pending_proc); } - else { pending_proc = push_array(dmn_lnx_state->arena, DMN_LNX_ProcessLaunch, 1); } - - *pending_proc = (DMN_LNX_ProcessLaunch){ 1, pid, DMN_LNX_ProcessLaunchState_Attach }; - dmn_lnx_process_launch_list_push_node(&dmn_lnx_state->pending_procs, pending_proc); - + dmn_lnx_process_alloc(pid, DMN_LNX_ProcessState_Attach, 0, 1, 0); is_attached = 1; } else @@ -2103,217 +2356,216 @@ dmn_ctrl_attach(DMN_CtrlCtx *ctx, U32 pid) OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_DETACH, pid, 0, 0)); } } - return is_attached; } internal B32 -dmn_ctrl_kill(DMN_CtrlCtx *ctx, DMN_Handle process, U32 exit_code) +dmn_ctrl_kill(DMN_CtrlCtx *ctx, DMN_Handle process_handle, U32 exit_code) { B32 result = 0; - DMN_LNX_Entity *process_entity = dmn_lnx_entity_from_handle(process); - if(process_entity != dmn_lnx_nil_entity && - OS_LNX_RETRY_ON_EINTR(kill(process_entity->id, SIGKILL)) != -1) + mutex_take(dmn_lnx_state->halter_mutex); + DMN_LNX_Process *process = dmn_lnx_process_from_handle(process_handle); + if(process) { - result = 1; + result = OS_LNX_RETRY_ON_EINTR(kill(process->pid, SIGKILL)) >= 0; } + mutex_drop(dmn_lnx_state->halter_mutex); return result; } internal B32 -dmn_ctrl_detach(DMN_CtrlCtx *ctx, DMN_Handle process) +dmn_ctrl_detach(DMN_CtrlCtx *ctx, DMN_Handle process_handle) { B32 result = 0; - DMN_LNX_Entity *process_entity = dmn_lnx_entity_from_handle(process); - if(process_entity != dmn_lnx_nil_entity && - OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_DETACH, process_entity->id, 0, 0)) != -1) + mutex_take(dmn_lnx_state->halter_mutex); + DMN_LNX_Process *process = dmn_lnx_process_from_handle(process_handle); + if(process) { - result = 1; + result = OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_DETACH, process->pid, 0, 0)) >= 0; } + mutex_drop(dmn_lnx_state->halter_mutex); return result; } internal DMN_EventList dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) { - Assert(gettid() == dmn_lnx_state->tracer_tid); Temp scratch = scratch_begin(&arena, 1); DMN_EventList events = {0}; mutex_take(dmn_lnx_state->halter_mutex); - // write traps to memory - DMN_ActiveTrap *active_trap_first = 0, *active_trap_last = 0; + // wait for signals from the running threads + if(dmn_lnx_state->process_count > 0) { - HashTable *process_ht = hash_table_init(scratch.arena, dmn_lnx_state->active_process_count); - for EachNode(n, DMN_TrapChunkNode, ctrls->traps.first) + // write traps to memory + DMN_ActiveTrap *active_trap_first = 0, *active_trap_last = 0; { - for EachIndex(n_idx, n->count) + HashTable *process_ht = hash_table_init(scratch.arena, dmn_lnx_state->process_count); + for EachNode(n, DMN_TrapChunkNode, ctrls->traps.first) { - // skip hardware breakpoints - DMN_Trap *trap = n->v+n_idx; - if(trap->flags) { continue; } - - HashTable *active_trap_ht = hash_table_search_u64_raw(process_ht, trap->process.u64[0]); - if(active_trap_ht == 0) + for EachIndex(n_idx, n->count) { - 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); + // skip hardware breakpoints + DMN_Trap *trap = n->v+n_idx; + 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 + DMN_ActiveTrap *is_set = hash_table_search_u64_raw(active_trap_ht, trap->vaddr); + if(is_set) { continue; } + + // TODO: ctrl sends down traps for exited process + DMN_LNX_Process *process = dmn_lnx_process_from_handle(trap->process); + if(!process) { continue; } + + // trap instruction + DMN_ActiveTrap *active_trap = dmn_set_trap(scratch.arena, trap); + + // add trap to the active list + SLLQueuePush(active_trap_first, active_trap_last, active_trap); + + // add (address -> trap) + hash_table_push_u64_raw(scratch.arena, active_trap_ht, trap->vaddr, active_trap); } - - // TODO: ctrl sends down duplicate traps - DMN_ActiveTrap *is_set = hash_table_search_u64_raw(active_trap_ht, trap->vaddr); - if(is_set) { continue; } - - // TODO: ctrl sends down traps for exited process - DMN_LNX_Entity *process = dmn_lnx_entity_from_handle(trap->process); - if(process == dmn_lnx_nil_entity) { continue; } - - // trap instruction - DMN_ActiveTrap *active_trap = dmn_set_trap(scratch.arena, trap); - - // add trap to the active list - SLLQueuePush(active_trap_first, active_trap_last, active_trap); - - // add (address -> trap) - hash_table_push_u64_raw(scratch.arena, active_trap_ht, trap->vaddr, active_trap); } } - } - // enable single stepping - if(!dmn_handle_match(ctrls->single_step_thread, dmn_handle_zero())) - { - DMN_LNX_Entity *single_step_thread = dmn_lnx_entity_from_handle(ctrls->single_step_thread); - dmn_lnx_set_single_step_flag(single_step_thread, 1); - } - - // schedule threads to run - DMN_LNX_EntityList running_threads = {0}; - { - for(DMN_LNX_Entity *process = dmn_lnx_state->entities_base->first; process != dmn_lnx_nil_entity; process = process->next) + // enable single stepping + if(!dmn_handle_match(ctrls->single_step_thread, dmn_handle_zero())) { - if(process->kind != DMN_LNX_EntityKind_Process) { continue; } - - //- rjf: determine if this process is frozen - B32 process_is_frozen = 0; - if(ctrls->run_entities_are_processes) + DMN_LNX_Thread *single_step_thread = dmn_lnx_thread_from_handle(ctrls->single_step_thread); + if(single_step_thread) { - for EachIndex(idx, ctrls->run_entity_count) + dmn_lnx_set_single_step_flag(single_step_thread, 1); + } + else + { + Assert(0 && "invalid single_step_thread handle"); + } + } + + // schedule threads to run + DMN_LNX_ThreadPtrList running_threads = {0}; + { + for EachNode(process, DMN_LNX_Process, dmn_lnx_state->first_process) + { + //- rjf: determine if this process is frozen + B32 process_is_frozen = 0; + if(ctrls->run_entities_are_processes) { - if(dmn_handle_match(ctrls->run_entities[idx], dmn_lnx_handle_from_entity(process))) + for EachIndex(idx, ctrls->run_entity_count) { - process_is_frozen = 1; - break; + if(dmn_handle_match(ctrls->run_entities[idx], dmn_lnx_handle_from_process(process))) + { + process_is_frozen = 1; + break; + } } } - } - if(!process_is_frozen && process->thread_count == 0) - { - if(OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_CONT, (pid_t)process->id, 0, 0)) < 0) { InvalidPath; } - break; - } - - for(DMN_LNX_Entity *thread = process->first; thread != dmn_lnx_nil_entity; thread = thread->next) - { - if(thread->kind != DMN_LNX_EntityKind_Thread) { continue; } - - //- rjf: determine if this thread is frozen - B32 is_frozen = 0; - - // rjf: not single-stepping? determine based on run controls freezing info - if(dmn_handle_match(dmn_handle_zero(), ctrls->single_step_thread)) + for EachNode(thread, DMN_LNX_Thread, process->first_thread) { - if(ctrls->run_entities_are_processes) + //- rjf: determine if this thread is frozen + B32 is_frozen = 0; + + // rjf: not single-stepping? determine based on run controls freezing info + if(dmn_handle_match(dmn_handle_zero(), ctrls->single_step_thread)) { - is_frozen = process_is_frozen; - } - else - { - for EachIndex(idx, ctrls->run_entity_count) + if(ctrls->run_entities_are_processes) { - if(dmn_handle_match(ctrls->run_entities[idx], dmn_lnx_handle_from_entity(thread))) + is_frozen = process_is_frozen; + } + else + { + for EachIndex(idx, ctrls->run_entity_count) { - is_frozen = 1; - break; + if(dmn_handle_match(ctrls->run_entities[idx], dmn_lnx_handle_from_thread(thread))) + { + is_frozen = 1; + break; + } } } - } - if(ctrls->run_entities_are_unfrozen) - { - is_frozen ^= 1; - } - } - // rjf: single-step? freeze if not the single-step thread. - else - { - is_frozen = !dmn_handle_match(dmn_lnx_handle_from_entity(thread), ctrls->single_step_thread); - } - - // resume thread - if(!is_frozen) - { - AssertAlways(thread->thread_state == DMN_LNX_ThreadState_Stopped); - - // write registers - if(thread->is_reg_block_dirty) - { - thread->is_reg_block_dirty = !dmn_lnx_thread_write_reg_block(thread); - } - - // pass signal to the child process - void *sig_code = 0; - if(thread->pass_through_signal) - { - thread->pass_through_signal = 0; - if(!ctrls->ignore_previous_exception) + if(ctrls->run_entities_are_unfrozen) { - sig_code = (void *)(uintptr_t)thread->pass_through_signo; + is_frozen ^= 1; } } + // rjf: single-step? freeze if not the single-step thread. + else + { + is_frozen = !dmn_handle_match(dmn_lnx_handle_from_thread(thread), ctrls->single_step_thread); + } // resume thread - if(OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_CONT, (pid_t)thread->id, 0, sig_code)) >= 0) + if(!is_frozen) { - thread->thread_state = DMN_LNX_ThreadState_Running; - thread->is_reg_block_dirty = 1; - dmn_lnx_entity_list_push(scratch.arena, &running_threads, thread); - } - else - { - if(errno == ESRCH) + AssertAlways(thread->state == DMN_LNX_ThreadState_Stopped); + + // write registers + if(thread->is_reg_block_dirty) { - // Race note: In multithreaded programs, ptrace(PTRACE_CONT) may fail if a running thread calls - // exit_group while we are resuming threads. The kernel begins tearing down the thread group so - // the target might not be in a ptrace-soppped state anymore. Handle this by ignoring, on waitpid - // we will get chance to report exit event. - // - // TODO: unfortunatley, kernel also sends us ESRCH whenever ptrace(PTRACE_CONT) is sent to a tracee - // that is not in a ptace-stop, so how can we tell these errors apart? + thread->is_reg_block_dirty = !dmn_lnx_thread_write_reg_block(thread); + } + + // pass signal to the child process + void *sig_code = 0; + if(thread->pass_through_signal) + { + thread->pass_through_signal = 0; + if(!ctrls->ignore_previous_exception) + { + sig_code = (void *)(uintptr_t)thread->pass_through_signo; + } + } + + // resume thread + if(OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_CONT, thread->tid, 0, sig_code)) >= 0) + { + thread->state = DMN_LNX_ThreadState_Running; + thread->is_reg_block_dirty = 1; + dmn_lnx_thread_ptr_list_push(scratch.arena, &running_threads, thread); } else { - InvalidPath; + if(errno == ESRCH) + { + // Race note: In multithreaded programs, ptrace(PTRACE_CONT) may fail if a running thread calls + // exit_group while we are resuming threads. The kernel begins tearing down the thread group so + // the target might not be in a ptrace-soppped state anymore. Handle this by ignoring, on waitpid + // we will get chance to report exit event. + // + // TODO: unfortunatley, kernel also sends us ESRCH whenever ptrace(PTRACE_CONT) is sent to a tracee + // that is not in a ptace-stop, so how can we tell these errors apart? + } + else + { + InvalidPath; + } } } } } } - } - // wait for signals from the running threads - { - B32 is_halt_done = 0; - for(U64 stopped_threads = 0;;) + // hash running threads tids + HashTable *running_threads_ht = hash_table_init(scratch.arena, running_threads.count * 2); + for EachNode(n, DMN_LNX_ThreadPtrNode, running_threads.first) { - // do not wait if there are no pending or active processes - if(dmn_lnx_state->pending_procs.count == 0 && dmn_lnx_state->active_process_count == 0) - { - dmn_lnx_handle_not_attached(arena, &events); - break; - } + hash_table_push_u64_raw(scratch.arena, running_threads_ht, n->v->tid, n); + } + B32 is_halt_done = 0; + DMN_LNX_ThreadPtrList stopped_threads = {0}; + do + { // wait for a signal int status = 0; pid_t wait_id = 0; @@ -2335,97 +2587,115 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) int wstopsig = WSTOPSIG(status); int event_code = (status >> 16); - // intercept signals meant for the process launch sequence + // intercept initing processes { - // pid -> pending process launch - DMN_LNX_ProcessLaunch *pending_proc = 0; - for EachNode(n, DMN_LNX_ProcessLaunch, dmn_lnx_state->pending_procs.first) { if(n->pid == wait_id) { pending_proc = n; break; } } + DMN_LNX_Process *process = dmn_lnx_process_from_pid(wait_id); - if(pending_proc) + if(process && process->state != DMN_LNX_ProcessState_Normal) { - // process unexpectedly exited - if(wifexited) { pending_proc->state = DMN_LNX_ProcessLaunchState_Exit; } - - if(pending_proc->state == DMN_LNX_ProcessLaunchState_Exec) + switch(process->state) { - if(wstopsig == SIGTRAP && event_code == PTRACE_EVENT_EXEC) + case DMN_LNX_ProcessState_Null: + case DMN_LNX_ProcessState_Normal: { - // move pending process node to the free list - dmn_lnx_process_launch_list_remove(&dmn_lnx_state->pending_procs, pending_proc); - dmn_lnx_process_launch_list_push_node(&dmn_lnx_state->free_pids, pending_proc); + InvalidPath; + } break; + case DMN_LNX_ProcessState_Launch: + { + if(wstopsig == SIGSTOP) + { + if(OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_CONT, process->pid, 0, 0)) >= 0) + { + process->state = DMN_LNX_ProcessState_WaitForExec; + goto wait_for_signal; + } + else { Assert(0 && "failed to resume tracee"); } + } + // else { Assert(0 && "unexpected signal"); } + } break; + case DMN_LNX_ProcessState_WaitForExec: + { + if(wstopsig == SIGTRAP && event_code == PTRACE_EVENT_EXEC) + { + DMN_LNX_CreateProcessFlags create_flags = process->debug_subprocesses ? DMN_LNX_CreateProcessFlag_DebugSubprocesses : 0; + dmn_lnx_process_release(process); + dmn_lnx_event_create_process(arena, &events, wait_id, 0, create_flags); + goto wait_for_signal; + } + //else { Assert(0 && "unexpected signal"); } + } break; + case DMN_LNX_ProcessState_ExecFailedDoExit: + { + if(wifexited) + { + dmn_lnx_process_release(process); + goto wait_for_signal; + } + else { Assert(0 && "unexpected signal"); } + } break; + case DMN_LNX_ProcessState_Attach: + { + if(wstopsig == SIGSTOP) + { + DMN_LNX_CreateProcessFlags create_flags = process->debug_subprocesses ? DMN_LNX_CreateProcessFlag_DebugSubprocesses : 0; + dmn_lnx_process_release(process); + dmn_lnx_event_create_process(arena, &events, wait_id, 0, create_flags); + goto wait_for_signal; + } + else { Assert(0 && "unexpected signal"); } + } break; + } - // push create process events - DMN_LNX_Entity *process = dmn_lnx_handle_create_process(arena, &events, pending_proc->debug_subprocesses, 0, wait_id); - - // override event code so the main code path does not create another process - event_code = PTRACE_EVENT_STOP; + // shutdown process + { + if(OS_LNX_RETRY_ON_EINTR(kill(wait_id, SIGKILL)) >= 0) + { + process->state = DMN_LNX_ProcessState_ExecFailedDoExit; } else { - // shutdown process on abnormal init - OS_LNX_RETRY_ON_EINTR(kill(wait_id, SIGKILL)); - pending_proc->state = DMN_LNX_ProcessLaunchState_Exit; - continue; + //Assert(0 && "failed to send kill signal to the tracee"); + dmn_lnx_process_release(process); } } - else if(pending_proc->state == DMN_LNX_ProcessLaunchState_Attach) - { - // move pending process node to the free list - dmn_lnx_process_launch_list_remove(&dmn_lnx_state->pending_procs, pending_proc); - dmn_lnx_process_launch_list_push_node(&dmn_lnx_state->free_pids, pending_proc); - dmn_lnx_handle_attach(arena, &events, wait_id); - - // override event code - event_code = PTRACE_EVENT_STOP; - } - else if(pending_proc->state == DMN_LNX_ProcessLaunchState_Exit) - { - // move pending process node to the free list - dmn_lnx_process_launch_list_remove(&dmn_lnx_state->pending_procs, pending_proc); - dmn_lnx_process_launch_list_push_node(&dmn_lnx_state->free_pids, pending_proc); - continue; - } + wait_for_signal:; + continue; } } if(wifstopped || wifsignaled || wifexited) { - DMN_LNX_Entity *thread = dmn_lnx_thread_from_pid(wait_id); - if(thread->thread_state != DMN_LNX_ThreadState_PendingCreation) + DMN_LNX_ThreadPtrNode *thread_n = hash_table_search_u64_raw(running_threads_ht, wait_id); + if(thread_n) { - // kernel may send multiple stop signals for the same thread, - // so count first stop signal and ignore subsequent signals - B32 is_first_thread_to_stop = 0; - if(thread->thread_state == DMN_LNX_ThreadState_Running) - { - is_first_thread_to_stop = (stopped_threads == 0); - stopped_threads += 1; - } + DMN_LNX_Thread *thread = thread_n->v; + AssertAlways(thread->state == DMN_LNX_ThreadState_Running); + + // remove mapping + hash_table_purge_u64(running_threads_ht, thread->tid); + + // move thread to the stopped list + dmn_lnx_thread_ptr_list_remove(&running_threads, thread_n); + dmn_lnx_thread_ptr_list_push_node(&stopped_threads, thread_n); // update thread state - if(thread != dmn_lnx_nil_entity) + if(wifstopped && !wifsignaled && !wifexited) { - if(wifstopped && !wifsignaled && !wifexited) - { - thread->thread_state = DMN_LNX_ThreadState_Stopped; - } - else if(!wifstopped && (wifsignaled || wifexited)) - { - thread->thread_state = DMN_LNX_ThreadState_Exited; - } - else { InvalidPath; } + thread->state = DMN_LNX_ThreadState_Stopped; } + else if(!wifstopped && (wifsignaled || wifexited)) + { + thread->state = DMN_LNX_ThreadState_Exited; + } + else { InvalidPath; } // stop all other threads - if(is_first_thread_to_stop) + if(stopped_threads.count == 1) { - for EachNode(thread, DMN_LNX_EntityNode, running_threads.first) + for EachNode(n, DMN_LNX_ThreadPtrNode, running_threads.first) { - if(thread->v->thread_state == DMN_LNX_ThreadState_Running) - { - if(OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_INTERRUPT, thread->v->id, 0, 0)) < 0) { Assert(0 && "failed to interrupt process"); } - } + if(OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_INTERRUPT, n->v->tid, 0, 0)) < 0) { Assert(0 && "failed to interrupt process"); } } } } @@ -2434,21 +2704,21 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) // normal child exit via _exit or exit() if(wifexited) { - dmn_lnx_handle_exit_thread(arena, &events, wait_id, WEXITSTATUS(status)); + dmn_lnx_event_exit_thread(arena, &events, wait_id, WEXITSTATUS(status)); } // exit because child did not handle a signal else if(wifsignaled) { - dmn_lnx_handle_exit_thread(arena, &events, wait_id, WTERMSIG(status)); + dmn_lnx_event_exit_thread(arena, &events, wait_id, WTERMSIG(status)); } // thread or group stop else if(wifstopped) { // read thread registers { - DMN_LNX_Entity *thread = dmn_lnx_thread_from_pid(wait_id); - if(thread != dmn_lnx_nil_entity && thread->thread_state != DMN_LNX_ThreadState_PendingCreation) + DMN_LNX_Thread *thread = dmn_lnx_thread_from_pid(wait_id); + if(thread && thread->state != DMN_LNX_ThreadState_PendingCreation) { thread->is_reg_block_dirty = !dmn_lnx_thread_read_reg_block(thread); Assert(!thread->is_reg_block_dirty); @@ -2467,10 +2737,10 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) { switch(siginfo.si_code) { - 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_first, 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 SI_KERNEL: { dmn_lnx_event_breakpoint(arena, &events, active_trap_first, wait_id); } break; + case TRAP_BRKPT: { dmn_lnx_event_breakpoint(arena, &events, active_trap_first, wait_id); } break; + case TRAP_TRACE: { dmn_lnx_event_single_step(arena, &events, wait_id); } break; + case TRAP_HWBKPT: { dmn_lnx_event_data_breakpoint(arena, &events, wait_id); } break; case TRAP_BRANCH: { NotImplemented; }break; case TRAP_UNK: { NotImplemented; }break; default: { InvalidPath; } break; @@ -2479,122 +2749,50 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) }break; case PTRACE_EVENT_FORK: { - // 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"); } - - // grab parent process - DMN_LNX_Entity *parent_thread = dmn_lnx_thread_from_pid(wait_id); - DMN_LNX_Entity *parent_process = parent_thread->parent; - - // clean up in the child process instrumentation inherited during fork - { - 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) - { - 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); - } - - // remove probe traps from the child process - for EachNode(n, DMN_ActiveTrap, parent_process->first_probe_trap) - { - 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); - } - - // remove traps from the child process - for EachNode(n, DMN_ActiveTrap, active_trap_first) - { - 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); - AssertAlways(is_written); - } - } - - close(child_memory_fd); - temp_end(temp); - } - - if(parent_process->debug_subprocesses) - { - // 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"); } - } + NotImplemented; }break; case PTRACE_EVENT_VFORK: { NotImplemented; }break; + case PTRACE_EVENT_VFORK_DONE: + { + NotImplemented; + }break; case PTRACE_EVENT_CLONE: { // kernel stopped the parent just before scheduling the child to // give us a chance to prepare to trace it; next event for the child // will be a PTRACE_EVENT_STOP - pid_t new_pid; - if(OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_GETEVENTMSG, wait_id, 0, &new_pid)) >= 0) + pid_t new_tid; + if(OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_GETEVENTMSG, wait_id, 0, &new_tid)) >= 0) { - DMN_LNX_Entity *thread = dmn_lnx_thread_from_pid(wait_id); - DMN_LNX_Entity *process = thread->parent; + DMN_LNX_Thread *thread = dmn_lnx_thread_from_pid(wait_id); // create a new partially inited thread - DMN_LNX_Entity *new_thread = dmn_lnx_entity_alloc(process, DMN_LNX_EntityKind_Thread); - new_thread->id = new_pid; - new_thread->thread_state = DMN_LNX_ThreadState_PendingCreation; - - // create mapping from tid -> new thread - hash_table_push_u64_raw(dmn_lnx_state->arena, dmn_lnx_state->tid_ht, new_thread->id, new_thread); - - // do not exit the wait loop before new threads are inited - dmn_lnx_state->threads_pending_creation += 1; + dmn_lnx_thread_alloc(thread->process, DMN_LNX_ThreadState_PendingCreation, new_tid); } else { Assert(0 && "failed to get new tid"); } }break; case PTRACE_EVENT_EXEC: { - DMN_LNX_Entity *thread = dmn_lnx_thread_from_pid(wait_id); - DMN_LNX_Entity *process = thread->parent; + DMN_LNX_Thread *thread = dmn_lnx_thread_from_pid(wait_id); + DMN_LNX_Process *process = thread->process; if(process->debug_subprocesses) { - 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_event_exit_thread(arena, &events, wait_id, 0); + dmn_lnx_event_create_process(arena, &events, wait_id, 0, DMN_LNX_CreateProcessFlag_DebugSubprocesses); } else { if(OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_DETACH, wait_id, 0, 0)) >= 0) { - dmn_lnx_handle_exit_thread(arena, &events, wait_id, 0); + dmn_lnx_event_exit_thread(arena, &events, wait_id, 0); } else { Assert(0 && "failed to detach"); } } }break; - case PTRACE_EVENT_VFORK_DONE: - { - NotImplemented; - }break; case PTRACE_EVENT_EXIT: { AssertAlways(0 && "exit tracing is disabled, this event should not be delivered"); @@ -2605,19 +2803,16 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) }break; case PTRACE_EVENT_STOP: { - DMN_LNX_Entity *thread = dmn_lnx_thread_from_pid(wait_id); - DMN_LNX_Entity *process = thread->parent; - - AssertAlways(thread->thread_state == DMN_LNX_ThreadState_PendingCreation || thread->thread_state == DMN_LNX_ThreadState_Stopped); - - if(thread->thread_state == DMN_LNX_ThreadState_PendingCreation) + DMN_LNX_Thread *thread = dmn_lnx_thread_from_pid(wait_id); + if(thread->state == DMN_LNX_ThreadState_PendingCreation) { - // finish thread init - dmn_lnx_init_create_thread(arena, &events, process, thread); - - // update global thread counter - AssertAlways(dmn_lnx_state->threads_pending_creation > 0); - dmn_lnx_state->threads_pending_creation -= 1; + DMN_LNX_Process *process = thread->process; + dmn_lnx_thread_release(thread); + thread = dmn_lnx_event_create_thread(arena, &events, process, wait_id); + } + else + { + AssertAlways(thread->state == DMN_LNX_ThreadState_Stopped); } }break; default: { Assert(0 && "unexpected ptrace code"); } break; @@ -2641,23 +2836,17 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) } else { - dmn_lnx_handle_exception(arena, &events, wait_id, wstopsig); + dmn_lnx_event_exception(arena, &events, wait_id, wstopsig); } } else { Assert(0 && "unexpected stop code"); } - - // do not wait if all threads are stopped, there are no launching processes, and all threads were created - if(stopped_threads >= running_threads.count && dmn_lnx_state->pending_procs.count == 0 && dmn_lnx_state->threads_pending_creation == 0) - { - break; - } - } + } while(running_threads.count > 0 || dmn_lnx_state->process_pending_creation > 0 || dmn_lnx_state->threads_pending_creation > 0); // finalize halter state if(is_halt_done) { // push event - dmn_lnx_handle_halt(arena, &events); + dmn_lnx_event_halt(arena, &events); // reset state dmn_lnx_state->halter_tid = 0; @@ -2665,21 +2854,26 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) dmn_lnx_state->halt_user_data = 0; dmn_lnx_state->is_halting = 0; } - } - // restore original instruction bytes - for EachNode(active_trap, DMN_ActiveTrap, active_trap_first) - { - // skip process that exited during the wait - DMN_LNX_Entity *process = dmn_lnx_entity_from_handle(active_trap->trap->process); - if(process == dmn_lnx_nil_entity) { continue; } - - if(!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)) + // restore original instruction bytes + for EachNode(active_trap, DMN_ActiveTrap, active_trap_first) { - Assert(0 && "failed to restore original instruction bytes"); + // skip process that exited during the wait + DMN_LNX_Process *process = dmn_lnx_process_from_handle(active_trap->trap->process); + if(!process) { continue; } + + if(!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(0 && "failed to restore original instruction bytes"); + } } } - + + if(events.count == 0 && dmn_lnx_state->process_count == 0) + { + dmn_lnx_push_event_not_attached(arena, &events); + } + mutex_drop(dmn_lnx_state->halter_mutex); scratch_end(scratch); return events; @@ -2692,15 +2886,14 @@ internal void dmn_halt(U64 code, U64 user_data) { mutex_take(dmn_lnx_state->halter_mutex); - if(dmn_lnx_state->active_process_count) + if(dmn_lnx_state->process_count) { dmn_lnx_state->halter_tid = gettid(); dmn_lnx_state->halt_code = code; dmn_lnx_state->halt_user_data = user_data; - for (DMN_LNX_Entity *process = dmn_lnx_state->entities_base->first; process != dmn_lnx_nil_entity; process = process->next) + for EachNode(process, DMN_LNX_Process, dmn_lnx_state->first_process) { - if(process->kind != DMN_LNX_EntityKind_Process) { continue; } - if(OS_LNX_RETRY_ON_EINTR(kill(process->id, SIGSTOP)) < 0) { Assert(0 && "failed to send SIGSTOP"); } + if(OS_LNX_RETRY_ON_EINTR(kill(process->pid, SIGSTOP)) < 0) { Assert(0 && "failed to send SIGSTOP"); } } } mutex_drop(dmn_lnx_state->halter_mutex); @@ -2765,78 +2958,93 @@ dmn_process_memory_protect(DMN_Handle process, U64 vaddr, U64 size, OS_AccessFla } internal U64 -dmn_process_read(DMN_Handle process, Rng1U64 range, void *dst) +dmn_process_read(DMN_Handle process_handle, Rng1U64 range, void *dst) { - DMN_LNX_Entity *entity = dmn_lnx_entity_from_handle(process); - U64 result = dmn_lnx_read(entity->fd, range, dst); + DMN_LNX_Process *process = dmn_lnx_process_from_handle(process_handle); + U64 result = 0; + if(process) + { + result = dmn_lnx_read(process->fd, range, dst); + } return result; } internal B32 -dmn_process_write(DMN_Handle process, Rng1U64 range, void *src) +dmn_process_write(DMN_Handle process_handle, Rng1U64 range, void *src) { - DMN_LNX_Entity *entity = dmn_lnx_entity_from_handle(process); - B32 result = dmn_lnx_write(entity->fd, range, src); + DMN_LNX_Process *process = dmn_lnx_process_from_handle(process_handle); + B32 result = 0; + if(process) + { + result = dmn_lnx_write(process->fd, range, src); + } return result; } //- rjf: threads internal Arch -dmn_arch_from_thread(DMN_Handle handle) +dmn_arch_from_thread(DMN_Handle thread_handle) { - DMN_LNX_Entity *thread = dmn_lnx_entity_from_handle(handle); - return thread->arch; + Arch arch = Arch_Null; + DMN_LNX_Thread *thread = dmn_lnx_thread_from_handle(thread_handle); + if(thread) + { + arch = thread->process->ctx->arch; + } + return arch; } internal U64 -dmn_stack_base_vaddr_from_thread(DMN_Handle handle) +dmn_stack_base_vaddr_from_thread(DMN_Handle thread_handle) { return 0; } internal U64 -dmn_tls_root_vaddr_from_thread(DMN_Handle handle) +dmn_tls_root_vaddr_from_thread(DMN_Handle thread_handle) { U64 tls_root_vaddr = max_U64; DMN_AccessScope { - DMN_LNX_Entity *thread = dmn_lnx_entity_from_handle(handle); - switch (thread->arch) + DMN_LNX_Thread *thread = dmn_lnx_thread_from_handle(thread_handle); + if(thread) { - case Arch_Null: {} break; - case Arch_x64: + switch(thread->process->ctx->arch) { - DMN_LNX_Entity *process = thread->parent; - REGS_RegBlockX64 *reg_block = thread->reg_block; - U64 dtv_pointer = 0; - if(dmn_lnx_read_struct(process->fd, reg_block->fsbase.u64 + 8, &dtv_pointer)) + case Arch_Null: {} break; + case Arch_x64: { - tls_root_vaddr = dtv_pointer; - } - } break; - case Arch_x86: - case Arch_arm32: - case Arch_arm64: - { - NotImplemented; - } break; - default: { InvalidPath; } break; + REGS_RegBlockX64 *reg_block = thread->reg_block; + U64 dtv_pointer = 0; + if(dmn_lnx_read_struct(thread->process->fd, reg_block->fsbase.u64 + 8, &dtv_pointer)) + { + tls_root_vaddr = dtv_pointer; + } + } break; + case Arch_x86: + case Arch_arm32: + case Arch_arm64: + { + NotImplemented; + } break; + default: { InvalidPath; } break; + } } } return tls_root_vaddr; } internal B32 -dmn_thread_read_reg_block(DMN_Handle handle, void *reg_block) +dmn_thread_read_reg_block(DMN_Handle thread_handle, void *reg_block) { B32 result = 0; DMN_AccessScope { - DMN_LNX_Entity *thread = dmn_lnx_entity_from_handle(handle); - if(thread != dmn_lnx_nil_entity) + DMN_LNX_Thread *thread = dmn_lnx_thread_from_handle(thread_handle); + if(thread) { - U64 reg_block_size = regs_block_size_from_arch(thread->arch); + U64 reg_block_size = regs_block_size_from_arch(thread->process->ctx->arch); MemoryCopy(reg_block, thread->reg_block, reg_block_size); } result = 1; @@ -2845,23 +3053,19 @@ dmn_thread_read_reg_block(DMN_Handle handle, void *reg_block) } internal B32 -dmn_thread_write_reg_block(DMN_Handle handle, void *reg_block) +dmn_thread_write_reg_block(DMN_Handle thread_handle, void *reg_block) { B32 result = 0; DMN_AccessScope { - DMN_LNX_Entity *thread = dmn_lnx_entity_from_handle(handle); - U64 reg_block_size = regs_block_size_from_arch(thread->arch); - if(thread == dmn_lnx_nil_entity) - { - MemoryZero(reg_block, reg_block_size); - } - else + DMN_LNX_Thread *thread = dmn_lnx_thread_from_handle(thread_handle); + if(thread) { + U64 reg_block_size = regs_block_size_from_arch(thread->process->ctx->arch); MemoryCopy(thread->reg_block, reg_block, reg_block_size); thread->is_reg_block_dirty = 1; + result = 1; } - result = 1; } return result; } diff --git a/src/demon/linux/demon_core_linux.h b/src/demon/linux/demon_core_linux.h index f0fcb288..94a667c7 100644 --- a/src/demon/linux/demon_core_linux.h +++ b/src/demon/linux/demon_core_linux.h @@ -5,27 +5,19 @@ #define DEMON_CORE_LINUX_H //////////////////////////////// -//~ rjf: Includes +//~ TLS -#include -#include -#include - -//////////////////////////////// - -typedef struct DMN_LNX_DbDesc DMN_LNX_DbDesc; -struct DMN_LNX_DbDesc +typedef struct DMN_LNX_DbDesc { U32 bit_size; U32 count; U32 offset; -}; +} DMN_LNX_DbDesc; //////////////////////////////// //~ SDT Probes -typedef struct DMN_LNX_Probe DMN_LNX_Probe; -struct DMN_LNX_Probe +typedef struct DMN_LNX_Probe { String8 provider; String8 name; @@ -33,22 +25,20 @@ struct DMN_LNX_Probe STAP_ArgArray args; U64 pc; U64 semaphore; -}; +} DMN_LNX_Probe; -typedef struct DMN_LNX_ProbeNode DMN_LNX_ProbeNode; -struct DMN_LNX_ProbeNode +typedef struct DMN_LNX_ProbeNode { DMN_LNX_Probe v; - DMN_LNX_ProbeNode *next; -}; + struct DMN_LNX_ProbeNode *next; +} DMN_LNX_ProbeNode; -typedef struct DMN_LNX_ProbeList DMN_LNX_ProbeList; -struct DMN_LNX_ProbeList +typedef struct DMN_LNX_ProbeList { U64 count; DMN_LNX_ProbeNode *first; DMN_LNX_ProbeNode *last; -}; +} DMN_LNX_ProbeList; #define DMN_LNX_Probe_XList \ X(InitStart, 2, "init_start") \ @@ -73,10 +63,9 @@ typedef enum } DMN_LNX_ProbeType; //////////////////////////////// -//~ rjf: Process Info Extraction Types +//~ Process Info -typedef struct DMN_LNX_ProcessAuxv DMN_LNX_ProcessAuxv; -struct DMN_LNX_ProcessAuxv +typedef struct DMN_LNX_Auxv { U64 base; U64 phnum; @@ -84,10 +73,9 @@ struct DMN_LNX_ProcessAuxv U64 phdr; U64 execfn; U64 pagesz; -}; +} DMN_LNX_Auxv; -typedef struct DMN_LNX_DynamicInfo DMN_LNX_DynamicInfo; -struct DMN_LNX_DynamicInfo +typedef struct DMN_LNX_DynamicInfo { U64 hash_vaddr; U64 gnu_hash_vaddr; @@ -95,21 +83,19 @@ struct DMN_LNX_DynamicInfo U64 strtab_size; U64 symtab_vaddr; U64 symtab_entry_size; -}; +} DMN_LNX_DynamicInfo; //////////////////////////////// -//~ rjf: Entity Types +//~ Entities typedef enum DMN_LNX_EntityKind { DMN_LNX_EntityKind_Null, - DMN_LNX_EntityKind_Root, DMN_LNX_EntityKind_Process, + DMN_LNX_EntityKind_ProcessCtx, DMN_LNX_EntityKind_Thread, DMN_LNX_EntityKind_Module, - DMN_LNX_EntityKind_COUNT -} -DMN_LNX_EntityKind; +} DMN_LNX_EntityKind; typedef enum DMN_LNX_ThreadState { @@ -120,120 +106,178 @@ typedef enum DMN_LNX_ThreadState DMN_LNX_ThreadState_PendingCreation, } DMN_LNX_ThreadState; -typedef struct DMN_LNX_Entity DMN_LNX_Entity; -struct DMN_LNX_Entity +typedef struct DMN_LNX_Thread { - // entity hierarchy - DMN_LNX_Entity *first; - DMN_LNX_Entity *last; - DMN_LNX_Entity *next; - DMN_LNX_Entity *prev; - DMN_LNX_Entity *parent; + pid_t tid; + DMN_LNX_ThreadState state; + struct DMN_LNX_Process *process; + void *reg_block; + B32 is_reg_block_dirty; + B32 pass_through_signal; + U64 pass_through_signo; + U64 orig_rax; - // common - DMN_LNX_EntityKind kind; - U32 gen; - Arch arch; - U64 id; + struct DMN_LNX_Thread *next; + struct DMN_LNX_Thread *prev; +} DMN_LNX_Thread; - // process - Arena *arena; - int fd; - pid_t tracer_tid; - B32 debug_subprocesses; - U64 rdebug_vaddr; - ELF_Class dl_class; - HashTable *loaded_modules_ht; - DMN_LNX_Probe **probes; - DMN_ActiveTrap *first_probe_trap; - DMN_ActiveTrap *last_probe_trap; - U64 main_thread_exit_code; - U64 thread_count; +typedef struct DMN_LNX_ThreadPtrNode +{ + DMN_LNX_Thread *v; + struct DMN_LNX_ThreadPtrNode *next; + struct DMN_LNX_ThreadPtrNode *prev; +} DMN_LNX_ThreadPtrNode; - // process x64 - U64 xcr0; - U64 xsave_size; - X64_XSaveLayout xsave_layout; +typedef struct DMN_LNX_ThreadPtrList +{ + U64 count; + DMN_LNX_ThreadPtrNode *first; + DMN_LNX_ThreadPtrNode *last; +} DMN_LNX_ThreadPtrList; - // thread - void *reg_block; - B32 expecting_dummy_sigstop; - B32 is_reg_block_dirty; - B32 pass_through_signal; - U64 pass_through_signo; - DMN_LNX_ThreadState thread_state; - U64 thread_local_base; - U64 orig_rax; - - // module - U64 module_name_vaddr; +typedef struct DMN_LNX_Module +{ + U64 name_vaddr; U64 base_vaddr; + U64 name_space_id; + U64 size; U64 phvaddr; U64 phentsize; U64 phcount; - U64 name_space_id; + U64 tls_index; + U64 tls_offset; B8 is_live; B8 is_main; -}; -typedef struct DMN_LNX_EntityNode DMN_LNX_EntityNode; -struct DMN_LNX_EntityNode + struct DMN_LNX_Module *next; + struct DMN_LNX_Module *prev; +} DMN_LNX_Module; + +typedef struct DMN_LNX_ModulePtrNode { - DMN_LNX_EntityNode *next; - DMN_LNX_Entity *v; -}; + DMN_LNX_Module *v; + struct DMN_LNX_ModulePtrNode *next; +} DMN_LNX_ModulePtrNode; -typedef struct DMN_LNX_EntityList DMN_LNX_EntityList; -struct DMN_LNX_EntityList +typedef struct DMN_LNX_ModulePtrList +{ + U64 count; + DMN_LNX_ModulePtrNode *first; + DMN_LNX_ModulePtrNode *last; +} DMN_LNX_ModulePtrList; + +typedef enum +{ + DMN_LNX_ProcessState_Null, + DMN_LNX_ProcessState_Launch, + DMN_LNX_ProcessState_Attach, + DMN_LNX_ProcessState_WaitForExec, + DMN_LNX_ProcessState_ExecFailedDoExit, + DMN_LNX_ProcessState_Normal, +} DMN_LNX_ProcessState; + +typedef enum +{ + DMN_LNX_CreateProcessFlag_DebugSubprocesses = (1 << 0), + DMN_LNX_CreateProcessFlag_Rebased = (1 << 1), + DMN_LNX_CreateProcessFlag_Cow = (1 << 2), + DMN_LNX_CreateProcessFlag_ClonedMemory = (1 << 3), +} DMN_LNX_CreateProcessFlags; + +typedef struct DMN_LNX_Process +{ + pid_t pid; + int fd; + DMN_LNX_ProcessState state; + B32 debug_subprocesses; + B32 is_cow; + B32 vfork_with_spoof; + U64 thread_count; + DMN_LNX_Thread *first_thread; + DMN_LNX_Thread *last_thread; + U64 main_thread_exit_code; + struct DMN_LNX_Process *parent_process; + struct DMN_LNX_ProcessCtx *ctx; + + + struct DMN_LNX_Process *next; + struct DMN_LNX_Process *prev; +} DMN_LNX_Process; + +typedef struct DMN_LNX_ProcessPtrNode +{ + DMN_LNX_Process *v; + struct DMN_LNX_ProcessPtrNode *next; +} DMN_LNX_ProcessPtrNode; + +typedef struct DMN_LNX_ProcessPtrList +{ + U64 count; + DMN_LNX_ProcessPtrNode *first; + DMN_LNX_ProcessPtrNode *last; +} DMN_LNX_ProcessPtrList; + +typedef struct DMN_LNX_ProcessCtx +{ + Arena *arena; + Arch arch; + U64 rdebug_vaddr; + ELF_Class dl_class; + HashTable *loaded_modules_ht; + DMN_LNX_Probe **probes; + DMN_ActiveTrap *first_probe_trap; + DMN_ActiveTrap *last_probe_trap; + DMN_LNX_Module *first_module; + DMN_LNX_Module *last_module; + U64 module_count; + U64 ref_count; + + String8List free_reg_blocks; + String8List free_reg_block_nodes; + + // x64 + U64 xcr0; + U64 xsave_size; + X64_XSaveLayout xsave_layout; +} DMN_LNX_ProcessCtx; + +typedef struct DMN_LNX_Entity +{ + union + { + DMN_LNX_Process process; + DMN_LNX_ProcessCtx process_ctx; + DMN_LNX_Thread thread; + DMN_LNX_Module module; + struct + { + struct DMN_LNX_Entity *next; + }; + }; + U32 gen; + DMN_LNX_EntityKind kind; +} DMN_LNX_Entity; + +typedef struct DMN_LNX_EntityNode +{ + DMN_LNX_Entity *v; + struct DMN_LNX_EntityNode *next; +} DMN_LNX_EntityNode; + +typedef struct DMN_LNX_EntityList { U64 count; DMN_LNX_EntityNode *first; DMN_LNX_EntityNode *last; -}; +} DMN_LNX_EntityList; //////////////////////////////// -//~ rjf: Main State Bundle +//~ Global State -typedef U32 DMN_LNX_ProcessLaunchState; -enum -{ - DMN_LNX_ProcessLaunchState_Null, - DMN_LNX_ProcessLaunchState_Exec, - DMN_LNX_ProcessLaunchState_Attach, - DMN_LNX_ProcessLaunchState_Exit, -}; - -typedef struct DMN_LNX_ProcessLaunch DMN_LNX_ProcessLaunch; -struct DMN_LNX_ProcessLaunch -{ - B32 debug_subprocesses; - pid_t pid; - DMN_LNX_ProcessLaunchState state; - - DMN_LNX_ProcessLaunch *next; - DMN_LNX_ProcessLaunch *prev; -}; - -typedef struct DMN_LNX_ProcessLaunchList DMN_LNX_ProcessLaunchList; -struct DMN_LNX_ProcessLaunchList -{ - U64 count; - DMN_LNX_ProcessLaunch *first; - DMN_LNX_ProcessLaunch *last; -}; - -typedef struct DMN_LNX_Halt -{ - pid_t halter_pid; -} DMN_LNX_Halt; - -typedef struct DMN_LNX_State DMN_LNX_State; -struct DMN_LNX_State +typedef struct DMN_LNX_State { Arena *arena; - pid_t tracer_tid; - // rjf: access locking mechanism Mutex access_mutex; B32 access_run_state; @@ -247,11 +291,13 @@ struct DMN_LNX_State HashTable *tid_ht; // thread id -> thread entity HashTable *pid_ht; // process id -> process entity - // pid tracking - U64 active_process_count; - DMN_LNX_ProcessLaunchList pending_procs; - DMN_LNX_ProcessLaunchList free_pids; + // process tracking + U64 process_count; + DMN_LNX_Process *first_process; + DMN_LNX_Process *last_process; + // process/thread creation tracking + U64 process_pending_creation; U64 threads_pending_creation; // halter @@ -265,23 +311,17 @@ struct DMN_LNX_State B32 is_tls_detected; DMN_LNX_DbDesc tls_modid_desc; DMN_LNX_DbDesc tls_offset_desc; -}; +} DMN_LNX_State; //////////////////////////////// //~ Globals -global DMN_LNX_State *dmn_lnx_state = 0; -thread_static B32 dmn_lnx_ctrl_thread = 0; - -read_only global DMN_LNX_Entity dmn_lnx_nil_entity_ = {&dmn_lnx_nil_entity_, &dmn_lnx_nil_entity_, &dmn_lnx_nil_entity_, &dmn_lnx_nil_entity_, &dmn_lnx_nil_entity_}; -read_only global DMN_LNX_Entity *dmn_lnx_nil_entity = &dmn_lnx_nil_entity_; +global B32 dmn_lnx_ctrl_thread; +global DMN_LNX_State *dmn_lnx_state; //////////////////////////////// -//~ rjf: Helpers +//~ Memory R/W -internal DMN_LNX_EntityNode * dmn_lnx_entity_list_push(Arena *arena, DMN_LNX_EntityList *list, DMN_LNX_Entity *v); - -//- rjf: file descriptor memory reading/writing helpers internal U64 dmn_lnx_read(int memory_fd, Rng1U64 range, void *dst); internal B32 dmn_lnx_write(int memory_fd, Rng1U64 range, void *src); internal String8 dmn_lnx_read_string_capped(Arena *arena, int memory_fd, U64 base_vaddr, U64 cap_size); @@ -289,35 +329,114 @@ internal String8 dmn_lnx_read_string(Arena *arena, int memory_fd, U64 base_vaddr #define dmn_lnx_read_struct(fd, vaddr, ptr) dmn_lnx_read((fd), r1u64((vaddr), (vaddr)+sizeof(*(ptr))), (ptr)) #define dmn_lnx_write_struct(fd, vaddr, ptr) dmn_lnx_write((fd), r1u64((vaddr), (vaddr)+sizeof(*(ptr))), (ptr)) -//- rjf: pid => info extraction -internal String8 dmn_lnx_exe_path_from_pid(Arena *arena, pid_t pid); -internal String8 dmn_lnx_dl_path_from_pid(Arena *arena, pid_t pid, U64 auxv_base); -internal ELF_Hdr64 dmn_lnx_ehdr_from_pid(pid_t pid); -internal DMN_LNX_ProcessAuxv dmn_lnx_auxv_from_pid(pid_t pid, ELF_Class elf_class); +//////////////////////////////// +//~ ELF/GNU info -//- ELF/GNU info from memory +internal Rng1U64 dmn_lnx_compute_image_vrange(int memory_fd, ELF_Class elf_class, U64 rebase, U64 e_phaddr, U64 e_phentsize, U64 e_phnum); internal DMN_LNX_DynamicInfo dmn_lnx_dynamic_info_from_memory(int memory_fd, ELF_Class elf_Class, U64 rebase, U64 dynamic_vaddr); internal U64 dmn_lnx_rdebug_vaddr_from_memory(int memory_fd, U64 loader_vaddr, B32 is_rebased); //////////////////////////////// -//~ rjf: Entity Functions +//~ Process Info -internal DMN_LNX_Entity * dmn_lnx_entity_alloc(DMN_LNX_Entity *parent, DMN_LNX_EntityKind kind); -internal void dmn_lnx_entity_release(DMN_LNX_Entity *entity); -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); +internal String8 dmn_lnx_exe_path_from_pid(Arena *arena, pid_t pid); +internal String8 dmn_lnx_dl_path_from_pid(Arena *arena, pid_t pid, U64 auxv_base); +internal ELF_Hdr64 dmn_lnx_ehdr_from_pid(pid_t pid); +internal DMN_LNX_Auxv dmn_lnx_auxv_from_pid(pid_t pid, ELF_Class elf_class); +internal DMN_LNX_Thread * dmn_lnx_thread_from_pid(pid_t pid); +internal DMN_LNX_Process * dmn_lnx_process_from_pid(pid_t pid); -//- Process -internal void dmn_lnx_process_install_probes(); +//////////////////////////////// +//~ Entity -//- 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); -internal void dmn_lnx_thread_write_sp(DMN_LNX_Entity *thread, U64 sp); -internal B32 dmn_lnx_thread_read_reg_block(DMN_LNX_Entity *thread); -internal B32 dmn_lnx_thread_write_reg_block(DMN_LNX_Entity *thread); -internal B32 dmn_lnx_set_single_step_flag(DMN_LNX_Entity *thread, B32 is_on); +// alloc +internal DMN_LNX_Entity * dmn_lnx_entity_alloc(DMN_LNX_EntityKind kind); +internal DMN_LNX_Process * dmn_lnx_process_alloc(pid_t pid, DMN_LNX_ProcessState state, DMN_LNX_Process *parent_process, B32 debug_subprocess, B32 is_cow); +internal DMN_LNX_ProcessCtx * dmn_lnx_process_ctx_alloc(DMN_LNX_Process *process, B32 is_rebased); +internal DMN_LNX_Thread * dmn_lnx_thread_alloc(DMN_LNX_Process *process, DMN_LNX_ThreadState thread_state, pid_t tid); +internal DMN_LNX_Module * dmn_lnx_module_alloc(DMN_LNX_ProcessCtx *ctx, int memory_fd, U64 base_vaddr, U64 name_vaddr, U64 name_space_id, B32 is_main); + +// release +internal void dmn_lnx_entity_release(DMN_LNX_Entity *entity); +internal void dmn_lnx_process_release(DMN_LNX_Process *process); +internal void dmn_lnx_process_ctx_release(DMN_LNX_ProcessCtx *process_ctx); +internal void dmn_lnx_thread_release(DMN_LNX_Thread *thread); +internal void dmn_lnx_module_release(DMN_LNX_ProcessCtx *ctx, DMN_LNX_Module *module); + +// clone +internal DMN_LNX_ProcessCtx * dmn_lnx_process_ctx_clone(DMN_LNX_Process *process, DMN_LNX_ProcessCtx *ctx); +internal DMN_LNX_Module * dmn_lnx_module_clone(DMN_LNX_ProcessCtx *process_ctx, DMN_LNX_Module *module); + +// entity -> handle +internal DMN_Handle dmn_lnx_handle_from_entity(DMN_LNX_Entity *entity); +internal DMN_Handle dmn_lnx_handle_from_process(DMN_LNX_Process *process); +internal DMN_Handle dmn_lnx_handle_from_process_ctx(DMN_LNX_ProcessCtx *process_ctx); +internal DMN_Handle dmn_lnx_handle_from_thread(DMN_LNX_Thread *thread); +internal DMN_Handle dmn_lnx_handle_from_module(DMN_LNX_Module *module); + +// handle -> entity +internal DMN_LNX_Entity * dmn_lnx_entity_from_handle(DMN_Handle handle, DMN_LNX_EntityKind expected_kind); +internal DMN_LNX_Process * dmn_lnx_process_from_handle(DMN_Handle process_handle); +internal DMN_LNX_ProcessCtx * dmn_lnx_process_ctx_from_handle(DMN_Handle process_ctx_handle); +internal DMN_LNX_Thread * dmn_lnx_thread_from_handle(DMN_Handle thread_handle); +internal DMN_LNX_Module * dmn_lnx_module_from_handle(DMN_Handle module_handle); + +//////////////////////////////// +//~ Process Helpers + +internal void dmn_lnx_process_trap_probes(DMN_LNX_Process *process); +internal void dmn_lnx_process_untrap_probes(DMN_LNX_Process *process); + +//////////////////////////////// +//~ Thread Helpers + +internal U64 dmn_lnx_thread_read_ip(DMN_LNX_Thread *thread); +internal U64 dmn_lnx_thread_read_sp(DMN_LNX_Thread *thread); +internal void dmn_lnx_thread_write_ip(DMN_LNX_Thread *thread, U64 ip); +internal void dmn_lnx_thread_write_sp(DMN_LNX_Thread *thread, U64 sp); +internal B32 dmn_lnx_thread_read_reg_block(DMN_LNX_Thread *thread); +internal B32 dmn_lnx_thread_write_reg_block(DMN_LNX_Thread *thread); +internal B32 dmn_lnx_set_single_step_flag(DMN_LNX_Thread *thread, B32 is_on); + +//////////////////////////////// +//~ List Helpers + +internal void dmn_lnx_thread_ptr_list_push_node(DMN_LNX_ThreadPtrList *list, DMN_LNX_ThreadPtrNode *n); +internal DMN_LNX_ThreadPtrNode * dmn_lnx_thread_ptr_list_push(Arena *arena, DMN_LNX_ThreadPtrList *list, DMN_LNX_Thread *v); +internal DMN_LNX_ModulePtrNode * dmn_lnx_module_ptr_list_push(Arena *arena, DMN_LNX_ModulePtrList *list, DMN_LNX_Module *v); +internal DMN_LNX_ProcessPtrNode * dmn_lnx_process_ptr_list_push(Arena *arena, DMN_LNX_ProcessPtrList *list, DMN_LNX_Process *v); + +//////////////////////////////// +//~ Debug Event Pushers + +internal void dmn_lnx_push_event_create_process(Arena *arena, DMN_EventList *events, DMN_LNX_Process *process); +internal void dmn_lnx_push_event_exit_process(Arena *arena, DMN_EventList *events, DMN_LNX_Process *process); +internal void dmn_lnx_push_event_create_thread(Arena *arena, DMN_EventList *events, DMN_LNX_Thread *thread); +internal void dmn_lnx_push_event_exit_thread(Arena *arena, DMN_EventList *events, DMN_LNX_Thread *thread, U64 exit_code); +internal void dmn_lnx_push_event_load_module(Arena *arena, DMN_EventList *events, DMN_LNX_Thread *thread, DMN_LNX_Module *module); +internal void dmn_lnx_push_event_unload_module(Arena *arena, DMN_EventList *events, DMN_LNX_Process *process, DMN_LNX_Module *module); +internal void dmn_lnx_push_event_handshake_complete(Arena *arena, DMN_EventList *events, DMN_LNX_Process *process); +internal void dmn_lnx_push_event_breakpoint(Arena *arena, DMN_EventList *events, DMN_LNX_Thread *thread, U64 address); +internal void dmn_lnx_push_event_single_step(Arena *arena, DMN_EventList *events, DMN_LNX_Thread *thread); +internal void dmn_lnx_push_event_exception(Arena *arena, DMN_EventList *events, DMN_LNX_Thread *thread, U64 signo); +internal void dmn_lnx_push_event_halt(Arena *arena, DMN_EventList *events); +internal void dmn_lnx_push_event_not_attached(Arena *arena, DMN_EventList *events); + +//////////////////////////////// +//~ Debug Event + +internal DMN_LNX_Thread * dmn_lnx_event_create_thread(Arena *arena, DMN_EventList *events, DMN_LNX_Process *process, pid_t tid); +internal void dmn_lnx_event_exit_thread(Arena *arena, DMN_EventList *events, pid_t tid, U64 exit_code); +internal DMN_LNX_Process * dmn_lnx_event_create_process(Arena *arena, DMN_EventList *events, pid_t pid, DMN_LNX_Process *parent_process, DMN_LNX_CreateProcessFlags flags); +internal void dmn_lnx_event_exit_process(Arena *arena, DMN_EventList *events, pid_t pid); +internal void dmn_lnx_event_load_module(Arena *arena, DMN_EventList *events, DMN_LNX_Thread *thread, U64 name_space_id, U64 new_link_map_vaddr); +internal void dmn_lnx_event_unload_module(Arena *arena, DMN_EventList *events, DMN_LNX_Process *process, U64 rdebug_vaddr); +internal void dmn_lnx_event_breakpoint(Arena *arena, DMN_EventList *events, DMN_ActiveTrap *user_traps, pid_t tid); +internal void dmn_lnx_event_data_breakpoint(Arena *arena, DMN_EventList *events, pid_t tid); +internal void dmn_lnx_event_halt(Arena *arena, DMN_EventList *events); +internal void dmn_lnx_event_single_step(Arena *arena, DMN_EventList *events, pid_t tid); +internal void dmn_lnx_event_exception(Arena *arena, DMN_EventList *events, pid_t tid, U64 signo); +internal DMN_LNX_Process * dmn_lnx_event_attach(Arena *arena, DMN_EventList *events, pid_t pid); #endif // DEMON_CORE_LINUX_H +