// Copyright (c) Epic Games Tools // Licensed under the MIT license (https://opensource.org/license/mit/) // TODO(allen): run controls: ignore_previous_exception //////////////////////////////// //~ allen: Elf Parsing Code #include "syms/syms_elf_inc.c" //////////////////////////////// //~ rjf: Globals global B32 demon_lnx_already_has_halt_injection = false; global U64 demon_lnx_halt_code = 0; global U64 demon_lnx_halt_user_data = 0; global B32 demon_lnx_new_process_pending = false; global Arena *demon_lnx_event_arena = 0; global DEMON_EventList demon_lnx_queued_events = {0}; global U32 demon_lnx_ptrace_options = (PTRACE_O_TRACEEXIT| PTRACE_O_EXITKILL| PTRACE_O_TRACEFORK| PTRACE_O_TRACEVFORK| PTRACE_O_TRACECLONE); //////////////////////////////// //~ rjf: Helpers internal DEMON_LNX_ThreadExt* demon_lnx_thread_ext(DEMON_Entity *entity){ DEMON_LNX_ThreadExt *result = (DEMON_LNX_ThreadExt*)&entity->ext; return(result); } internal B32 demon_lnx_attach_pid(Arena *arena, pid_t pid, DEMON_LNX_AttachNode **new_node){ B32 result = false; int attach_result = ptrace(PTRACE_ATTACH, pid, 0, 0); if (attach_result == -1){ // TODO(allen): attach denied } else{ // return a new attachment node as soon as the ptrace exists. we use these nodes // for cleanup on failure *and* for initializing on success. either way we need // to see all new attachments whether or not they fully initialized correctly. DEMON_LNX_AttachNode *proc_attachment = push_array_no_zero(arena, DEMON_LNX_AttachNode, 1); proc_attachment->next = 0; proc_attachment->pid = pid; *new_node = proc_attachment; int status = 0; pid_t wait_id = waitpid(pid, &status, __WALL); // NOTE(allen): if wait_id != pid we don't know what that means; study that case before // deciding how error handling around it works. if (wait_id == pid){ int setoptions_result = ptrace(PTRACE_SETOPTIONS, pid, 0, PtrFromInt(demon_lnx_ptrace_options)); if (setoptions_result == -1){ // TODO(allen): setup failed } else{ result = true; } } } return(result); } internal String8 demon_lnx_executable_path_from_pid(Arena *arena, pid_t pid){ // get symbolic path Temp scratch = scratch_begin(&arena, 1); String8 exe_symbol_path = push_str8f(scratch.arena, "/proc/%d/exe", pid); // try to read the link for a bit Temp restore_point = temp_begin(arena); B32 got_final_result = false; U8 *buffer = 0; int size = 0; S64 cap = PATH_MAX; for (S64 r = 0; r < 4; cap *= 2, r += 1){ temp_end(restore_point); buffer = push_array_no_zero(arena, U8, cap); size = readlink((char*)exe_symbol_path.str, (char*)buffer, cap); if (size < cap){ got_final_result = true; break; } } // finalize result String8 result = {0}; if (!got_final_result || size == -1){ temp_end(restore_point); } else{ arena_pop(arena, (cap - size - 1)); result = str8(buffer, size + 1); } scratch_end(scratch); return(result); } internal int demon_lnx_open_memory_fd_for_pid(pid_t pid){ Temp scratch = scratch_begin(0, 0); String8 memory_path = push_str8f(scratch.arena, "/proc/%i/mem", pid); int result = open((char*)memory_path.str, O_RDWR); scratch_end(scratch); return(result); } internal Arch demon_lnx_arch_from_pid(pid_t pid){ Temp scratch = scratch_begin(0, 0); Arch result = Arch_Null; // exe path String8 exe_path = demon_lnx_executable_path_from_pid(scratch.arena, pid); // handle to exe int exe_fd = -1; if (exe_path.size != 0){ exe_fd = open((char*)exe_path.str, O_RDONLY); } // elf identification B32 is_elf = false; U8 e_ident[SYMS_ElfIdentifier_NIDENT] = {0}; if (exe_fd >= 0){ if (pread(exe_fd, e_ident, sizeof(e_ident), 0) == sizeof(e_ident)){ is_elf = (e_ident[SYMS_ElfIdentifier_MAG0] == 0x7f && e_ident[SYMS_ElfIdentifier_MAG1] == 'E' && e_ident[SYMS_ElfIdentifier_MAG2] == 'L' && e_ident[SYMS_ElfIdentifier_MAG3] == 'F'); } } // elf class U8 elf_class = 0; if (is_elf){ elf_class = e_ident[SYMS_ElfIdentifier_CLASS]; } // exe header data SYMS_ElfEhdr64 ehdr = {0}; switch (elf_class){ case 1: { SYMS_ElfEhdr32 ehdr32 = {0}; if (pread(exe_fd, &ehdr32, sizeof(ehdr32), 0) == sizeof(ehdr32)){ ehdr = syms_elf_ehdr64_from_ehdr32(ehdr32); } }break; case 2: { pread(exe_fd, &ehdr, sizeof(ehdr), 0); }break; } // determine machine type switch (ehdr.e_machine){ case SYMS_ElfMachineKind_386: { result = Arch_x86; }break; case SYMS_ElfMachineKind_ARM: { result = Arch_arm32; }break; case SYMS_ElfMachineKind_X86_64: { result = Arch_x64; }break; case SYMS_ElfMachineKind_AARCH64: { result = Arch_arm64; }break; } scratch_end(scratch); return(result); } internal DEMON_LNX_ProcessAux demon_lnx_aux_from_pid(pid_t pid, Arch arch){ DEMON_LNX_ProcessAux result = {0}; B32 addr_32bit = (arch == Arch_x86 || arch == Arch_arm32); // open aux data Temp scratch = scratch_begin(0, 0); String8 auxv_symbol_path = push_str8f(scratch.arena, "/proc/%d/auxv", pid); int aux_fd = open((char*)auxv_symbol_path.str, O_RDONLY); // scan aux data if (aux_fd >= 0){ for (;;){ result.filled = true; // read next aux U64 type = 0; U64 val = 0; if (addr_32bit){ SYMS_ElfAuxv32 aux; if (read(aux_fd, &aux, sizeof(aux)) != sizeof(aux)){ goto brkloop; } type = aux.a_type; val = aux.a_val; } else{ SYMS_ElfAuxv64 aux; if (read(aux_fd, &aux, sizeof(aux)) != sizeof(aux)){ goto brkloop; } type = aux.a_type; val = aux.a_val; } // place value in result switch (type){ default:break; case SYMS_ElfAuxType_NULL: goto brkloop; break; case SYMS_ElfAuxType_PHNUM: result.phnum = val; break; case SYMS_ElfAuxType_PHENT: result.phent = val; break; case SYMS_ElfAuxType_PHDR: result.phdr = val; break; case SYMS_ElfAuxType_EXECFN: result.execfn = val; break; } } brkloop:; close(aux_fd); } scratch_end(scratch); return(result); } internal DEMON_LNX_PhdrInfo demon_lnx_phdr_info_from_memory(int memory_fd, B32 is_32bit, U64 phvaddr, U64 phentsize, U64 phcount){ DEMON_LNX_PhdrInfo result = {0}; result.range.min = max_U64; // how much phdr will we read? U64 phdr_size_expected = (is_32bit?sizeof(SYMS_ElfPhdr32):sizeof(SYMS_ElfPhdr64)); U64 phdr_stride = (phentsize?phentsize:phdr_size_expected); U64 phdr_read_size = ClampTop(phdr_stride, phdr_size_expected); // scan table U64 va = phvaddr; for (U64 i = 0; i < phcount; i += 1, va += phdr_stride){ // get type and range SYMS_ElfPKind p_type = 0; U64 p_vaddr = 0; U64 p_memsz = 0; if (is_32bit){ SYMS_ElfPhdr32 phdr32 = {0}; demon_lnx_read_memory(memory_fd, &phdr32, va, phdr_read_size); p_type = phdr32.p_type; p_vaddr = phdr32.p_vaddr; p_memsz = phdr32.p_memsz; } else{ SYMS_ElfPhdr64 phdr64 = {0}; demon_lnx_read_memory(memory_fd, &phdr64, va, phdr_read_size); p_type = phdr64.p_type; p_vaddr = phdr64.p_vaddr; p_memsz = phdr64.p_memsz; } // save useful info switch (p_type){ case SYMS_ElfPKind_Dynamic: { result.dynamic = p_vaddr; }break; case SYMS_ElfPKind_Load: { U64 min = p_vaddr; U64 max = p_vaddr + p_memsz; result.range.min = Min(result.range.min, min); result.range.max = Max(result.range.max, max); }break; } } return(result); } internal DEMON_LNX_ModuleNode* demon_lnx_module_list_from_process(Arena *arena, DEMON_Entity *process){ Arch arch = (Arch)process->arch; B32 is_32bit = (arch == Arch_x86 || arch == Arch_arm32); int memory_fd = (int)process->ext_u64; // aux from pid DEMON_LNX_ProcessAux aux = demon_lnx_aux_from_pid((pid_t)process->id, arch); // extract info from program headers DEMON_LNX_PhdrInfo phdr_info = demon_lnx_phdr_info_from_memory(memory_fd, is_32bit, aux.phdr, aux.phent, aux.phnum); // linkmap first from memory space & dyn address U64 first_linkmap_va = 0; if (phdr_info.dynamic != 0){ U64 off = phdr_info.dynamic; for (;;){ SYMS_ElfDyn64 dyn = {0}; if (is_32bit){ SYMS_ElfDyn32 dyn32 = {0}; demon_lnx_read_memory(memory_fd, &dyn32, off, sizeof(dyn32)); dyn.tag = dyn32.tag; dyn.val = dyn32.val; off += sizeof(dyn32); } else{ demon_lnx_read_memory(memory_fd, &dyn, off, sizeof(dyn)); off += sizeof(dyn); } if (dyn.tag == SYMS_ElfDynTag_NULL){ break; } if (dyn.tag == SYMS_ElfDynTag_PLTGOT){ // True for x86 and x64 // vas[0] virtual address of .dynamic // vas[2] callback for resolving function address of relocation and if successful jumps to it. // // Code that sets up PLTGOT is in glibc/sysdeps/x86_64/dl_machine.h -> elf_machine_runtime_setup U64 vas_off = dyn.val; U64 vas[3] = {0}; demon_lnx_read_memory(memory_fd, vas, vas_off, sizeof(vas)); first_linkmap_va = vas[1]; break; } } } // setup output list DEMON_LNX_ModuleNode *first = 0; DEMON_LNX_ModuleNode *last = 0; // main module { DEMON_LNX_ModuleNode *node = push_array(arena, DEMON_LNX_ModuleNode, 1); SLLQueuePush(first, last, node); node->vaddr = phdr_info.range.min; node->size = phdr_info.range.max - phdr_info.range.min; node->name = aux.execfn; } // iterate link maps if (first_linkmap_va != 0){ U64 linkmap_va = first_linkmap_va; for (;;){ SYMS_ElfLinkMap64 linkmap = {0}; if (is_32bit){ // TOOD(nick): endian awarness SYMS_ElfLinkMap32 linkmap32 = {0}; demon_lnx_read_memory(memory_fd, &linkmap32, linkmap_va, sizeof(linkmap32)); linkmap.base = linkmap32.base; linkmap.name = linkmap32.name; linkmap.ld = linkmap32.ld; linkmap.next = linkmap32.next; } else{ demon_lnx_read_memory(memory_fd, &linkmap, linkmap_va, sizeof(linkmap)); } if (linkmap.base != 0){ // find phdrs for this module SYMS_U64 phvaddr = 0; SYMS_U64 phentsize = 0; SYMS_U64 phcount = 0; if (is_32bit){ SYMS_ElfEhdr32 ehdr = {0}; demon_lnx_read_memory(memory_fd, &ehdr, linkmap.base, sizeof(ehdr)); phvaddr = ehdr.e_phoff + linkmap.base; phentsize = ehdr.e_phentsize; phcount = ehdr.e_phnum; } else{ SYMS_ElfEhdr64 ehdr = {0}; demon_lnx_read_memory(memory_fd, &ehdr, linkmap.base, sizeof(ehdr)); phvaddr = ehdr.e_phoff + linkmap.base; phentsize = ehdr.e_phentsize; phcount = ehdr.e_phnum; } // extract info from phdrs DEMON_LNX_PhdrInfo module_phdr_info = demon_lnx_phdr_info_from_memory(memory_fd, is_32bit, phvaddr, phentsize, phcount); // save module node DEMON_LNX_ModuleNode *node = push_array(arena, DEMON_LNX_ModuleNode, 1); SLLQueuePush(first, last, node); node->vaddr = linkmap.base; node->size = module_phdr_info.range.max - module_phdr_info.range.min; node->name = linkmap.name; } linkmap_va = linkmap.next; if (linkmap_va == 0){ break; } } } return(first); } internal U64 demon_lnx_read_memory(int memory_fd, void *dst, U64 src, U64 size){ U64 bytes_read = 0; U8 *ptr = (U8*)dst; U8 *opl = ptr + size; U64 cursor = src; for (;ptr < opl;){ size_t to_read = (size_t)(opl - ptr); ssize_t actual_read = pread(memory_fd, ptr, to_read, cursor); if (actual_read == -1){ break; } ptr += actual_read; cursor += actual_read; bytes_read += actual_read; } return(bytes_read); } internal B32 demon_lnx_write_memory(int memory_fd, U64 dst, void *src, U64 size){ B32 result = true; U8 *ptr = (U8*)src; U8 *opl = ptr + size; U64 cursor = dst; for (;ptr < opl;){ size_t to_write = (size_t)(opl - ptr); ssize_t actual_write = pwrite(memory_fd, ptr, to_write, cursor); if (actual_write == -1){ result = false; break; } ptr += actual_write; cursor += actual_write; } return(result); } internal String8 demon_lnx_read_memory_str(Arena *arena, int memory_fd, U64 address){ // TODO(allen): this could be done better with a demon_lnx_read_memory // that returns a read amount instead of a success/fail. // scan piece by piece Temp scratch = scratch_begin(&arena, 1); String8List list = {0}; U64 max_cap = 256; U64 cap = max_cap; U64 read_p = address; for (;;){ U8 *block = push_array(scratch.arena, U8, cap); for (;cap > 0;){ if (demon_lnx_read_memory(memory_fd, block, read_p, cap)){ break; } cap /= 2; } read_p += cap; U64 block_opl = 0; for (;block_opl < cap; block_opl += 1){ if (block[block_opl] == 0){ break; } } if (block_opl > 0){ str8_list_push(scratch.arena, &list, str8(block, block_opl)); } if (block_opl < cap || cap == 0){ break; } } // assemble results String8 result = str8_list_join(arena, &list, 0); scratch_end(scratch); return(result); } internal void demon_lnx_regs_x64_from_usr_regs_x64(SYMS_RegX64 *dst, DEMON_LNX_UserRegsX64 *src){ dst->rax.u64 = src->rax; dst->rcx.u64 = src->rcx; dst->rdx.u64 = src->rdx; dst->rbx.u64 = src->rbx; dst->rsp.u64 = src->rsp; dst->rbp.u64 = src->rbp; dst->rsi.u64 = src->rsi; dst->rdi.u64 = src->rdi; dst->r8.u64 = src->r8; dst->r9.u64 = src->r9; dst->r10.u64 = src->r10; dst->r11.u64 = src->r11; dst->r12.u64 = src->r12; dst->r13.u64 = src->r13; dst->r14.u64 = src->r14; dst->r15.u64 = src->r15; dst->cs.u16 = src->cs; dst->ds.u16 = src->ds; dst->es.u16 = src->es; dst->fs.u16 = src->fs; dst->gs.u16 = src->gs; dst->ss.u16 = src->ss; dst->fsbase.u64 = src->fsbase; dst->gsbase.u64 = src->gsbase; dst->rip.u64 = src->rip; dst->rflags.u64 = src->rflags; } internal void demon_lnx_usr_regs_x64_from_regs_x64(DEMON_LNX_UserRegsX64 *dst, SYMS_RegX64 *src){ dst->rax = src->rax.u64; dst->rcx = src->rcx.u64; dst->rdx = src->rdx.u64; dst->rbx = src->rbx.u64; dst->rsp = src->rsp.u64; dst->rbp = src->rbp.u64; dst->rsi = src->rsi.u64; dst->rdi = src->rdi.u64; dst->r8 = src->r8.u64; dst->r9 = src->r9.u64; dst->r10 = src->r10.u64; dst->r11 = src->r11.u64; dst->r12 = src->r12.u64; dst->r13 = src->r13.u64; dst->r14 = src->r14.u64; dst->r15 = src->r15.u64; dst->cs = src->cs.u16; dst->ds = src->ds.u16; dst->es = src->es.u16; dst->fs = src->fs.u16; dst->gs = src->gs.u16; dst->ss = src->ss.u16; dst->fsbase = src->fsbase.u64; dst->gsbase = src->gsbase.u64; dst->rip = src->rip.u64; dst->rflags = src->rflags.u64; } //////////////////////////////// internal String8 demon_lnx_read_int_string(Arena *arena, int fd, int radix){ String8 integer = str8(0,0); int to_read = 0; int to_seek = 0; for (;;){ char b = 0; if (read(fd, &b, sizeof(b)) == 0){ break; } to_seek += 1; if ( ! char_is_digit(b, radix)){ break; } to_read += 1; } if (lseek(fd, -to_seek, SEEK_CUR) != -1) { char *buf = push_array_no_zero(arena, char, to_read + 1); read(fd, buf, to_read); buf[to_read] = '\0'; integer = str8((U8*)buf, (U64)to_read); } return(integer); } internal U64 demon_lnx_read_u64(int fd, int radix){ Temp scratch = scratch_begin(0, 0); String8 integer = demon_lnx_read_int_string(scratch.arena, fd, radix); U64 result = u64_from_str8(integer, radix); scratch_end(scratch); return(result); } internal S64 demon_lnx_read_s64(int fd, int radix){ Temp scratch = scratch_begin(0, 0); String8 integer = demon_lnx_read_int_string(scratch.arena, fd, radix); S64 result = s64_from_str8(integer, radix); scratch_end(scratch); return(result); } internal B32 demon_lnx_read_expect(int fd, char expect){ char got = 0; read(fd, &got, sizeof(got)); B32 result = (got == expect); if (!result){ lseek(fd, -1, SEEK_CUR); } return(result); } internal int demon_lnx_read_whitespace(int fd){ int whitespace_size = 0; for (;;){ if (!demon_lnx_read_expect(fd, ' ')){ if (!demon_lnx_read_expect(fd, '\t')){ break; } } whitespace_size += 1; } return whitespace_size; } internal String8 demon_lnx_read_string(Arena *arena, int fd){ String8 result = str8(0,0); int to_read = 0; int to_seek = 0; for (;;){ char b = 0; if (read(fd, &b, sizeof(b)) == 0) { break; } to_seek += 1; if (b == '\0' || b == '\n'){ break; } to_read += 1; } if (to_seek > 0 && lseek(fd, -to_seek, SEEK_CUR) != -1){ char *buf = push_array_no_zero(arena, char, to_read + 1); read(fd, buf, to_read); buf[to_read] = '\0'; result = str8((U8*)buf, to_read); } return(result); } internal int demon_lnx_open_maps(pid_t pid){ Temp scratch = scratch_begin(0, 0); String8 path = push_str8f(scratch.arena, "/proc/%d/maps", pid); int maps = open((char*)path.str, O_RDONLY); scratch_end(scratch); return(maps); } internal B32 demon_lnx_next_map(Arena *arena, int maps, DEMON_LNX_MapsEntry *entry_out){ B32 is_parsed = false; MemoryZeroStruct(entry_out); do{ U64 address_lo = 0; U64 address_hi = 0; DEMON_LNX_PermFlags perms = 0; U64 offset = 0; U64 dev_major = 0; U64 dev_minor = 0; U64 inode = 0; String8 pathname = str8(0,0); // address range address_lo = demon_lnx_read_u64(maps, 16); if (!demon_lnx_read_expect(maps, '-')){ break; } address_hi = demon_lnx_read_u64(maps, 16); if (demon_lnx_read_whitespace(maps) == 0){ break; } // permission flags char b; if (read(maps, &b, sizeof(b)) == 0){ break; } if (b=='r'){ perms |= DEMON_LNX_PermFlags_Read; } if (read(maps, &b, sizeof(b)) == 0){ break; } if (b=='w'){ perms |= DEMON_LNX_PermFlags_Write; } if (read(maps, &b, sizeof(b)) == 0){ break; } if (b=='x'){ perms |= DEMON_LNX_PermFlags_Exec; } if (read(maps, &b, sizeof(b)) == 0){ break; } if (b == 'p'){ perms |= DEMON_LNX_PermFlags_Private; } if (demon_lnx_read_whitespace(maps) == 0){ break; } // offset offset = demon_lnx_read_u64(maps, 16); if (demon_lnx_read_whitespace(maps) == 0){ break; } // dev dev_major = demon_lnx_read_u64(maps, 10); if (!demon_lnx_read_expect(maps, ':')){ break; } dev_minor = demon_lnx_read_u64(maps, 10); if (demon_lnx_read_whitespace(maps) == 0){ break; } // inode inode = demon_lnx_read_u64(maps, 10); if (demon_lnx_read_whitespace(maps) == 10){ break; } // pathname pathname = demon_lnx_read_string(arena, maps); // emit entry if en b = 0; read(maps, &b, sizeof(b)); if (b != '\n' && b != '\0') { break; } // fill result entry_out->address_lo = address_lo; entry_out->address_hi = address_hi; entry_out->perms = perms; entry_out->offset = offset; entry_out->dev_major = (U32)dev_major; entry_out->dev_minor = (U32)dev_minor; entry_out->inode = inode; entry_out->pathname = pathname; entry_out->type = DEMON_LNX_MapsEntryType_Null; entry_out->stack_tid = 0; if (str8_match(pathname, str8_lit("/"), StringMatchFlag_RightSideSloppy)){ entry_out->type = DEMON_LNX_MapsEntryType_Path; } else if (str8_match(pathname, str8_lit("[heap]"), 0)){ entry_out->type = DEMON_LNX_MapsEntryType_Heap; } else if (str8_match(pathname, str8_lit("[stack]"), 0)){ entry_out->type = DEMON_LNX_MapsEntryType_Stack; } else if (str8_match(pathname, str8_lit("[stack:"), StringMatchFlag_RightSideSloppy)){ entry_out->type = DEMON_LNX_MapsEntryType_Stack; String8 tid = str8_substr(pathname, r1u64(7, pathname.size - 8)); entry_out->stack_tid = (pid_t)u64_from_str8(tid, 10); } is_parsed = true; }while(0); return(is_parsed); } //////////////////////////////// //~ rjf: @demon_os_hooks Main Layer Initialization internal void demon_os_init(void){ demon_lnx_event_arena = arena_alloc(); } //////////////////////////////// //~ rjf: @demon_os_hooks Running/Halting internal DEMON_EventList demon_os_run(Arena *arena, DEMON_OS_RunCtrls *controls){ DEMON_EventList result = {0}; if (demon_ent_root == 0){ demon_push_event(arena, &result, DEMON_EventKind_NotInitialized); } else if (demon_ent_root->first == 0 && !demon_lnx_new_process_pending){ demon_push_event(arena, &result, DEMON_EventKind_NotAttached); } else{ Temp scratch = scratch_begin(&arena, 1); // use queued events if there are any if (demon_lnx_queued_events.first != 0){ // copy event queue for (DEMON_Event *node = demon_lnx_queued_events.first; node != 0; node = node->next){ DEMON_Event *copy = push_array_no_zero(arena, DEMON_Event, 1); MemoryCopyStruct(copy, node); SLLQueuePush(result.first, result.last, copy); } result.count = demon_lnx_queued_events.count; // zero stored queue MemoryZeroStruct(&demon_lnx_queued_events); arena_clear(demon_lnx_event_arena); } // get the single step thread (if any) DEMON_Entity *single_step_thread = controls->single_step_thread; // do setup B32 did_setup = false; U8 *trap_swap_bytes = 0; if (result.first == 0){ // TODO(allen): per-Arch implementation of single steps // set single step bit if (single_step_thread != 0){ switch (single_step_thread->arch){ case Arch_x86: { // TODO(allen): possibly buggy SYMS_RegX86 regs = {0}; demon_os_read_regs_x86(single_step_thread, ®s); regs.eflags.u32 |= 0x100; demon_os_write_regs_x86(single_step_thread, ®s); }break; case Arch_x64: { // TODO(allen): possibly buggy SYMS_RegX64 regs = {0}; demon_os_read_regs_x64(single_step_thread, ®s); regs.rflags.u64 |= 0x100; demon_os_write_regs_x64(single_step_thread, ®s); }break; } } // TODO(allen): per-Arch implementation of traps trap_swap_bytes = push_array_no_zero(scratch.arena, U8, controls->trap_count); { DEMON_OS_Trap *trap = controls->traps; for (U64 i = 0; i < controls->trap_count; i += 1, trap += 1){ if (demon_os_read_memory(trap->process, trap_swap_bytes + i, trap->address, 1)){ U8 int3 = 0xCC; demon_os_write_memory(trap->process, trap->address, &int3, 1); } else{ trap_swap_bytes[i] = 0xCC; } } } did_setup = true; } // do run B32 did_run = false; if (did_setup){ // continue non-frozen threads DEMON_LNX_EntityNode *resume_threads = 0; for (DEMON_Entity *process = demon_ent_root->first; process != 0; process = process->next){ if (process->kind == DEMON_EntityKind_Process){ // determine if this process is frozen B32 process_is_frozen = false; if (controls->run_entities_are_processes){ for (U64 i = 0; i < controls->run_entity_count; i += 1){ if (controls->run_entities[i] == process){ process_is_frozen = true; break; } } } for (DEMON_Entity *thread = process->first; thread != 0; thread = thread->next){ if (thread->kind == DEMON_EntityKind_Thread){ // determine if this thread is frozen B32 is_frozen = false; if (controls->single_step_thread != 0 && controls->single_step_thread != thread){ is_frozen = true; } else{ if (controls->run_entities_are_processes){ is_frozen = process_is_frozen; } else{ for (U64 i = 0; i < controls->run_entity_count; i += 1){ if (controls->run_entities[i] == thread){ is_frozen = true; break; } } } if (controls->run_entities_are_unfrozen){ is_frozen = !is_frozen; } } // continue if not frozen if (!is_frozen){ errno = 0; ptrace(PTRACE_CONT, (pid_t)thread->id, 0, 0); DEMON_LNX_EntityNode *thread_node = push_array_no_zero(scratch.arena, DEMON_LNX_EntityNode, 1); SLLStackPush(resume_threads, thread_node); thread_node->entity = thread; } } } } } // get next stop wait_for_stop: B32 did_dummy_stop = false; int status = 0; pid_t wait_id = waitpid(-1, &status, __WALL); // increment demon time demon_time += 1; // handle devent DEMON_Entity *thread = demon_ent_map_entity_from_id(DEMON_EntityKind_Thread, wait_id); if (thread == 0){ if (wait_id >= 0){ // TODO(allen): this isn't a great situation! From what I can tell there's no // options that I am super happy with for going from unknown tid -> pid. // We can parse it out of /proc//status; but I don't want to do that until // I'm forced to, because it seems like this shouldn't happen if the ptrace // API works correctly and we don't have any bugs in our demon entity system. } } else{ B32 thread_exit = false; U64 exit_code = 0; DEMON_Entity *process = thread->parent; // NOTE(allen): hitting this assert should never ever be possible, if our entities // are wired up correctly. it doesn't matter what ptrace or waitpid are doing. Assert(process != 0); // read register info U64 instruction_pointer = 0; union{ SYMS_RegX86 x86; SYMS_RegX64 x64; } regs = {0}; switch (thread->arch){ case Arch_x86: { demon_os_read_regs_x86(thread, ®s.x86); instruction_pointer = regs.x86.eip.u32; }break; case Arch_x64: { demon_os_read_regs_x64(thread, ®s.x64); instruction_pointer = regs.x64.rip.u64; }break; } // check stop status if (WIFEXITED(status)){ thread_exit = true; } if (WIFSIGNALED(status)){ exit_code = WTERMSIG(status); thread_exit = true; } // extra event list DEMON_EventList stop_events = {0}; if (WIFSTOPPED(status)){ switch (WSTOPSIG(status)){ case SIGTRAP: { switch (status >> 8){ case (SIGTRAP | (PTRACE_EVENT_EXIT << 8)): { // TODO(allen): (not sure actually, study this part) thread_exit = true; }break; case (SIGTRAP | (PTRACE_EVENT_CLONE << 8)): { // new thread coming unsigned long new_tid = 0; int get_message_result = ptrace(PTRACE_GETEVENTMSG, wait_id, 0, &new_tid); if (get_message_result == -1){ // TODO(allen): this isn't right, time to give up on getting this process. // this will likely lead to getting unrecognized wait_id s later. So we need // this stuff in the log to make sense of it still. } else{ // thread entity DEMON_Entity *new_thread = demon_ent_new(process, DEMON_EntityKind_Thread, new_tid); demon_thread_count += 1; DEMON_LNX_ThreadExt *thread_ext = demon_lnx_thread_ext(new_thread); thread_ext->expecting_dummy_sigstop = true; // thread event DEMON_Event *e = demon_push_event(arena, &stop_events, DEMON_EventKind_CreateThread); e->process = demon_ent_handle_from_ptr(process); e->thread = demon_ent_handle_from_ptr(new_thread); } }break; case (SIGTRAP | (PTRACE_EVENT_FORK << 8)): case (SIGTRAP | (PTRACE_EVENT_VFORK << 8)): { // new process coming unsigned long new_pid = 0; int get_message_result = ptrace(PTRACE_GETEVENTMSG, wait_id, 0, &new_pid); if (get_message_result == -1){ // TODO(allen): this isn't right, time to give up on getting this process. // this will likely lead to getting unrecognized wait_id s later. So we need // this stuff in the log to make sense of it still. } else{ Arch arch = demon_lnx_arch_from_pid(new_pid); // process entity DEMON_Entity *new_process = demon_ent_new(demon_ent_root, DEMON_EntityKind_Process, new_pid); new_process->arch = arch; new_process->ext_u64 = demon_lnx_open_memory_fd_for_pid(new_pid); demon_lnx_new_process_pending = false; // thread entity DEMON_Entity *new_thread = demon_ent_new(new_process, DEMON_EntityKind_Thread, new_pid); demon_thread_count += 1; DEMON_LNX_ThreadExt *thread_ext = demon_lnx_thread_ext(new_thread); thread_ext->expecting_dummy_sigstop = true; // process event { DEMON_Event *e = demon_push_event(arena, &stop_events, DEMON_EventKind_CreateProcess); e->process = demon_ent_handle_from_ptr(new_process); } // thread event { DEMON_Event *e = demon_push_event(arena, &stop_events, DEMON_EventKind_CreateThread); e->process = demon_ent_handle_from_ptr(new_process); e->thread = demon_ent_handle_from_ptr(new_thread); } } }break; default: { // check single step DEMON_EventKind e_kind = DEMON_EventKind_Trap; if (thread == single_step_thread){ e_kind = DEMON_EventKind_SingleStep; } // check bp if (e_kind == DEMON_EventKind_Trap){ DEMON_OS_Trap *trap = controls->traps; for (U64 i = 0; i < controls->trap_count; i += 1, trap += 1){ if (trap->process == process && trap->address == instruction_pointer - 1){ e_kind = DEMON_EventKind_Breakpoint; break; } } } // adjust ip after breakpoint if (e_kind == DEMON_EventKind_Breakpoint){ // TODO(allen): possibly buggy switch (thread->arch){ case Arch_x86: { instruction_pointer -= 1; regs.x86.eip.u32 = instruction_pointer; demon_os_write_regs_x86(thread, ®s.x86); }break; case Arch_x64: { instruction_pointer -= 1; regs.x64.rip.u64 = instruction_pointer; demon_os_write_regs_x64(thread, ®s.x64); }break; } } // event DEMON_Event *e = demon_push_event(arena, &stop_events, e_kind); e->process = demon_ent_handle_from_ptr(process); e->thread = demon_ent_handle_from_ptr(thread); e->instruction_pointer = instruction_pointer; }break; } }break; case SIGSTOP: { // TODO(allen): we need to figure out how we want to tell apart: // SIGSTOP All-Stop, SIGSTOP Halt, SIGSTOP "User" // what we're doing right now == big-time race conditions DEMON_LNX_ThreadExt *thread_ext = demon_lnx_thread_ext(thread); if (thread_ext->expecting_dummy_sigstop){ thread_ext->expecting_dummy_sigstop = false; did_dummy_stop = true; } else if (demon_lnx_already_has_halt_injection){ DEMON_Event *e = demon_push_event(arena, &stop_events, DEMON_EventKind_Halt); e->process = demon_ent_handle_from_ptr(process); e->thread = demon_ent_handle_from_ptr(thread); e->instruction_pointer = instruction_pointer; } else{ // TODO(allen): a signal we don't want to mess with (except to record that it happened maybe) // we should "hand it back" } }break; default: { #if 0 // these are a little special. the program cannot continue after these // unless the user first does something to change the state (move the IP, change a variable, w/e) case SIGABRT:case SIGFPE:case SIGSEGV: #endif // event DEMON_Event *e = demon_push_event(arena, &stop_events, DEMON_EventKind_Exception); e->process = demon_ent_handle_from_ptr(process); e->thread = demon_ent_handle_from_ptr(thread); e->instruction_pointer = instruction_pointer; e->signo = WSTOPSIG(status); }break; } } // entity cleanup if (thread_exit){ if (thread->id == process->id){ // generate events for threads & modules for (DEMON_Entity *entity = process->first; entity != 0; entity = entity->next){ if (entity->kind == DEMON_EntityKind_Thread){ DEMON_Event *e = demon_push_event(arena, &result, DEMON_EventKind_ExitThread); e->process = demon_ent_handle_from_ptr(process); e->thread = demon_ent_handle_from_ptr(entity); } else{ DEMON_Event *e = demon_push_event(arena, &result, DEMON_EventKind_UnloadModule); e->process = demon_ent_handle_from_ptr(process); e->module = demon_ent_handle_from_ptr(entity); } } // exit event DEMON_Event *e = demon_push_event(arena, &stop_events, DEMON_EventKind_ExitProcess); e->process = demon_ent_handle_from_ptr(process); e->code = exit_code; // free entity demon_ent_release_root_and_children(process); } else{ // exit event DEMON_Event *e = demon_push_event(arena, &stop_events, DEMON_EventKind_ExitThread); e->process = demon_ent_handle_from_ptr(process); e->thread = demon_ent_handle_from_ptr(thread); e->code = exit_code; // free entity demon_ent_release_root_and_children(thread); } } // update all module lists (for each process ...) DEMON_EventList module_change_events = {0}; for (DEMON_Entity *proc_node = demon_ent_root->first; proc_node != 0; proc_node = proc_node->next){ DEMON_LNX_ModuleNode *first_module = demon_lnx_module_list_from_process(scratch.arena, proc_node); DEMON_LNX_EntityNode *first_unloaded = 0; DEMON_LNX_EntityNode *last_unloaded = 0; // compute the delta (mark known modules, save list of unloaded modules) for (DEMON_Entity *entity = proc_node->first; entity != 0; entity = entity->next){ if (entity->kind == DEMON_EntityKind_Module){ U64 base = entity->id; U64 name = entity->ext_u64; B32 still_exists = false; for (DEMON_LNX_ModuleNode *module_node = first_module; module_node != 0; module_node = module_node->next){ if (module_node->vaddr == base && module_node->name == name){ module_node->already_known = true; still_exists = true; break; } } if (!still_exists){ DEMON_LNX_EntityNode *node = push_array_no_zero(scratch.arena, DEMON_LNX_EntityNode, 1); SLLQueuePush(first_unloaded, last_unloaded, node); node->entity = entity; } } } // handle unloads for (DEMON_LNX_EntityNode *unloaded_node = first_unloaded; unloaded_node != 0; unloaded_node = unloaded_node->next){ DEMON_Entity *module = unloaded_node->entity; // event { DEMON_Event *e = demon_push_event(arena, &module_change_events, DEMON_EventKind_UnloadModule); e->process = demon_ent_handle_from_ptr(proc_node); e->module = demon_ent_handle_from_ptr(module); } // free entity demon_ent_release_root_and_children(module); } // handle loads for (DEMON_LNX_ModuleNode *module_node = first_module; module_node != 0; module_node = module_node->next){ if (!module_node->already_known){ // entity DEMON_Entity *module = demon_ent_new(proc_node, DEMON_EntityKind_Module, module_node->vaddr); demon_module_count += 1; module->ext_u64 = module_node->name; // event { DEMON_Event *e = demon_push_event(arena, &module_change_events, DEMON_EventKind_LoadModule); e->process = demon_ent_handle_from_ptr(proc_node); e->module = demon_ent_handle_from_ptr(module); e->address = module_node->vaddr; e->size = module_node->size; } } } } // concat the events list (with module changes first) result.count = module_change_events.count + stop_events.count; result.first = module_change_events.first; result.last = module_change_events.last; if (stop_events.first != 0){ if (result.first != 0){ result.last->next = stop_events.first; result.last = stop_events.last; } else{ result.first = stop_events.first; result.last = stop_events.last; } } } // do we have a reason to keep going? B32 skip_this_stop = false; if (did_dummy_stop && result.count == 0){ skip_this_stop = true; } // ignore this stop, resume and wait again if (skip_this_stop){ if (wait_id != 0){ ptrace(PTRACE_CONT, (pid_t)wait_id, 0, 0); } goto wait_for_stop; } // stop all running threads for (DEMON_LNX_EntityNode *node = resume_threads; node != 0; node = node->next){ DEMON_Entity *thread = node->entity; pid_t thread_id = (pid_t)thread->id; if (thread_id != wait_id){ union sigval sv = {0}; sigqueue(thread_id, SIGSTOP, sv); DEMON_LNX_ThreadExt *thread_ext = demon_lnx_thread_ext(thread); thread_ext->expecting_dummy_sigstop = true; } } did_run = true; } // cleanup if (did_run){ // TODO(allen): per-Arch // unset traps { DEMON_OS_Trap *trap = controls->traps; for (U64 i = 0; i < controls->trap_count; i += 1, trap += 1){ U8 og_byte = trap_swap_bytes[i]; if (og_byte != 0xCC){ demon_os_write_memory(trap->process, trap->address, &og_byte, 1); } } } // TODO(allen): per-Arch // unset single step bit // the single step bit is automatically unset whenever we single step // but if *something else* happened, it will still be there ready to // confound us later; so here we're just being sure it's taken out. if (single_step_thread != 0){ // TODO(allen): possibly buggy switch (single_step_thread->arch){ case Arch_x86: { SYMS_RegX86 regs = {0}; demon_os_read_regs_x86(single_step_thread, ®s); regs.eflags.u32 &= ~0x100; demon_os_write_regs_x86(single_step_thread, ®s); }break; case Arch_x64: { SYMS_RegX64 regs = {0}; demon_os_read_regs_x64(single_step_thread, ®s); regs.rflags.u64 &= ~0x100; demon_os_write_regs_x64(single_step_thread, ®s); }break; } } } scratch_end(scratch); } return(result); } internal void demon_os_halt(U64 code, U64 user_data){ if (demon_ent_root != 0 && !demon_lnx_already_has_halt_injection){ DEMON_Entity *process = demon_ent_root->first; if (process != 0){ demon_lnx_already_has_halt_injection = true; demon_lnx_halt_code = code; demon_lnx_halt_user_data = user_data; union sigval sv = {0}; if (sigqueue(process->id, SIGSTOP, sv) == -1){ demon_lnx_already_has_halt_injection = false; } } } } // NOTE(allen): siginfo hint from old code: #if 0 { switch (siginfo.si_code){ // SI_KERNEL (hit int3; 0xCC) case 0x80: { // TODO(allen): breakpoint event }break; // TRAP_UNK, TRAP_HWBKPT, TRAP_BRKPT, TRAP_TRACE case 0x5: case 0x4: case 0x1: case 0x2: { // TODO(allen): breakpoint event (?) }break; case 0x3: case 0x0: { // TODO(allen): do nothing I guess? }break; } } #endif //////////////////////////////// //~ rjf: @demon_os_hooks Target Process Launching/Attaching/Killing/Detaching/Halting internal U32 demon_os_launch_process(OS_LaunchOptions *options){ U32 result = 0; Temp scratch = scratch_begin(0, 0); // arrange options char *binary = 0; char **args = 0; if (options->cmd_line.node_count > 0){ args = push_array_no_zero(scratch.arena, char*, options->cmd_line.node_count + 1); char **arg_ptr = args; for (String8Node *node = options->cmd_line.first; node != 0; node = node->next, arg_ptr += 1){ String8 string = push_str8_copy(scratch.arena, node->string); *arg_ptr = (char*)string.str; } *arg_ptr = 0; binary = args[0]; } char *path = 0; { String8 string = push_str8_copy(scratch.arena, options->path); path = (char*)string.str; } char **env = 0; if (options->env.node_count > 0){ env = push_array_no_zero(scratch.arena, char*, options->env.node_count + 1); char **env_ptr = env; for (String8Node *node = options->env.first; node != 0; node = node->next, env_ptr += 1){ String8 string = push_str8_copy(scratch.arena, node->string); *env_ptr = (char*)string.str; } *env_ptr = 0; } // fork if (binary != 0){ pid_t pid = fork(); if (pid == -1){ // TODO(allen): fork error } else if (pid == 0){ // NOTE(allen): child process int ptrace_result = ptrace(PTRACE_TRACEME, 0, 0, 0); if (ptrace_result != -1){ int chdir_result = chdir(path); if (chdir_result != -1){ execve(binary, args, env); } } // failed to init fully; abort so the parent can clean up the child abort(); } else{ // NOTE(allen): parent process // wait for child int status = 0; pid_t wait_id = waitpid(pid, &status, __WALL); // determine child launch status enum{ LaunchCode_Null, LaunchCode_FailBeforePtrace, LaunchCode_FailAfterPtrace, LaunchCode_Success, }; U32 launch_result = LaunchCode_Null; // NOTE(allen): if wait_id != pid we don't know what that means; study that case before // deciding how error handling around it works. if (wait_id == pid){ if (WIFSTOPPED(status)){ if (WSTOPSIG(status) == SIGTRAP){ launch_result = LaunchCode_Success; } else{ launch_result = LaunchCode_FailAfterPtrace; } } else{ launch_result = LaunchCode_FailBeforePtrace; } } // handle launch result switch (launch_result){ default: { // TODO(allen): error that we do not understand }break; case LaunchCode_FailBeforePtrace: { // TODO(allen): child ptrace init failed }break; case LaunchCode_FailAfterPtrace: { // need to specifically pull the exit status out of the child // or it will sit around as a zombie forever since it is ptraced. B32 cleanup_good = false; int detach_result = ptrace(PTRACE_DETACH, pid, 0, (void*)SIGCONT); if (detach_result != -1){ int status_cleanup = 0; pid_t wait_id_cleanup = waitpid(pid, &status_cleanup, __WALL); if (wait_id_cleanup == pid){ cleanup_good = true; } } if (cleanup_good){ // TODO(allen): child init failed } else{ // TODO(allen): child init failed; something went wrong and a process may have leaked } }break; case LaunchCode_Success: { int setoptions_result = ptrace(PTRACE_SETOPTIONS, pid, 0, PtrFromInt(demon_lnx_ptrace_options)); if (setoptions_result == -1){ // TODO(allen): ptrace setup failed; need to kill the child and clean it up } else{ result = pid; Arch arch = demon_lnx_arch_from_pid(pid); // process entity DEMON_Entity *process = demon_ent_new(demon_ent_root, DEMON_EntityKind_Process, pid); demon_proc_count += 1; process->arch = arch; process->ext_u64 = demon_lnx_open_memory_fd_for_pid(pid); // thread entity DEMON_Entity *thread = demon_ent_new(process, DEMON_EntityKind_Thread, pid); demon_thread_count += 1; // process event { DEMON_Event *e = demon_push_event(demon_lnx_event_arena, &demon_lnx_queued_events, DEMON_EventKind_CreateProcess); e->process = demon_ent_handle_from_ptr(process); } // thread event { DEMON_Event *e = demon_push_event(demon_lnx_event_arena, &demon_lnx_queued_events, DEMON_EventKind_CreateThread); e->process = demon_ent_handle_from_ptr(process); e->thread = demon_ent_handle_from_ptr(thread); } // get module list DEMON_LNX_ModuleNode *module_list = demon_lnx_module_list_from_process(scratch.arena, process); // for each module ... for (DEMON_LNX_ModuleNode *node = module_list; node != 0; node = node->next){ // module entity DEMON_Entity *module = demon_ent_new(process, DEMON_EntityKind_Module, node->vaddr); demon_module_count += 1; module->ext_u64 = node->name; // event { DEMON_Event *e = demon_push_event(demon_lnx_event_arena, &demon_lnx_queued_events, DEMON_EventKind_LoadModule); e->process = demon_ent_handle_from_ptr(process); e->module = demon_ent_handle_from_ptr(module); e->address = node->vaddr; e->size = node->size; } } // handshake event { DEMON_Event *e = demon_push_event(demon_lnx_event_arena, &demon_lnx_queued_events, DEMON_EventKind_HandshakeComplete); e->process = demon_ent_handle_from_ptr(process); e->thread = demon_ent_handle_from_ptr(thread); } } }break; } } } scratch_end(scratch); return(result); } internal B32 demon_os_attach_process(U32 pid){ B32 result = false; Temp scratch = scratch_begin(0, 0); DEMON_LNX_AttachNode *attachments = 0; DEMON_LNX_AttachNode *the_process = 0; // TODO(allen): double check that this logic only lets us // "attach" when pid is the id of the main thread of a process. // attach this process B32 attached_proc = false; if (kill(pid, 0) == -1){ // TODO(allen): process does not exist } else{ attached_proc = demon_lnx_attach_pid(scratch.arena, pid, &the_process); if (the_process != 0){ SLLStackPush(attachments, the_process); } } // open thread list if (attached_proc){ String8 threads_path = push_str8f(scratch.arena, "/proc/%d/task", pid); DIR *proc_dir = opendir((char*)threads_path.str); if (proc_dir == 0){ // TODO(allen): could not read proc threads somehow; no good! } else{ // attach all threads B32 attached_all_threads = true; for (;;){ struct dirent *entry = readdir(proc_dir); if (entry == 0){ break; } String8 name = str8_cstring(entry->d_name); if (str8_is_integer(name, 10)){ pid_t tid = u64_from_str8(name, 10); if (tid != pid){ DEMON_LNX_AttachNode *new_attachment = 0; B32 attached_this_thread = demon_lnx_attach_pid(scratch.arena, tid, &new_attachment); if (new_attachment != 0){ SLLStackPush(attachments, new_attachment); } if (!attached_this_thread){ attached_all_threads = false; break; } } } } closedir(proc_dir); if (attached_all_threads){ result = true; } } } // initialize new entities on success if (result){ Arch arch = demon_lnx_arch_from_pid(the_process->pid); // process entity DEMON_Entity *process = demon_ent_new(demon_ent_root, DEMON_EntityKind_Process, the_process->pid); demon_proc_count += 1; process->arch = arch; process->ext_u64 = demon_lnx_open_memory_fd_for_pid(the_process->pid); // process event { DEMON_Event *e = demon_push_event(demon_lnx_event_arena, &demon_lnx_queued_events, DEMON_EventKind_CreateProcess); e->process = demon_ent_handle_from_ptr(process); } // TODO(allen): happens on windows here? for (DEMON_LNX_AttachNode *node = attachments; node != 0; node = node->next){ DEMON_Entity *thread = demon_ent_new(process, DEMON_EntityKind_Thread, node->pid); demon_thread_count += 1; // thread event { DEMON_Event *e = demon_push_event(demon_lnx_event_arena, &demon_lnx_queued_events, DEMON_EventKind_CreateThread); e->process = demon_ent_handle_from_ptr(process); e->thread = demon_ent_handle_from_ptr(thread); } } // TODO(allen): sync modules in process } // cleanup on failure else{ for (DEMON_LNX_AttachNode *node = attachments; node != 0; node = node->next){ ptrace(PTRACE_DETACH, node->pid, 0, (void*)SIGCONT); } } scratch_end(scratch); return(result); } internal B32 demon_os_kill_process(DEMON_Entity *process, U32 exit_code){ B32 result = false; if (process != 0){ if (kill(process->id, SIGKILL) != -1){ result = true; } } return(result); } internal B32 demon_os_detach_process(DEMON_Entity *process){ B32 result = false; if (process != 0){ int detach_result = ptrace(PTRACE_DETACH, process->id, 0, 0); result = (detach_result != -1); } return(0); } //////////////////////////////// //~ rjf: @demon_os_hooks Entity Functions //- rjf: cleanup internal void demon_os_entity_cleanup(DEMON_Entity *entity) { // NOTE(rjf): no-op } //- rjf: introspection internal String8 demon_os_full_path_from_module(Arena *arena, DEMON_Entity *module){ DEMON_Entity *process = module->parent; int memory_fd = (int)process->ext_u64; U64 name_va = module->ext_u64; String8 result = demon_lnx_read_memory_str(arena, memory_fd, name_va); return(result); } internal U64 demon_os_stack_base_vaddr_from_thread(DEMON_Entity *thread){ Temp scratch = scratch_begin(0, 0); U64 stack_base = 0; DEMON_Entity *process = thread->parent; // id for main thread is zero B32 is_main_thread = (thread->id == process->id); pid_t match_tid = is_main_thread ? 0 : thread->id; // open /proc/$pid/maps int maps = demon_lnx_open_maps(process->id); // look for entry with stack markings and matching thread id for (;;){ DEMON_LNX_MapsEntry e; Temp temp = temp_begin(scratch.arena); if (!demon_lnx_next_map(temp.arena, maps, &e)){ break; } if (e.type == DEMON_LNX_MapsEntryType_Stack && e.stack_tid == match_tid){ stack_base = e.address_lo; break; } temp_end(temp); } scratch_end(scratch); return(stack_base); } internal U64 demon_os_tls_root_vaddr_from_thread(DEMON_Entity *thread){ U64 result = 0; switch (thread->arch){ case Arch_x64: case Arch_x86: { U32 fsbase = 0; pid_t tid = (pid_t)thread->id; if (ptrace(PT_GETFSBASE, tid, (void*)&fsbase, 0) != -1){ result = (U64)fsbase; } if (thread->arch == Arch_x64){ result += 8; } else{ result += 4; } }break; } return(result); } //- rjf: target process memory allocation/protection internal U64 demon_os_reserve_memory(DEMON_Entity *process, U64 size){ U64 result = 0; NotImplemented; return(result); } internal void demon_os_set_memory_protect_flags(DEMON_Entity *process, U64 page_vaddr, U64 size, DEMON_MemoryProtectFlags flags){ NotImplemented; } internal void demon_os_release_memory(DEMON_Entity *process, U64 vaddr, U64 size){ NotImplemented; } //- rjf: target process memory reading/writing internal U64 demon_os_read_memory(DEMON_Entity *process, void *dst, U64 src_address, U64 size){ int memory_fd = (int)process->ext_u64; U64 result = demon_lnx_read_memory(memory_fd, dst, src_address, size); return(result); } internal B32 demon_os_write_memory(DEMON_Entity *process, U64 dst_address, void *src, U64 size){ int memory_fd = (int)process->ext_u64; B32 result = demon_lnx_write_memory(memory_fd, dst_address, src, size); return(result); } //- rjf: thread registers reading/writing internal B32 demon_os_read_regs_x86(DEMON_Entity *thread, SYMS_RegX86 *dst){ B32 result = false; NotImplemented; return(result); } internal B32 demon_os_write_regs_x86(DEMON_Entity *thread, SYMS_RegX86 *src){ B32 result = false; NotImplemented; return(result); } internal B32 demon_os_read_regs_x64(DEMON_Entity *thread, SYMS_RegX64 *dst){ pid_t tid = (pid_t)thread->id; // gpr B32 got_gpr = false; DEMON_LNX_UserX64 ctx = {0}; struct iovec iov_gpr = {0}; iov_gpr.iov_len = sizeof(ctx); iov_gpr.iov_base = &ctx; if (ptrace(PTRACE_GETREGSET, tid, (void*)NT_PRSTATUS, &iov_gpr) != -1){ demon_lnx_regs_x64_from_usr_regs_x64(dst, &ctx.regs); got_gpr = true; } // fpr B32 got_fpr = false; if (got_gpr){ B32 got_xsave = false; { U8 xsave_buffer[KB(4)]; struct iovec iov_xsave = {0}; iov_xsave.iov_len = sizeof(xsave_buffer); iov_xsave.iov_base = xsave_buffer; if (ptrace(PTRACE_GETREGSET, tid, (void*)NT_X86_XSTATE, &iov_xsave) != -1){ SYMS_XSave *xsave = (SYMS_XSave*)xsave_buffer; syms_x64_regs__set_full_regs_from_xsave_legacy(dst, &xsave->legacy); // TODO(allen): this is a lie; ymm can technically move around // we need some more low-level-assembly-fu to do this hardcore. B32 has_ymm_registers = ((xsave->header.xstate_bv & 4) != 0); if (has_ymm_registers){ syms_x64_regs__set_full_regs_from_xsave_avx_extension(dst, xsave->ymmh); } got_xsave = true; } } B32 got_fxsave = false; if (!got_xsave){ SYMS_XSaveLegacy fxsave = {0}; struct iovec iov_fxsave = {0}; iov_fxsave.iov_len = sizeof(fxsave); iov_fxsave.iov_base = &fxsave; if (ptrace(PTRACE_GETREGSET, tid, (void*)NT_FPREGSET, &iov_fxsave) != -1){ syms_x64_regs__set_full_regs_from_xsave_legacy(dst, &fxsave); got_fxsave = true; } } if (got_xsave || got_fxsave){ got_fpr = true; } } // debug B32 got_debug = false; if (got_fpr){ got_debug = true; SYMS_Reg32 *dr_d = &dst->dr0; for (U32 i = 0; i < 8; i += 1, dr_d += 1){ if (i != 4 && i != 5){ U64 offset = OffsetOf(DEMON_LNX_UserX64, u_debugreg[i]); errno = 0; int peek_result = ptrace(PTRACE_PEEKUSER, tid, PtrFromInt(offset), 0); if (errno == 0){ dr_d->u32 = (U32)peek_result; } else{ got_debug = false; } } } } // got everything B32 result = got_debug; return(result); } internal B32 demon_os_write_regs_x64(DEMON_Entity *thread, SYMS_RegX64 *src){ pid_t tid = (pid_t)thread->id; // gpr DEMON_LNX_UserX64 ctx = {0}; demon_lnx_usr_regs_x64_from_regs_x64(&ctx.regs, src); struct iovec iov_gpr = {0}; iov_gpr.iov_base = &ctx; iov_gpr.iov_len = sizeof(ctx); int gpr_result = ptrace(PTRACE_SETREGSET, tid, (void*)NT_PRSTATUS, &iov_gpr); B32 gpr_success = (gpr_result != -1); // fpr int xsave_result = 0; int fxsave_result = 0; { U8 xsave_buffer[KB(4)] = {0}; SYMS_XSave *xsave = (SYMS_XSave*)xsave_buffer; syms_x64_regs__set_xsave_legacy_from_full_regs(&xsave->legacy, src); xsave->header.xstate_bv = 7; // TODO(allen): this is a lie; ymm can technically move around // we need some more low-level-assembly-fu to do this hardcore. syms_x64_regs__set_xsave_avx_extension_from_full_regs(xsave->ymmh, src); { struct iovec iov_xsave = {0}; iov_xsave.iov_base = &xsave; iov_xsave.iov_len = sizeof(xsave); xsave_result = ptrace(PTRACE_SETREGSET, tid, (void*)NT_X86_XSTATE, &iov_xsave); } if (xsave_result == -1){ struct iovec iov_fxsave = {0}; iov_fxsave.iov_base = &xsave->legacy; iov_fxsave.iov_len = sizeof(xsave->legacy); fxsave_result = ptrace(PTRACE_SETREGSET, tid, (void*)NT_FPREGSET, &iov_fxsave); } } B32 fpr_success = (xsave_result != -1 || fxsave_result != -1); // debug B32 dr_success = true; { SYMS_Reg32 *dr_s = &src->dr0; for (U32 i = 0; i < 8; i += 1, dr_s += 1){ if (i != 4 && i != 5){ U64 offset = OffsetOf(DEMON_LNX_UserX64, u_debugreg[i]); errno = 0; int poke_result = ptrace(PTRACE_POKEUSER, tid, PtrFromInt(offset), dr_s->u32); if (poke_result == -1){ dr_success = false; } } } } // assemble result B32 result = (gpr_success && fpr_success && dr_success); return(result); } //////////////////////////////// //~ rjf: @demon_os_hooks Process Listing internal void demon_os_proc_iter_begin(DEMON_ProcessIter *iter){ DIR *dir = opendir("/proc"); MemoryZeroStruct(iter); iter->v[0] = IntFromPtr(dir); } internal B32 demon_os_proc_iter_next(Arena *arena, DEMON_ProcessIter *iter, DEMON_ProcessInfo *info_out){ // scan for a process id B32 got_pid = false; String8 pid_string = {0}; DIR *dir = (DIR*)PtrFromInt(iter->v[0]); if (dir != 0 && iter->v[1] == 0){ for (;;){ struct dirent *d = readdir(dir); if (d == 0){ break; } // check file name is integer String8 file_name = str8_cstring((char*)d->d_name); B32 is_integer = str8_is_integer(file_name, 10); // break on integers (which represent processes) if (is_integer){ got_pid = true; pid_string = file_name; break; } } } // mark iterator dead if nothing found if (!got_pid){ iter->v[1] = 1; } // if got process id convert pid -> process info B32 result = false; if (got_pid){ // determine the name we will report pid_t pid = u64_from_str8(pid_string, 10); String8 name = demon_lnx_executable_path_from_pid(arena, pid); if (name.size == 0){ name = str8_lit(""); } // finish conversion info_out->name = name; info_out->pid = pid; result = true; } return(result); } internal void demon_os_proc_iter_end(DEMON_ProcessIter *iter){ DIR *dir = (DIR*)PtrFromInt(iter->v[0]); if (dir != 0){ closedir(dir); } MemoryZeroStruct(iter); }