Files
raddebugger/src/demon/linux/demon_os_linux.c
T
2025-06-02 14:13:57 -07:00

2107 lines
65 KiB
C

// 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, &regs);
regs.eflags.u32 |= 0x100;
demon_os_write_regs_x86(single_step_thread, &regs);
}break;
case Arch_x64:
{
// TODO(allen): possibly buggy
SYMS_RegX64 regs = {0};
demon_os_read_regs_x64(single_step_thread, &regs);
regs.rflags.u64 |= 0x100;
demon_os_write_regs_x64(single_step_thread, &regs);
}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/<tid>/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, &regs.x86);
instruction_pointer = regs.x86.eip.u32;
}break;
case Arch_x64:
{
demon_os_read_regs_x64(thread, &regs.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, &regs.x86);
}break;
case Arch_x64:
{
instruction_pointer -= 1;
regs.x64.rip.u64 = instruction_pointer;
demon_os_write_regs_x64(thread, &regs.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, &regs);
regs.eflags.u32 &= ~0x100;
demon_os_write_regs_x86(single_step_thread, &regs);
}break;
case Arch_x64:
{
SYMS_RegX64 regs = {0};
demon_os_read_regs_x64(single_step_thread, &regs);
regs.rflags.u64 &= ~0x100;
demon_os_write_regs_x64(single_step_thread, &regs);
}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("<name-not-resolved>");
}
// 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);
}