From 09f74805e0e59cb302af3dcca6a842bb60fb3a09 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Thu, 28 Mar 2024 14:54:52 -0700 Subject: [PATCH] restore old demon layer linux implementation, to not have to seek back in version control to find it when porting --- src/demon/linux/demon_os_linux.c | 2106 ++++++++++++++++++++++++++++++ src/demon/linux/demon_os_linux.h | 222 ++++ 2 files changed, 2328 insertions(+) create mode 100644 src/demon/linux/demon_os_linux.c create mode 100644 src/demon/linux/demon_os_linux.h diff --git a/src/demon/linux/demon_os_linux.c b/src/demon/linux/demon_os_linux.c new file mode 100644 index 00000000..0c92f461 --- /dev/null +++ b/src/demon/linux/demon_os_linux.c @@ -0,0 +1,2106 @@ +// Copyright (c) 2024 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_put_back(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 Architecture +demon_lnx_arch_from_pid(pid_t pid){ + Temp scratch = scratch_begin(0, 0); + Architecture result = Architecture_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 = Architecture_x86; + }break; + + case SYMS_ElfMachineKind_ARM: + { + result = Architecture_arm32; + }break; + + case SYMS_ElfMachineKind_X86_64: + { + result = Architecture_x64; + }break; + + case SYMS_ElfMachineKind_AARCH64: + { + result = Architecture_arm64; + }break; + } + + scratch_end(scratch); + return(result); +} + +internal DEMON_LNX_ProcessAux +demon_lnx_aux_from_pid(pid_t pid, Architecture arch){ + DEMON_LNX_ProcessAux result = {0}; + B32 addr_32bit = (arch == Architecture_x86 || arch == Architecture_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){ + Architecture arch = (Architecture)process->arch; + B32 is_32bit = (arch == Architecture_x86 || arch == Architecture_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-Architecture implementation of single steps + // set single step bit + if (single_step_thread != 0){ + switch (single_step_thread->arch){ + case Architecture_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 Architecture_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-Architecture 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 Architecture_x86: + { + demon_os_read_regs_x86(thread, ®s.x86); + instruction_pointer = regs.x86.eip.u32; + }break; + + case Architecture_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{ + Architecture 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 Architecture_x86: + { + instruction_pointer -= 1; + regs.x86.eip.u32 = instruction_pointer; + demon_os_write_regs_x86(thread, ®s.x86); + }break; + + case Architecture_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-Architecture + // 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-Architecture + // 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 Architecture_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 Architecture_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; + + Architecture 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){ + Architecture 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 Architecture_x64: + case Architecture_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 == Architecture_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); +} diff --git a/src/demon/linux/demon_os_linux.h b/src/demon/linux/demon_os_linux.h new file mode 100644 index 00000000..4e039e35 --- /dev/null +++ b/src/demon/linux/demon_os_linux.h @@ -0,0 +1,222 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef DEMON_OS_LINUX_H +#define DEMON_OS_LINUX_H + +// TODO(allen): Potential Upgrades: +// +// memory fd upgrade - Right now for each process we hold open a file +// descriptor for the process's memory (/proc/%d/mem) for the entire lifetime +// of the process; it could be opened and closed with some kind of LRU cache +// to put a finite cap on the number of handles the demon holds +// + +//////////////////////////////// +//~ NOTE(allen): Get The Linux Includes + +#include +#include +#include +#include +#include +#include +#include + +//////////////////////////////// +//~ NOTE(allen): Linux Demon Types + +//- entities + +// Demon Linux Entity Extensions +// Process: ext_u64 set to memory file descriptor +// Thread : ext_u64 cast to DEMON_LNX_ThreadExt +// Module : ext_u64 set to U64 (address of name) + +struct DEMON_LNX_ThreadExt{ + B32 expecting_dummy_sigstop; +}; +StaticAssert(sizeof(DEMON_LNX_ThreadExt) <= sizeof(Member(DEMON_Entity, ext_u64)), check_demon_lnx_thread_ext); + +//- helpers + +struct DEMON_LNX_AttachNode{ + DEMON_LNX_AttachNode *next; + pid_t pid; +}; + +struct DEMON_LNX_ProcessAux{ + B32 filled; + U64 phnum; + U64 phent; + U64 phdr; + U64 execfn; +}; + +struct DEMON_LNX_PhdrInfo{ + Rng1U64 range; + U64 dynamic; +}; + +struct DEMON_LNX_ModuleNode{ + DEMON_LNX_ModuleNode *next; + U64 vaddr; + U64 size; + U64 name; + U64 already_known; +}; + +struct DEMON_LNX_EntityNode{ + DEMON_LNX_EntityNode *next; + DEMON_Entity *entity; +}; + +//////////////////////////////// +//~ NOTE(allen): Linux Demon Register Layouts + +// these are defined in but only for one architecture at a time +// (and we can't really trick it into giving us both in any obvious way) +// we define them here so that we have them all "at once" + +struct DEMON_LNX_UserRegsX64{ + U64 r15; + U64 r14; + U64 r13; + U64 r12; + U64 rbp; + U64 rbx; + U64 r11; + U64 r10; + U64 r9; + U64 r8; + U64 rax; + U64 rcx; + U64 rdx; + U64 rsi; + U64 rdi; + U64 orig_rax; + U64 rip; + U64 cs; + U64 rflags; + U64 rsp; + U64 ss; + U64 fsbase; + U64 gsbase; + U64 ds; + U64 es; + U64 fs; + U64 gs; +}; + +struct DEMON_LNX_UserX64{ + DEMON_LNX_UserRegsX64 regs; + S32 u_fpvalid, _pad0; + SYMS_XSaveLegacy i387; + U64 u_tsize, u_dsize, u_ssize, start_code, start_stack; + U64 signal; + S32 reserved, _pad1; + U64 u_ar0, u_fpstate; + U64 magic; + U8 u_comm[32]; + U64 u_debugreg[8]; +}; + +struct DEMON_LNX_UserRegsX86{ + U32 ebx; + U32 ecx; + U32 edx; + U32 esi; + U32 edi; + U32 ebp; + U32 eax; + U32 ds; + U32 es; + U32 fs; + U32 gs; + U32 orig_eax; + U32 eip; + U32 cs; + U32 eflags; + U32 sp; + U32 ss; +}; + +struct DEMON_LNX_UserX86{ + DEMON_LNX_UserRegsX86 regs; + S32 u_fpvalid; + SYMS_FSave i387; + U32 u_tsize, u_dsize, u_ssize, start_code, start_stack; + S32 signal, reserved; + U32 u_ar0, u_fpstate; + U32 magic; + U8 u_comm[32]; + U32 u_debugreg[8]; +}; + +//////////////////////////////// + +enum +{ + DEMON_LNX_PermFlags_Read = (1 << 0), + DEMON_LNX_PermFlags_Write = (1 << 1), + DEMON_LNX_PermFlags_Exec = (1 << 2), + DEMON_LNX_PermFlags_Private = (1 << 3) +}; +typedef int DEMON_LNX_PermFlags; + +enum +{ + DEMON_LNX_MapsEntryType_Null, + DEMON_LNX_MapsEntryType_Path, + DEMON_LNX_MapsEntryType_Heap, + DEMON_LNX_MapsEntryType_Stack, + DEMON_LNX_MapsEntryType_VDSO, +}; +typedef int DEMON_LNX_MapsEntryType; + +struct DEMON_LNX_MapsEntry +{ + U64 address_lo; + U64 address_hi; + DEMON_LNX_PermFlags perms; + U64 offset; + U32 dev_major; + U32 dev_minor; + U64 inode; + String8 pathname; + DEMON_LNX_MapsEntryType type; + pid_t stack_tid; +}; + +//////////////////////////////// +//~ rjf: Helpers + +internal DEMON_LNX_ThreadExt* demon_lnx_thread_ext(DEMON_Entity *entity); + +internal B32 demon_lnx_attach_pid(Arena *arena, pid_t pid, DEMON_LNX_AttachNode **new_node); + +internal String8 demon_lnx_executable_path_from_pid(Arena *arena, pid_t pid); +internal int demon_lnx_open_memory_fd_for_pid(pid_t pid); + +internal Architecture demon_lnx_arch_from_pid(pid_t pid); +internal DEMON_LNX_ProcessAux demon_lnx_aux_from_pid(pid_t pid, Architecture arch); +internal DEMON_LNX_PhdrInfo demon_lnx_phdr_info_from_memory(int memory_fd, B32 is_32bit, + U64 phvaddr, U64 phstride, U64 phcount); +internal DEMON_LNX_ModuleNode* demon_lnx_module_list_from_process(Arena *arena, DEMON_Entity *process); + +internal U64 demon_lnx_read_memory(int memory_fd, void *dst, U64 src, U64 size); +internal B32 demon_lnx_write_memory(int memory_fd, U64 dst, void *src, U64 size); +internal String8 demon_lnx_read_memory_str(Arena *arena, int memory_fd, U64 address); + +internal void demon_lnx_regs_x64_from_usr_regs_x64(SYMS_RegX64 *dst, DEMON_LNX_UserRegsX64 *src); +internal void demon_lnx_usr_regs_x64_from_regs_x64(DEMON_LNX_UserRegsX64 *dst, SYMS_RegX64 *src); + +internal String8 demon_lnx_read_int_string(int fd); +internal B32 demon_lnx_read_expect(int fd, char expect); +internal int demon_lnx_read_whitespace(int fd); +internal String8 demon_lnx_read_string(Arena *arena, int fd); + +internal int demon_lnx_open_maps(pid_t pid); +internal B32 demon_lnx_next_map(Arena *arena, int maps, DEMON_LNX_MapsEntry *entry_out); + +#endif //DEMON_OS_LINUX_H