Files
raddebugger/src/demon/linux/demon_core_linux.c
T

2837 lines
91 KiB
C

// Copyright (c) Epic Games Tools
// Licensed under the MIT license (https://opensource.org/license/mit/)
////////////////////////////////
//~ rjf: Helpers
internal DMN_LNX_EntityNode *
dmn_lnx_entity_list_push(Arena *arena, DMN_LNX_EntityList *list, DMN_LNX_Entity *v)
{
DMN_LNX_EntityNode *n = push_array(arena, DMN_LNX_EntityNode, 1);
n->v = v;
SLLQueuePush(list->first, list->last, n);
list->count += 1;
return n;
}
//- rjf: file descriptor memory reading/writing helpers
internal U64
dmn_lnx_size_from_fd(int memory_fd, U64 cap)
{
U8 temp[4096];
size_t cursor = 0;
while(cursor < cap)
{
ssize_t actual_read = pread(memory_fd, temp, sizeof(temp), cursor);
if(actual_read < 0)
{
if(errno == EINTR) { continue; }
break;
}
if(actual_read == 0) { break; }
cursor += (U64)actual_read;
}
return (U64)cursor;
}
internal U64
dmn_lnx_read(int memory_fd, Rng1U64 range, void *dst)
{
size_t cursor = 0, size = dim_1u64(range);
while(cursor < size)
{
size_t to_read = size - cursor;
ssize_t actual_read = pread(memory_fd, (U8 *)dst + cursor, to_read, range.min + cursor);
if(actual_read < 0)
{
if(errno == EINTR) { continue; }
break;
}
if(actual_read == 0) { break; }
cursor += actual_read;
}
return (U64)cursor;
}
internal B32
dmn_lnx_write(int memory_fd, Rng1U64 range, void *src)
{
B32 result = 1;
U8 *ptr = (U8 *)src;
U8 *opl = ptr + dim_1u64(range);
U64 cursor = range.min;
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 = 0;
break;
}
ptr += actual_write;
cursor += actual_write;
}
return result;
}
internal String8
dmn_lnx_read_string_capped(Arena *arena, int memory_fd, U64 base_vaddr, U64 cap_size)
{
String8 result = {0};
U64 string_size = 0;
for(U64 vaddr = base_vaddr; string_size < cap_size; vaddr += 1, string_size += 1)
{
char byte = 0;
if(pread(memory_fd, &byte, sizeof(byte), vaddr) == 0)
{
break;
}
if(byte == '\0' || byte == '\n')
{
break;
}
}
if(string_size != 0)
{
char *buf = push_array_no_zero(arena, char, string_size+1);
pread(memory_fd, buf, string_size, base_vaddr);
buf[string_size] = '\0';
result = str8((U8 *)buf, string_size);
}
return result;
}
internal String8
dmn_lnx_read_string(Arena *arena, int memory_fd, U64 vaddr)
{
return dmn_lnx_read_string_capped(arena, memory_fd, vaddr, 4096);
}
////////////////////////////////
//~ Runtime Struct Helpers
internal B32
dmn_lnx_read_ehdr(int memory_fd, U64 addr, ELF_Hdr64 *ehdr_out)
{
B32 is_read = 0;
U8 e_ident[ELF_Identifier_Max] = {0};
U64 e_ident_size = dmn_lnx_read(memory_fd, r1u64(addr, addr + sizeof(e_ident)), &e_ident);
if(e_ident_size == sizeof(e_ident))
{
if(str8_match(str8_prefix(str8_array_fixed(e_ident), elf_magic_string.size), elf_magic_string, 0))
{
switch(e_ident[ELF_Identifier_Class])
{
default:{InvalidPath;}break;
case ELF_Class_None: {}break;
case ELF_Class_32:
{
ELF_Hdr32 ehdr32 = {0};
if(dmn_lnx_read_struct(memory_fd, addr, &ehdr32))
{
*ehdr_out = elf_hdr64_from_hdr32(ehdr32);
is_read = 1;
}
}break;
case ELF_Class_64:
{
is_read = dmn_lnx_read_struct(memory_fd, addr, ehdr_out);
}break;
}
}
}
return is_read;
}
internal B32
dmn_lnx_read_phdr(int memory_fd, U64 addr, ELF_Class elf_class, ELF_Phdr64 *phdr_out)
{
B32 is_read = 0;
switch (elf_class)
{
case ELF_Class_None: break;
case ELF_Class_32:
{
ELF_Phdr32 phdr32 = {0};
if(dmn_lnx_read_struct(memory_fd, addr, &phdr32))
{
*phdr_out = elf_phdr64_from_phdr32(phdr32);
is_read = 1;
}
}break;
case ELF_Class_64:
{
is_read = dmn_lnx_read_struct(memory_fd, addr, phdr_out);
}break;
default:{NotImplemented;}break;
}
return is_read;
}
internal B32
dmn_lnx_read_shdr(int memory_fd, U64 addr, ELF_Class elf_class, ELF_Shdr64 *shdr_out)
{
B32 is_read = 0;
switch (elf_class)
{
case ELF_Class_None: break;
case ELF_Class_32:
{
ELF_Shdr32 shdr32 = {0};
if(dmn_lnx_read_struct(memory_fd, addr, &shdr32))
{
*shdr_out = elf_shdr64_from_shdr32(shdr32);
is_read = 1;
}
}break;
case ELF_Class_64:
{
is_read = dmn_lnx_read_struct(memory_fd, addr, shdr_out);
}break;
default:{NotImplemented;}break;
}
return is_read;
}
internal B32
dmn_lnx_read_linkmap(int memory_fd, U64 addr, ELF_Class elf_class, GNU_LinkMap64 *linkmap_out)
{
B32 is_read = 0;
switch(elf_class)
{
case ELF_Class_None: {}break;
case ELF_Class_32:
{
// TODO(rjf): endianness
GNU_LinkMap32 linkmap32 = {0};
if(dmn_lnx_read_struct(memory_fd, addr, &linkmap32))
{
*linkmap_out = gnu_linkmap64_from_linkmap32(linkmap32);
is_read = 1;
}
}break;
case ELF_Class_64:
{
is_read = dmn_lnx_read_struct(memory_fd, addr, linkmap_out);
}break;
default:{NotImplemented;}break;
}
return is_read;
}
internal B32
dmn_lnx_read_dynamic(int memory_fd, U64 addr, ELF_Class elf_class, ELF_Dyn64 *dyn_out)
{
B32 is_read = 0;
switch(elf_class)
{
case ELF_Class_None:{}break;
case ELF_Class_32:
{
ELF_Dyn32 dyn32 = {0};
if(dmn_lnx_read_struct(memory_fd, addr, &dyn32))
{
*dyn_out = elf_dyn64_from_dyn32(dyn32);
is_read = 1;
}
}break;
case ELF_Class_64:
{
is_read = dmn_lnx_read_struct(memory_fd, addr, dyn_out);
}break;
default:{NotImplemented;}break;
}
return is_read;
}
internal B32
dmn_lnx_read_symbol(int memory_fd, U64 addr, ELF_Class elf_class, ELF_Sym64 *symbol_out)
{
B32 is_read = 0;
switch(elf_class)
{
case ELF_Class_None:{}break;
case ELF_Class_32:
{
ELF_Sym32 symbol32 = {0};
if(dmn_lnx_read_struct(memory_fd, addr, &symbol32))
{
*symbol_out = elf_sym64_from_sym32(symbol32);
is_read = 1;
}
}break;
case ELF_Class_64:
{
is_read = dmn_lnx_read_struct(memory_fd, addr, symbol_out);
}break;
default:{NotImplemented;}break;
}
return is_read;
}
internal B32
dmn_lnx_read_r_debug(int memory_fd, U64 addr, Arch arch, GNU_RDebugInfo64 *rdebug_out)
{
B32 is_read = 0;
switch(gnu_rdebug_info_size_from_arch(arch))
{
case 0: {} break;
case sizeof(GNU_RDebugInfo32): {
GNU_RDebugInfo32 rdebug32 = {0};
if(dmn_lnx_read_struct(memory_fd, addr, &rdebug32))
{
*rdebug_out = gnu_rdebug_info64_from_rdebug_info32(rdebug32);
is_read = 1;
}
}break;
case sizeof(GNU_RDebugInfo64):
{
is_read = dmn_lnx_read_struct(memory_fd, addr, rdebug_out);
}break;
default:{InvalidPath;}break;
}
Assert(is_read);
return is_read;
}
//- rjf: pid => info extraction
internal String8
dmn_lnx_exe_path_from_pid(Arena *arena, pid_t pid)
{
Temp scratch = scratch_begin(&arena, 1);
String8 exe_link_path = str8f(scratch.arena, "/proc/%d/exe", pid);
String8List parts = {0};
int readlink_result = 0;
for(S64 r = 0, cap = PATH_MAX; r < 4; cap *= 2, r += 1)
{
U8 *buffer = push_array(arena, U8, cap);
readlink_result = readlink((char *)exe_link_path.str, (char *)buffer, cap);
if(readlink_result < 0)
{
break;
}
str8_list_push(scratch.arena, &parts, str8(buffer, readlink_result));
if(readlink_result < cap)
{
break;
}
}
String8 result = str8_list_join(arena, &parts, 0);
scratch_end(scratch);
return result;
}
internal ELF_Hdr64
dmn_lnx_ehdr_from_pid(pid_t pid)
{
Temp scratch = scratch_begin(0, 0);
B32 is_read = 0;
ELF_Hdr64 exe = {0};
String8 exe_path = dmn_lnx_exe_path_from_pid(scratch.arena, pid);
if(exe_path.size != 0)
{
int exe_fd = open((char *)exe_path.str, O_RDONLY|O_CLOEXEC);
if(exe_fd != -1)
{
is_read = dmn_lnx_read_ehdr(exe_fd, 0, &exe);
close(exe_fd);
}
}
Assert(is_read);
scratch_end(scratch);
return exe;
}
internal DMN_LNX_ProcessAuxv
dmn_lnx_auxv_from_pid(pid_t pid, ELF_Class elf_class)
{
Temp scratch = scratch_begin(0, 0);
DMN_LNX_ProcessAuxv result = {0};
// rjf: open aux data
String8 auxv_path = push_str8f(scratch.arena, "/proc/%d/auxv", pid);
int auxv_fd = open((char*)auxv_path.str, O_RDONLY|O_CLOEXEC);
// rjf: scan aux data
if(auxv_fd >= 0)
{
for(;;)
{
// rjf: read next aux
ELF_Auxv64 auxv = {0};
switch(elf_class)
{
case ELF_Class_None:{}break;
case ELF_Class_32:
{
ELF_Auxv32 auxv32 = {0};
if(read(auxv_fd, &auxv32, sizeof(auxv32)) != sizeof(auxv32))
{
goto brkloop;
}
auxv = elf_auxv64_from_auxv32(auxv32);
}break;
case ELF_Class_64:
{
if(read(auxv_fd, &auxv, sizeof(auxv)) != sizeof(auxv))
{
goto brkloop;
}
}break;
default:{NotImplemented;}break;
}
// rjf: fill result
switch(auxv.a_type)
{
default:{}break;
case ELF_AuxType_Null: goto brkloop; break;
case ELF_AuxType_Base: result.base = auxv.a_val; break;
case ELF_AuxType_Phnum: result.phnum = auxv.a_val; break;
case ELF_AuxType_Phent: result.phent = auxv.a_val; break;
case ELF_AuxType_Phdr: result.phdr = auxv.a_val; break;
case ELF_AuxType_ExecFn: result.execfn = auxv.a_val; break;
case ELF_AuxType_Pagesz: result.pagesz = auxv.a_val; break;
}
}
brkloop:;
close(auxv_fd);
}
scratch_end(scratch);
return result;
}
internal DMN_LNX_PhdrInfo
dmn_lnx_phdr_info_from_memory(int memory_fd, ELF_Class elf_class, U64 rebase, U64 e_phaddr, U64 e_phentsize, U64 e_phnum)
{
DMN_LNX_PhdrInfo result = { .range.min = max_U64 };
// rjf: scan table
for(U64 ph_cursor = e_phaddr, ph_opl = (e_phaddr + e_phentsize * e_phnum);
ph_cursor < ph_opl;
ph_cursor += e_phentsize)
{
ELF_Phdr64 phdr = {0};
if(!dmn_lnx_read_phdr(memory_fd, ph_cursor, elf_class, &phdr))
{
Assert(0 && "unable to read a program header");
}
// rjf: save
switch(phdr.p_type)
{
default:{}break;
case ELF_PType_Dynamic:
{
result.dynamic = rebase + phdr.p_vaddr;
}break;
case ELF_PType_Load:
{
U64 min = rebase + phdr.p_vaddr;
U64 max = rebase + phdr.p_vaddr + phdr.p_memsz;
result.range.min = Min(result.range.min, min);
result.range.max = Max(result.range.max, max);
}break;
}
}
return result;
}
internal DMN_LNX_DynamicInfo
dmn_lnx_dynamic_info_from_memory(int memory_fd, ELF_Class elf_class, U64 rebase, U64 dynamic_vaddr)
{
DMN_LNX_DynamicInfo dynamic_info = {0};
for(U64 dynamic_cursor = dynamic_vaddr; ; dynamic_cursor += elf_dyn_size_from_class(elf_class))
{
// rjf: read next dyn entry
ELF_Dyn64 dyn = {0};
if(!dmn_lnx_read_dynamic(memory_fd, dynamic_cursor, elf_class, &dyn)) { Assert(0 && "unable to read dynamic"); }
// rjf: break on zero
if(dyn.tag == ELF_DynTag_Null) { break; }
// extract reuiqred values out of dynamic section
if(dyn.tag == ELF_DynTag_Strtab)
{
dynamic_info.strtab_vaddr = rebase + dyn.val;
}
else if(dyn.tag == ELF_DynTag_Strsz)
{
dynamic_info.strtab_size = dyn.val;
}
else if(dyn.tag == ELF_DynTag_Symtab)
{
dynamic_info.symtab_vaddr = rebase + dyn.val;
}
else if(dyn.tag == ELF_DynTag_Syment)
{
dynamic_info.symtab_entry_size = dyn.val;
}
else if(dyn.tag == ELF_DynTag_Hash)
{
dynamic_info.hash_vaddr = rebase + dyn.val;
}
else if(dyn.tag == ELF_DynTag_GNU_Hash)
{
dynamic_info.gnu_hash_vaddr = rebase + dyn.val;
}
}
return dynamic_info;
}
internal U64
dmn_lnx_rdebug_vaddr_from_memory(int memory_fd, U64 loader_vbase)
{
Temp scratch = scratch_begin(0, 0);
U64 rdebug_vaddr = 0;
// load DL's header
ELF_Hdr64 ehdr = {0};
if(!dmn_lnx_read_ehdr(memory_fd, loader_vbase, &ehdr)) { Assert(0 && "failed to read interp's header"); goto exit; }
U64 rebase = ehdr.e_type == ELF_Type_Dyn ? loader_vbase : 0;
ELF_Class elf_class = ehdr.e_ident[ELF_Identifier_Class];
// find dynamic program header
U64 dynamic_vaddr = max_U64;
for EachIndex(phdr_idx, ehdr.e_phnum)
{
U64 phdr_vaddr = loader_vbase + ehdr.e_phoff + phdr_idx * ehdr.e_phentsize;
ELF_Phdr64 phdr = {0};
if(!dmn_lnx_read_phdr(memory_fd, phdr_vaddr, elf_class, &phdr)) { Assert(0 && "failed to read program header"); goto exit; }
if(phdr.p_type == ELF_PType_Dynamic)
{
dynamic_vaddr = rebase + phdr.p_offset;
break;
}
}
// extract necessary info out of dynamic program header
DMN_LNX_DynamicInfo dynamic_info = dmn_lnx_dynamic_info_from_memory(memory_fd, elf_class, rebase, dynamic_vaddr);
// extract symbol table count from available options
U64 symbol_count = 0;
if(dynamic_info.hash_vaddr)
{
U64 hash_entry_size = 4;
if(elf_class == ELF_Class_64 && (ehdr.e_machine == ELF_MachineKind_ALPHA || ehdr.e_machine == ELF_MachineKind_S390 || ehdr.e_machine == ELF_MachineKind_S390_OLD))
{
hash_entry_size = 8;
}
U64 chain_count = 0;
if(dmn_lnx_read(memory_fd, r1u64(dynamic_info.hash_vaddr, dynamic_info.hash_vaddr + hash_entry_size), &chain_count) == hash_entry_size)
{
symbol_count = chain_count;
}
else
{
Assert(0 && "failed to read hash table's chain count out of HASH");
}
}
else
{
// TODO: extract count from GNU_HASH
NotImplemented;
}
// scan symbol table for the rendezvous symbol
if(dynamic_info.symtab_vaddr && dynamic_info.symtab_entry_size && symbol_count)
{
for EachIndex(symbol_idx, symbol_count)
{
ELF_Sym64 symbol = {0};
if(!dmn_lnx_read_symbol(memory_fd, dynamic_info.symtab_vaddr + symbol_idx * dynamic_info.symtab_entry_size, elf_class, &symbol))
{
Assert(0 && "failed to read symbol table");
break;
}
Temp temp = temp_begin(scratch.arena);
String8 symbol_name = {0};
if(symbol.st_name < dynamic_info.strtab_size)
{
U64 cap = dynamic_info.strtab_size - symbol.st_name;
symbol_name = dmn_lnx_read_string_capped(temp.arena, memory_fd, dynamic_info.strtab_vaddr + symbol.st_name, cap);
}
if(str8_match(symbol_name, str8_lit("_r_debug"), 0))
{
ELF_SymType symbol_type = ELF_ST_TYPE(symbol.st_info);
if(symbol_type == ELF_SymType_Object && symbol.st_size > 0)
{
rdebug_vaddr = rebase + symbol.st_value;
break;
}
}
temp_end(temp);
}
}
exit:;
scratch_end(scratch);
return rdebug_vaddr;
}
////////////////////////////////
//~ SDT Probes
internal DMN_LNX_ProbeList
dmn_lnx_read_probes(Arena *arena, int fd, U64 offset, U64 image_base)
{
Temp scratch = scratch_begin(&arena, 1);
DMN_LNX_ProbeList probes = {0};
ELF_Hdr64 ehdr = {0};
if(!dmn_lnx_read_ehdr(fd, offset, &ehdr)) { goto exit; }
U64 strtab_shdr_offset = offset + ehdr.e_shoff + ehdr.e_shstrndx * ehdr.e_shentsize;
ELF_Shdr64 strtab_shdr = {0};
if(!dmn_lnx_read_shdr(fd, strtab_shdr_offset, ehdr.e_ident[ELF_Identifier_Class], &strtab_shdr)) { goto exit; }
B32 found_probes = 0;
B32 found_probes_base = 0;
ELF_Shdr64 text_shdr = {0};
ELF_Shdr64 stapsdt_base_shdr = {0};
ELF_Shdr64 stapsdt_shdr = {0};
for(U64 shdr_off = offset + ehdr.e_shoff, shdr_opl = shdr_off + ehdr.e_shentsize * ehdr.e_shnum;
shdr_off < shdr_opl;
shdr_off += ehdr.e_shentsize) {
ELF_Shdr64 shdr = {0};
if(!dmn_lnx_read_shdr(fd, shdr_off, ehdr.e_ident[ELF_Identifier_Class], &shdr)) { goto exit; }
if(shdr.sh_type == ELF_ShType_Note)
{
U64 name_offset = offset + strtab_shdr.sh_offset + shdr.sh_name;
U64 name_cap = offset + strtab_shdr.sh_offset + strtab_shdr.sh_size;
String8 name = dmn_lnx_read_string_capped(scratch.arena, fd, name_offset, name_cap);
if(str8_match(name, str8_lit(".note.stapsdt"), 0))
{
stapsdt_shdr = shdr;
found_probes = 1;
}
}
else if(shdr.sh_type == ELF_ShType_ProgBits)
{
U64 name_offset = offset + strtab_shdr.sh_offset + shdr.sh_name;
U64 name_cap = offset + strtab_shdr.sh_offset + strtab_shdr.sh_size;
String8 name = dmn_lnx_read_string_capped(scratch.arena, fd, name_offset, name_cap);
if(str8_match(name, str8_lit(".stapsdt.base"), 0))
{
stapsdt_base_shdr = shdr;
found_probes_base = 1;
} else if(str8_match(name, str8_lit(".text"), 0))
{
text_shdr = shdr;
}
}
if(found_probes && found_probes_base) { break; }
}
if(!found_probes || !found_probes_base) { goto exit; }
U64 probes_base = stapsdt_base_shdr.sh_addr;
Rng1U64 note_range = shift_1u64(r1u64(stapsdt_shdr.sh_offset, stapsdt_shdr.sh_offset + stapsdt_shdr.sh_size), offset);
void *raw_note = push_array(arena, U8, stapsdt_shdr.sh_size);
U64 note_read_size = dmn_lnx_read(fd, note_range, raw_note);
if(note_read_size != dim_1u64(note_range)) { goto exit; }
Arch arch = arch_from_elf_machine(ehdr.e_machine);
ELF_NoteList note = elf_parse_note(scratch.arena, str8(raw_note, dim_1u64(note_range)), ehdr.e_ident[ELF_Identifier_Class], ehdr.e_machine);
for EachNode(n, ELF_NoteNode, note.first)
{
ELF_Note *note = &n->v;
if(!str8_match(note->owner, str8_lit("stapsdt"), 0)) { continue; }
if(note->type != ELF_NoteType_STapSdt) { continue; }
DMN_LNX_Probe probe = {0};
{
U64 cursor = 0;
U64 addr_size = ehdr.e_ident[ELF_Identifier_Class] == ELF_Class_64 ? 8 : 4;
U64 pc = 0;
U64 pc_size = str8_deserial_read(note->desc, cursor, &pc, addr_size, addr_size);
if (pc_size == 0) { goto exit; }
cursor += pc_size;
U64 base_addr = 0;
U64 base_addr_size = str8_deserial_read(note->desc, cursor, &base_addr, addr_size, addr_size);
if (base_addr_size == 0) { goto exit; }
cursor += base_addr_size;
U64 semaphore = 0;
U64 semaphore_size = str8_deserial_read(note->desc, cursor, &semaphore, addr_size, addr_size);
if (semaphore_size == 0) { goto exit; }
cursor += semaphore_size;
String8 provider = str8_cstring_capped(note->desc.str + cursor, note->desc.str + note->desc.size);
cursor += provider.size + 1;
if (cursor > note->desc.size) { goto exit; }
String8 name = str8_cstring_capped(note->desc.str + cursor, note->desc.str + note->desc.size);
cursor += name.size + 1;
if (cursor > note->desc.size) { goto exit; }
String8 args = str8_cstring_capped(note->desc.str + cursor, note->desc.str + note->desc.size);
cursor += args.size + 1;
if (cursor > note->desc.size) { goto exit; }
U64 probe_rebase = image_base + (base_addr - probes_base);
probe.provider = provider;
probe.name = name;
probe.args = stap_arg_array_from_string(arena, arch, args);
probe.pc = pc + probe_rebase;
probe.semaphore = semaphore ? semaphore + probe_rebase : 0;
}
DMN_LNX_ProbeNode *n = push_array(arena, DMN_LNX_ProbeNode, 1);
n->v = probe;
SLLQueuePush(probes.first, probes.last, n);
probes.count += 1;
}
exit:;
scratch_end(scratch);
return probes;
}
////////////////////////////////
//~ STAP
internal
STAP_MEMORY_READ(dmn_lnx_stap_memory_read)
{
DMN_LNX_Entity *process = raw_ctx;
U64 bytes_read = dmn_lnx_read(process->fd, r1u64(addr, addr + read_size), buffer);
return bytes_read == read_size;
}
////////////////////////////////
//~ rjf: Entity Functions
internal DMN_LNX_Entity *
dmn_lnx_entity_alloc(DMN_LNX_Entity *parent, DMN_LNX_EntityKind kind)
{
DMN_LNX_Entity *entity = dmn_lnx_state->free_entity;
if(entity != 0)
{
SLLStackPop(dmn_lnx_state->free_entity);
}
else
{
entity = push_array(dmn_lnx_state->entities_arena, DMN_LNX_Entity, 1);
dmn_lnx_state->entities_count += 1;
}
U32 gen = entity->gen;
MemoryCopyStruct(entity, &dmn_lnx_nil_entity);
entity->gen += 1;
if(parent != &dmn_lnx_nil_entity)
{
DLLPushBack_NPZ(&dmn_lnx_nil_entity, parent->first, parent->last, entity, next, prev);
entity->parent = parent;
}
entity->kind = kind;
return entity;
}
internal void
dmn_lnx_entity_release(DMN_LNX_Entity *entity)
{
if(entity->parent != &dmn_lnx_nil_entity)
{
DLLRemove_NPZ(&dmn_lnx_nil_entity, entity->parent->first, entity->parent->last, entity, next, prev);
entity->parent = &dmn_lnx_nil_entity;
}
{
Temp scratch = scratch_begin(0, 0);
DMN_LNX_EntityNode start_task = {0, entity};
DMN_LNX_EntityNode *first_task = &start_task;
for(DMN_LNX_EntityNode *t = first_task; t != 0; t = t->next)
{
SLLStackPush(dmn_lnx_state->free_entity, t->v);
for(DMN_LNX_Entity *child = t->v->first; child != &dmn_lnx_nil_entity; child = child->next)
{
DMN_LNX_EntityNode *task = push_array(scratch.arena, DMN_LNX_EntityNode, 1);
task->next = t->next;
t->next = task;
task->v = child;
}
}
scratch_end(scratch);
}
}
internal DMN_Handle
dmn_lnx_handle_from_entity(DMN_LNX_Entity *entity)
{
DMN_Handle handle = {0};
U64 index = (U64)(entity - dmn_lnx_state->entities_base);
if(index <= 0xffffffffu)
{
handle.u32[0] = index;
handle.u32[1] = entity->gen;
}
return handle;
}
internal DMN_LNX_Entity *
dmn_lnx_entity_from_handle(DMN_Handle handle)
{
DMN_LNX_Entity *result = &dmn_lnx_nil_entity;
U64 index = (U64)handle.u32[0];
if(index < dmn_lnx_state->entities_count &&
dmn_lnx_state->entities_base[index].gen == handle.u32[1])
{
result = &dmn_lnx_state->entities_base[index];
}
return result;
}
internal DMN_LNX_Entity *
dmn_lnx_thread_from_pid(pid_t pid)
{
DMN_LNX_Entity *result = &dmn_lnx_nil_entity;
if(pid != 0)
{
for EachIndex(idx, dmn_lnx_state->entities_count)
{
if(dmn_lnx_state->entities_base[idx].kind == DMN_LNX_EntityKind_Thread && (pid_t)dmn_lnx_state->entities_base[idx].id == pid)
{
result = &dmn_lnx_state->entities_base[idx];
break;
}
}
}
return result;
}
internal U64
dmn_lnx_thread_read_ip(DMN_LNX_Entity *thread)
{
U64 ip = 0;
if(thread->reg_block)
{
ip = regs_rip_from_arch_block(thread->arch, thread->reg_block);
}
Assert(ip);
return ip;
}
internal U64
dmn_lnx_thread_read_sp(DMN_LNX_Entity *thread)
{
U64 sp = 0;
if(thread->reg_block)
{
sp = regs_rsp_from_arch_block(thread->arch, thread->reg_block);
}
Assert(sp);
return sp;
}
internal B32
dmn_lnx_thread_write_ip(DMN_LNX_Entity *thread, U64 ip)
{
B32 is_ip_written = 0;
if(thread->reg_block)
{
REGS_RegBlockX64 *reg_block = thread->reg_block;
regs_arch_block_write_rip(thread->arch, reg_block, ip);
is_ip_written = dmn_lnx_thread_write_reg_block(thread, reg_block);
}
Assert(is_ip_written);
return is_ip_written;
}
internal B32
dmn_lnx_thread_write_sp(DMN_LNX_Entity *thread, U64 sp)
{
B32 is_sp_written = 0;
if(thread->reg_block)
{
REGS_RegBlockX64 *reg_block = thread->reg_block;
regs_arch_block_write_rsp(thread->arch, reg_block, sp);
is_sp_written = dmn_lnx_thread_write_reg_block(thread, reg_block);
}
Assert(is_sp_written);
return is_sp_written;
}
internal B32
dmn_lnx_thread_read_reg_block(DMN_LNX_Entity *thread, void *reg_block)
{
AssertAlways(gettid() == thread->parent->tracer_tid);
B32 result = 0;
switch(thread->arch)
{
case Arch_Null:
case Arch_COUNT:{}break;
case Arch_x86:
case Arch_arm64:
case Arch_arm32:
{NotImplemented;}break;
////////////////////////////
//- rjf: [x64]
//
case Arch_x64:
{
DMN_LNX_Entity *process = thread->parent;
pid_t tid = (pid_t)thread->id;
REGS_RegBlockX64 *dst = reg_block;
//- rjf: read GPR
B32 got_gpr = 0;
{
DMN_LNX_UserX64 ctx = {0};
int ptrace_result = ptrace(PTRACE_GETREGSET, tid, (void *)NT_PRSTATUS, &(struct iovec){ .iov_len = sizeof(ctx), .iov_base = &ctx });
if(ptrace_result != -1)
{
got_gpr = 1;
DMN_LNX_UserX64 *src = &ctx;
dst->rax.u64 = src->regs.rax;
dst->rcx.u64 = src->regs.rcx;
dst->rdx.u64 = src->regs.rdx;
dst->rbx.u64 = src->regs.rbx;
dst->rsp.u64 = src->regs.rsp;
dst->rbp.u64 = src->regs.rbp;
dst->rsi.u64 = src->regs.rsi;
dst->rdi.u64 = src->regs.rdi;
dst->r8.u64 = src->regs.r8;
dst->r9.u64 = src->regs.r9;
dst->r10.u64 = src->regs.r10;
dst->r11.u64 = src->regs.r11;
dst->r12.u64 = src->regs.r12;
dst->r13.u64 = src->regs.r13;
dst->r14.u64 = src->regs.r14;
dst->r15.u64 = src->regs.r15;
dst->cs.u16 = src->regs.cs;
dst->ds.u16 = src->regs.ds;
dst->es.u16 = src->regs.es;
dst->fs.u16 = src->regs.fs;
dst->gs.u16 = src->regs.gs;
dst->ss.u16 = src->regs.ss;
dst->fsbase.u64 = src->regs.fsbase;
dst->gsbase.u64 = src->regs.gsbase;
dst->rip.u64 = src->regs.rip;
dst->rflags.u64 = src->regs.rflags;
}
else { Assert(0 && "failed to get gprs"); }
}
//- rjf: read FPR
B32 got_fpr = 0;
if(got_gpr)
{
Temp scratch = scratch_begin(0, 0);
X64_XSave *xsave = 0;
X64_FXSave *fxsave = 0;
// get xsave
if(x64_is_xsave_supported())
{
void *xsave_raw = push_array(scratch.arena, U8, process->xsave_size);
int ptrace_result = ptrace(PTRACE_GETREGSET, tid, (void *)NT_X86_XSTATE, &(struct iovec){ .iov_len = process->xsave_size, .iov_base = xsave_raw });
if(ptrace_result != -1)
{
xsave = xsave_raw;
fxsave = &xsave->fxsave;
}
else { Assert(0 && "failed to get xsave"); }
}
// get fxsave
if (fxsave == 0)
{
fxsave = push_array(scratch.arena, X64_FXSave, 1);
int ptrace_result = ptrace(PTRACE_GETREGSET, tid, (void *)NT_FPREGSET, &(struct iovec){ .iov_len = sizeof(*fxsave), .iov_base = fxsave });
if(ptrace_result != -1)
{
fxsave = 0;
}
else { Assert(0 && "failed to get fxsave"); }
}
// copy fxsave registers
if(fxsave)
{
X64_FXSave *src = fxsave;
// copy x87 registers
dst->fcw.u16 = src->fcw;
dst->fsw.u16 = src->fsw;
dst->ftw.u16 = x64_xsave_tag_word_from_real_tag_word(src->ftw);
dst->fop.u16 = src->fop;
dst->fip.u64 = src->b64.fip;
dst->fdp.u64 = src->b64.fdp;
dst->mxcsr.u32 = src->mxcsr;
dst->mxcsr_mask.u32 = src->mxcsr_mask;
for EachIndex(i, 8)
{
MemoryCopy(&dst->st0 + i, src->st_space + i, sizeof(REGS_Reg80));
}
// SSE registers are always available in x64
{
U128 *xmm_d = fxsave->xmm_space;
REGS_Reg512 *zmm_s = &dst->zmm0;
for EachIndex(i, 16)
{
MemoryCopy(&zmm_s[i], &xmm_d[i], sizeof(*xmm_d));
}
}
}
// copy xsave registers
if(xsave)
{
if(xsave->header.xstate_bv & X64_XStateComponentFlag_AVX)
{
AssertAlways(process->xsave_layout.avx_offset + 16*sizeof(REGS_Reg128) <= process->xsave_size);
REGS_Reg128 *avx_s = (REGS_Reg128 *)((U8 *)xsave + process->xsave_layout.avx_offset);
REGS_Reg512 *zmm_d = &dst->zmm0;
for EachIndex(n, 16)
{
MemoryCopy(&zmm_d[n].v[16], &avx_s[n], sizeof(REGS_Reg128));
}
}
if(xsave->header.xstate_bv & X64_XStateComponentFlag_OPMASK)
{
AssertAlways(process->xsave_layout.opmask_offset + sizeof(REGS_Reg64) * 8 <= process->xsave_size);
REGS_Reg64 *kmask_s = (REGS_Reg64 *)((U8 *)xsave + process->xsave_layout.opmask_offset);
REGS_Reg64 *kmask_d = &dst->k0;
for EachIndex(n, 8)
{
MemoryCopy(&kmask_d[n], &kmask_s[n], sizeof(REGS_Reg64));
}
}
if(xsave->header.xstate_bv & X64_XStateComponentFlag_ZMM_H)
{
AssertAlways(process->xsave_layout.zmm_h_offset + sizeof(REGS_Reg256) * 16 <= process->xsave_size);
REGS_Reg256 *avx512h_s = (REGS_Reg256 *)((U8 *)xsave + process->xsave_layout.zmm_h_offset);
REGS_Reg512 *zmmh_d = &dst->zmm0;
for EachIndex(n, 16)
{
MemoryCopy(&zmmh_d[n].v[32], &avx512h_s[n], sizeof(REGS_Reg256));
}
}
if(xsave->header.xstate_bv & X64_XStateComponentFlag_ZMM)
{
AssertAlways(process->xsave_layout.zmm_offset + sizeof(REGS_Reg512) * 16 <= process->xsave_size);
REGS_Reg512 *avx512_s = (REGS_Reg512 *)((U8 *)xsave + process->xsave_layout.zmm_offset);
REGS_Reg512 *zmm_d = &dst->zmm16;
for EachIndex(n, 16)
{
MemoryCopy(&zmm_d[n], &avx512_s[n], sizeof(REGS_Reg512));
}
}
if(xsave->header.xstate_bv & X64_XStateComponentFlag_CETU)
{
AssertAlways(process->xsave_layout.cet_u_offset + sizeof(U64)*2 <= process->xsave_size);
U64 *cet_u = (U64 *)((U8 *)xsave + process->xsave_layout.cet_u_offset);
dst->cetmsr.u64 = cet_u[0];
dst->cetssp.u64 = cet_u[1];
}
}
got_fpr = (xsave || fxsave);
scratch_end(scratch);
}
//- rjf: read debug registers
B32 got_debug = 0;
if(got_fpr)
{
got_debug = 1;
REGS_Reg64 *dr_d = &dst->dr0;
for EachIndex(n, 8)
{
if(n != 4 && n != 5)
{
U64 offset = OffsetOf(DMN_LNX_UserX64, u_debugreg[n]);
errno = 0;
long peek_result = ptrace(PTRACE_PEEKUSER, tid, PtrFromInt(offset), 0);
if(errno == 0)
{
dr_d[n].u64 = (U64)peek_result;
}
else
{
got_debug = 0;
}
}
}
}
result = got_debug;
}break;
}
return result;
}
internal B32
dmn_lnx_thread_write_reg_block(DMN_LNX_Entity *thread, void *reg_block)
{
AssertAlways(gettid() == thread->parent->tracer_tid);
B32 result = 0;
switch(thread->arch)
{
case Arch_Null:
case Arch_COUNT:{}break;
case Arch_arm64:
case Arch_arm32:
case Arch_x86:
{NotImplemented;}break;
////////////////////////////
//- rjf: [x64]
//
case Arch_x64:
{
DMN_LNX_Entity *process = thread->parent;
pid_t tid = (pid_t)thread->id;
REGS_RegBlockX64 *src = reg_block;
//- rjf: write GPR
B32 did_gpr = 0;
{
DMN_LNX_UserX64 dst = {0};
dst.regs.rax = src->rax.u64;
dst.regs.rcx = src->rcx.u64;
dst.regs.rdx = src->rdx.u64;
dst.regs.rbx = src->rbx.u64;
dst.regs.rsp = src->rsp.u64;
dst.regs.rbp = src->rbp.u64;
dst.regs.rsi = src->rsi.u64;
dst.regs.rdi = src->rdi.u64;
dst.regs.r8 = src->r8.u64;
dst.regs.r9 = src->r9.u64;
dst.regs.r10 = src->r10.u64;
dst.regs.r11 = src->r11.u64;
dst.regs.r12 = src->r12.u64;
dst.regs.r13 = src->r13.u64;
dst.regs.r14 = src->r14.u64;
dst.regs.r15 = src->r15.u64;
dst.regs.cs = src->cs.u16;
dst.regs.ds = src->ds.u16;
dst.regs.es = src->es.u16;
dst.regs.fs = src->fs.u16;
dst.regs.gs = src->gs.u16;
dst.regs.ss = src->ss.u16;
dst.regs.fsbase = src->fsbase.u64;
dst.regs.gsbase = src->gsbase.u64;
dst.regs.rip = src->rip.u64;
dst.regs.rflags = src->rflags.u64;
did_gpr = ptrace(PTRACE_SETREGSET, tid, (void *)NT_PRSTATUS, &(struct iovec){ .iov_base = &dst, .iov_len = sizeof(dst) }) >= 0;
}
B32 did_fpr = 0;
if(did_gpr)
{
Temp scratch = scratch_begin(0, 0);
int xsave_result = -1;
int fxsave_result = -1;
X64_FXSave dst_fxsave = {0};
{
dst_fxsave.fcw = src->fcw.u16;
dst_fxsave.fsw = src->fsw.u16;
dst_fxsave.ftw = src->ftw.u16;
dst_fxsave.fop = src->fop.u16;
dst_fxsave.b64.fip = src->fip.u64;
dst_fxsave.b64.fdp = src->fdp.u64;
dst_fxsave.mxcsr = src->mxcsr.u32;
dst_fxsave.mxcsr_mask = src->mxcsr_mask.u32;
REGS_Reg128 *st_d = (REGS_Reg128 *)dst_fxsave.st_space;
REGS_Reg80 *st_s = &src->st0;
for EachIndex(n, 8)
{
MemoryCopy(&st_d[n], &st_s[n], sizeof(REGS_Reg80));
}
REGS_Reg128 *xmm_d = (REGS_Reg128 *)dst_fxsave.xmm_space;
REGS_Reg512 *xmm_s = &src->zmm0;
for EachIndex(n, 16)
{
MemoryCopy(&xmm_d[n], &xmm_s[n], sizeof(REGS_Reg128));
}
}
if(x64_is_xsave_supported())
{
U8 *xsave_raw = push_array(scratch.arena, U8, process->xsave_size);
int xsave_get = ptrace(PTRACE_GETREGSET, tid, (void *)NT_PRSTATUS, &(struct iovec){ .iov_base = xsave_raw, .iov_len = process->xsave_size });
AssertAlways(xsave_get >= 0);
X64_XSave *dst = (X64_XSave *)xsave_raw;
dst->fxsave = dst_fxsave;
if(process->xsave_layout.avx_offset)
{
if(process->xsave_layout.avx_offset + sizeof(REGS_Reg128) * 16 <= process->xsave_size)
{
REGS_Reg128 *avx_d = (REGS_Reg128 *)(xsave_raw + process->xsave_layout.avx_offset);
REGS_Reg512 *zmm_s = &src->zmm0;
for EachIndex(n, 16)
{
MemoryCopy(&avx_d[n], &zmm_s[n].v[16], sizeof(REGS_Reg128));
}
}
}
if(process->xsave_layout.opmask_offset)
{
if(process->xsave_layout.opmask_offset + sizeof(REGS_Reg64) * 8 <= process->xsave_size)
{
REGS_Reg64 *kmask_d = (REGS_Reg64 *)(xsave_raw + process->xsave_layout.opmask_offset);
REGS_Reg64 *kmask_s = &src->k0;
for EachIndex(n, 8)
{
MemoryCopy(&kmask_d[n], &kmask_s[n], sizeof(REGS_Reg64));
}
}
else { Assert(0 && "invalid xsave size"); }
}
if(process->xsave_layout.zmm_h_offset)
{
if(process->xsave_layout.zmm_h_offset + sizeof(REGS_Reg256) * 16 <= process->xsave_size)
{
REGS_Reg256 *avx512h_d = (REGS_Reg256 *)(xsave_raw + process->xsave_layout.zmm_h_offset);
REGS_Reg512 *zmmh_s = &src->zmm0;
for EachIndex(n, 16)
{
MemoryCopy(&avx512h_d[n], &zmmh_s[n].v[32], sizeof(REGS_Reg256));
}
}
else { Assert(0 && "invalid xsave size"); }
}
if(process->xsave_layout.zmm_offset)
{
if(process->xsave_layout.zmm_offset + sizeof(REGS_Reg512) * 16 <= process->xsave_size)
{
REGS_Reg512 *avx512_d = (REGS_Reg512 *)(xsave_raw + process->xsave_layout.zmm_offset);
REGS_Reg512 *zmm_s = &src->zmm16;
for EachIndex(n, 16)
{
MemoryCopy(&avx512_d[n], &zmm_s[n], sizeof(REGS_Reg512));
}
}
else { Assert(0 && "invalid xsave size"); }
}
if(process->xsave_layout.cet_u_offset)
{
if(process->xsave_layout.cet_u_offset + sizeof(REGS_Reg64) * 2 <= process->xsave_size)
{
REGS_Reg64 *cet_u = (REGS_Reg64 *)(xsave_raw + process->xsave_layout.cet_u_offset);
cet_u[0] = src->cetmsr;
cet_u[1] = src->cetssp;
}
else { Assert(0 && "invalid xsave size"); }
}
// xsave
xsave_result = ptrace(PTRACE_SETREGSET, tid, (void *)NT_X86_XSTATE, &(struct iovec){ .iov_base = dst, .iov_len = process->xsave_size });
Assert(xsave_result >= 0);
}
// fallback to fxsave
if(xsave_result < 0)
{
fxsave_result = ptrace(PTRACE_SETREGSET, tid, (void *)NT_FPREGSET, &(struct iovec){ .iov_base = &dst_fxsave, sizeof(dst_fxsave) });
Assert(fxsave_result >= 0);
}
// rjf: good finish requires xsave or fxsave
did_fpr = (xsave_result >= 0 || fxsave_result >= 0);
scratch_end(scratch);
}
//- rjf: write debug registers
B32 did_dbg = 0;
if(did_fpr)
{
did_dbg = 1;
src->dr7.u64 |= (1 << 10);
REGS_Reg64 *dr_s = &src->dr0;
for EachIndex(n, 8)
{
if(n != 4 && n != 5)
{
U64 offset = OffsetOf(DMN_LNX_UserX64, u_debugreg[n]);
int poke_result = ptrace(PTRACE_POKEUSER, tid, PtrFromInt(offset), dr_s[n].u64);
if(poke_result < 0)
{
did_dbg = 0;
break;
}
}
}
}
result = (did_dbg);
}break;
}
return result;
}
internal B32
dmn_lnx_set_single_step_flag(DMN_LNX_Entity *thread, B32 is_on)
{
B32 is_flag_set = 0;
switch(thread->arch)
{
case Arch_COUNT:
case Arch_Null: {} break;
case Arch_x64:
{
REGS_RegBlockX64 *reg_block = thread->reg_block;
if(is_on)
{
reg_block->rflags.u64 |= X64_RFlag_Trap;
}
else
{
reg_block->rflags.u64 &= ~X64_RFlag_Trap;
}
is_flag_set = dmn_lnx_thread_write_reg_block(thread, thread->reg_block);
}break;
case Arch_x86:
case Arch_arm32:
case Arch_arm64:
{
NotImplemented;
}break;
}
Assert(is_flag_set);
return is_flag_set;
}
internal void
dmn_lnx_process_loaded_modules(Arena *arena, DMN_EventList *events, DMN_LNX_Entity *process, U64 name_space_id, U64 new_link_map_vaddr)
{
GNU_LinkMap64 map = {0};
for(U64 map_vaddr = new_link_map_vaddr; map_vaddr != 0; map_vaddr = map.next_vaddr)
{
// read out new link map item
if(!dmn_lnx_read_linkmap(process->fd, map_vaddr, process->dl_class, &map)) { goto exit; }
// was module with this base already registered?
DMN_LNX_Entity *module = hash_table_search_u64_raw(process->loaded_modules_ht, map.addr_vaddr);
if(module) { continue; }
// parse out module's ELF header
ELF_Hdr64 module_ehdr = {0};
if(!dmn_lnx_read_ehdr(process->fd, map.addr_vaddr, &module_ehdr)) { goto exit; }
// gather info about module
U64 module_rebase = module_ehdr.e_type == ELF_Type_Dyn ? map.addr_vaddr : 0;
U64 module_phdr_vaddr = module_rebase + module_ehdr.e_phoff;
DMN_LNX_PhdrInfo module_phdr_info = dmn_lnx_phdr_info_from_memory(process->fd, module_ehdr.e_ident[ELF_Identifier_Class], module_rebase, module_phdr_vaddr, module_ehdr.e_phentsize, module_ehdr.e_phnum);
String8 module_name = dmn_lnx_read_string(process->arena, process->fd, map.name_vaddr);
// fill out module
module = dmn_lnx_entity_alloc(process, DMN_LNX_EntityKind_Module);
module->id = map.name_vaddr;
module->base_vaddr = map.addr_vaddr;
// push load event
if(!str8_match(module_name, str8_lit("linux-vdso.so.1"), 0))
{
DMN_Event *e = dmn_event_list_push(arena, events);
e->kind = DMN_EventKind_LoadModule;
e->process = dmn_lnx_handle_from_entity(process);
e->module = dmn_lnx_handle_from_entity(module);
e->arch = arch_from_elf_machine(module_ehdr.e_machine);
e->address = map.addr_vaddr;
e->size = dim_1u64(module_phdr_info.range);
e->string = module_name;
e->elf_phdr_vrange = r1u64(module_phdr_vaddr, module_phdr_vaddr + module_ehdr.e_phentsize * module_ehdr.e_phnum);
e->elf_phdr_entsize = module_ehdr.e_phentsize;
}
// create mapping for base -> module
hash_table_push_u64_raw(process->arena, process->loaded_modules_ht, map.addr_vaddr, module);
}
exit:;
}
internal void
dmn_lnx_process_unloaded_modules(Arena *arena, DMN_EventList *events, DMN_LNX_Entity *process, U64 name_space_id, U64 rdebug_vaddr)
{
Temp scratch = scratch_begin(&arena, 1);
B32 is_unmap_complete_finished = 0;
GNU_RDebugInfo64 rdebug = {0};
if(!dmn_lnx_read_r_debug(process->fd, rdebug_vaddr, process->arch, &rdebug)) { goto exit; }
if(rdebug.r_version != 1) { goto exit; }
// flag every module as inactive
for(DMN_LNX_Entity *module = process->first; module != &dmn_lnx_nil_entity; module = module->next)
{
if(module->kind != DMN_LNX_EntityKind_Module) {continue;}
module->is_live = 0;
}
// loop over modules in the link map and mark live modules
GNU_LinkMap64 map = {0};
for(U64 map_vaddr = rdebug.r_map; map_vaddr != 0; map_vaddr = map.next_vaddr)
{
if(dmn_lnx_read_linkmap(process->fd, map_vaddr, process->dl_class, &map))
{
DMN_LNX_Entity *module = hash_table_search_u64_raw(process->loaded_modules_ht, map.addr_vaddr);
if(module)
{
module->is_live = 1;
}
else { Assert(0 && "unknown module is being unloaded"); }
}
else { Assert(0 && "unable to read Link Map"); }
}
// unload inactive modules
DMN_HandleList to_release = {0};
for(DMN_LNX_Entity *module = process->first; module != &dmn_lnx_nil_entity; module = module->next)
{
if(module->kind != DMN_LNX_EntityKind_Module) {continue;}
if(module->is_live) {continue;}
dmn_handle_list_push(scratch.arena, &to_release, dmn_lnx_handle_from_entity(module));
}
// push events and clean up internal structures
for EachNode(n, DMN_HandleNode, to_release.first)
{
DMN_LNX_Entity *module = dmn_lnx_entity_from_handle(n->v);
DMN_Event *e = dmn_event_list_push(dmn_lnx_state->deferred_events_arena, &dmn_lnx_state->deferred_events);
e->kind = DMN_EventKind_UnloadModule;
e->process = dmn_lnx_handle_from_entity(process);
e->module = dmn_lnx_handle_from_entity(module);
e->string = dmn_lnx_read_string(arena, process->fd, module->id);
hash_table_purge_u64(process->loaded_modules_ht, module->base_vaddr);
dmn_lnx_entity_release(module);
}
is_unmap_complete_finished = 1;
exit:;
Assert(is_unmap_complete_finished);
scratch_end(scratch);
}
////////////////////////////////
//~ rjf: @dmn_os_hooks Main Layer Initialization (Implemented Per-OS)
internal void
dmn_init(void)
{
Arena *arena = arena_alloc();
dmn_lnx_state = push_array(arena, DMN_LNX_State, 1);
dmn_lnx_state->arena = arena;
dmn_lnx_state->deferred_events_arena = arena_alloc();
dmn_lnx_state->entities_arena = arena_alloc(.reserve_size = GB(32), .commit_size = KB(64), .flags = ArenaFlag_NoChain);
dmn_lnx_state->entities_base = push_array(dmn_lnx_state->entities_arena, DMN_LNX_Entity, 0);
dmn_lnx_entity_alloc(&dmn_lnx_nil_entity, DMN_LNX_EntityKind_Root);
dmn_lnx_state->access_mutex = mutex_alloc();
}
////////////////////////////////
//~ rjf: @dmn_os_hooks Blocking Control Thread Operations (Implemented Per-OS)
internal DMN_CtrlCtx *
dmn_ctrl_begin(void)
{
DMN_CtrlCtx *ctx = (DMN_CtrlCtx *)1;
dmn_lnx_ctrl_thread = 1;
return ctx;
}
internal void
dmn_ctrl_exclusive_access_begin(void)
{
MutexScope(dmn_lnx_state->access_mutex)
{
dmn_lnx_state->access_run_state = 1;
}
}
internal void
dmn_ctrl_exclusive_access_end(void)
{
MutexScope(dmn_lnx_state->access_mutex)
{
dmn_lnx_state->access_run_state = 0;
}
}
internal U32
dmn_ctrl_launch(DMN_CtrlCtx *ctx, OS_ProcessLaunchParams *params)
{
Temp scratch = scratch_begin(0, 0);
//- rjf: unpack command line
char **argv = 0;
int argc = 0;
{
argc = (int)(params->cmd_line.node_count);
argv = push_array(scratch.arena, char *, argc+1);
{
U64 idx = 0;
for(String8Node *n = params->cmd_line.first; n != 0; n = n->next, idx += 1)
{
argv[idx] = (char *)push_str8_copy(scratch.arena, n->string).str;
}
}
}
//- rjf: unpack path
char *path = (char *)push_str8_copy(scratch.arena, params->path).str;
//- rjf: unpack environment
char **env = 0;
{
env = push_array(scratch.arena, char *, params->env.node_count+1);
{
U64 idx = 0;
for(String8Node *n = params->env.first; n != 0; n = n->next, idx += 1)
{
env[idx] = (char *)push_str8_copy(scratch.arena, n->string).str;
}
}
}
//- rjf: create & set up new process
if(argv != 0 && argv[0] != 0)
{
pid_t pid = 0;
int ptrace_result = 0;
int chdir_result = 0;
B32 error__need_child_kill = 0;
// open temp pipes to communicate with child process
int pipe_fds[2] = {0};
if (pipe(&pipe_fds[0]) < 0) { InvalidPath; }
//- rjf: fork
pid = fork();
if(pid == -1) { goto error; }
//- rjf: child process -> execute actual target
if(pid == 0)
{
// wait for parent seize
char b;
read(pipe_fds[0], &b, sizeof(b));
// set current working directory to tracee
chdir_result = chdir(path);
if(chdir_result == -1) { goto error; }
// replace process with target
execve(argv[0], argv, env);
// execve failed -- exit
abort();
}
//- rjf: parent process
if(pid != 0)
{
enum LaunchStatus
{
LaunchStatus_Null,
LaunchStatus_FailBeforePtrace,
LaunchStatus_FailAfterPtrace,
LaunchStatus_Success,
};
enum LaunchStatus launch_status = LaunchStatus_FailBeforePtrace;
{
int s = 0;
// seize process
if (ptrace(PTRACE_SEIZE, pid, 0, 0) < 0) { Assert(0 && "seize failed"); goto launch_error; }
// interrupt process
launch_status = LaunchStatus_FailAfterPtrace;
if (ptrace(PTRACE_INTERRUPT, pid, 0, 0) < 0) { Assert(0 && "interrupt failed"); goto launch_error; }
if (waitpid(pid, &s, __WALL|__WNOTHREAD) < 0) { Assert(0 && "interrupt wait failed"); goto launch_error; }
// entry read
if (ptrace(PTRACE_SYSCALL, pid, 0, 0) < 0) { Assert(0 && "syscall failed"); goto launch_error; }
// resume child
char b = 1;
if (write(pipe_fds[1], &b, sizeof(b)) < 0) { Assert(0 && "resume child failed"); goto launch_error; }
// exit read
if (waitpid(pid, &s, __WALL|__WNOTHREAD) < 0) { Assert(0 && "wait failed"); goto launch_error; }
if (ptrace(PTRACE_SYSCALL, pid, 0, 0) < 0) { Assert(0 && "syscall failed"); goto launch_error; }
// entry chdir
if (waitpid(pid, &s, __WALL|__WNOTHREAD) < 0) { Assert(0 && "wait failed"); goto launch_error; }
if (ptrace(PTRACE_SYSCALL, pid, 0, 0) < 0) { Assert(0 && "syscall failed"); goto launch_error; }
// exit chdir
if (waitpid(pid, &s, __WALL|__WNOTHREAD) < 0) { Assert(0 && "wait failed"); goto launch_error; }
if (ptrace(PTRACE_SYSCALL, pid, 0, 0) < 0) { Assert(0 && "syscall failed"); goto launch_error; }
// entry execv
if (waitpid(pid, &s, __WALL|__WNOTHREAD) < 0) { Assert(0 && "wait failed"); goto launch_error; }
if (ptrace(PTRACE_SYSCALL, pid, 0, 0) < 0) { Assert(0 && "syscall failed"); goto launch_error; }
// exit execve
if (waitpid(pid, &s, __WALL | __WNOTHREAD) < 0) { Assert(0 && "wait failed"); goto launch_error; }
if (ptrace(PTRACE_SYSCALL, pid, 0, 0) < 0) { Assert(0 && "syscall failed"); goto launch_error; }
if (waitpid(pid, &s, __WALL | __WNOTHREAD) < 0) { Assert(0 && "wait failed"); goto launch_error; }
uintptr_t trace_options = PTRACE_O_TRACEEXIT | PTRACE_O_EXITKILL | PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK | PTRACE_O_TRACECLONE | PTRACE_O_TRACEVFORKDONE;
int setoptions_result = ptrace(PTRACE_SETOPTIONS, pid, 0, PtrFromInt(trace_options));
if (setoptions_result == -1) { Assert(0 && "failed to set options"); error__need_child_kill = 1; goto launch_error; }
launch_status = LaunchStatus_Success;
launch_error:;
}
//- rjf: respond to launch status appropriately
switch(launch_status)
{
//- rjf: no understood handling path
default:{}break;
//- rjf: failure, after ptrace => we need to explicitly obtain the
// result code & exit the process, otherwise it will become a zombie,
// since it is ptrace'd.
case LaunchStatus_FailAfterPtrace:
{
B32 cleanup_good = 0;
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 = 1;
}
}
if(cleanup_good)
{
// TODO(rjf): child initialization failed, but we at least cleaned it up.
}
else
{
// TODO(rjf): child initialization failed, *and* we couldn't clean it up, so we've created
// yet-another zombie.
}
}break;
//- rjf: successful launch
case LaunchStatus_Success:
{
ELF_Hdr64 exe_ehdr = dmn_lnx_ehdr_from_pid(pid);
int memory_fd = open((char*)str8f(scratch.arena, "/proc/%d/mem", pid).str, O_RDWR|O_CLOEXEC);
DMN_LNX_ProcessAuxv auxv = dmn_lnx_auxv_from_pid(pid, exe_ehdr.e_ident[ELF_Identifier_Class]);
Arch arch = arch_from_elf_machine(exe_ehdr.e_machine);
U64 rdebug_vaddr = dmn_lnx_rdebug_vaddr_from_memory(memory_fd, auxv.base);
U64 rdebug_brk_vaddr = rdebug_vaddr + gnu_r_brk_offset_from_arch(arch);
ELF_Class dl_class;
{
ELF_Hdr64 ehdr = {0};
if(!dmn_lnx_read_ehdr(memory_fd, auxv.base, &ehdr)) { Assert(0 && "failed to read interp's header"); }
dl_class = ehdr.e_ident[ELF_Identifier_Class];
}
U64 xcr0 = 0;
U64 xsave_size = 0;
X64_XSaveLayout xsave_layout = {0};
if(arch == Arch_x64)
{
X64_XSave xsave = {0};
if(ptrace(PTRACE_GETREGSET, pid, (void*)NT_X86_XSTATE, &(struct iovec){.iov_base = &xsave, .iov_len = sizeof(xsave) }) >= 0)
{
// Linux stores xcr0 bits in fxstate padding,
// see https://github.com/torvalds/linux/blob/6548d364a3e850326831799d7e3ea2d7bb97ba08/arch/x86/include/asm/user.h#L25
xcr0 = *(U64 *)((U8 *)&xsave + 464);
xsave_size = x64_get_xsave_size();
xsave_layout = x64_get_xsave_layout(xcr0);
}
else
{
Assert(0 && "failed to get xstate");
}
}
String8 dl_path = {0};
{
int maps_fd = open((char *)str8f(scratch.arena, "/proc/%d/maps", pid).str, O_RDONLY|O_CLOEXEC);
if(maps_fd != -1)
{
struct stat st = {0};
if(fstat(maps_fd, &st) != -1)
{
U64 maps_size = dmn_lnx_size_from_fd(maps_fd, MB(1));
U8 *maps_ptr = push_array(scratch.arena, U8, maps_size);
U64 read_size = dmn_lnx_read(maps_fd, r1u64(0, maps_size), maps_ptr);
if(read_size == maps_size)
{
String8 maps = str8(maps_ptr, maps_size);
String8List parts = {0};
{
for(U64 cursor = 0, part_off = 0; cursor < maps.size; cursor += 1)
{
if(maps.str[cursor] == '\\')
{
cursor += 1;
continue;
}
if(maps.str[cursor] == ' ' || maps.str[cursor] == '\n' || cursor + 1 >= maps.size)
{
String8 p = str8_substr(maps, r1u64(part_off, cursor));
if(p.size > 0)
{
str8_list_push(scratch.arena, &parts, p);
}
part_off = cursor + 1;
}
}
}
for(String8Node *n = parts.first; n != 0; )
{
String8 vrange_str = n->string;
n = n->next;
if(n == 0) { break; }
String8 perms_str = n->string;
n = n->next;
if(n == 0) { break; }
String8 offset_str = n->string;
n = n->next;
if(n == 0) { break; }
String8 dev_str = n->string;
n = n->next;
if(n == 0) { break; }
String8 inode_str = n->string;
n = n->next;
if(n == 0) { break; }
String8 path = n->string;
n = n->next;
if(n == 0) { break; }
String8List vaddr_list = str8_split_by_string_chars(scratch.arena, vrange_str, str8_lit("-"), 0);
if(vaddr_list.node_count != 2) { break; }
U64 lo_vaddr = u64_from_str8(vaddr_list.first->string, 16);
if(lo_vaddr == auxv.base)
{
dl_path = push_str8_copy(scratch.arena, path);
break;
}
}
}
}
close(maps_fd);
}
}
AssertAlways(dl_path.size);
// alloc arena for the process
Arena *process_arena = arena_alloc();
DMN_LNX_Probe **known_probes = push_array(process_arena, DMN_LNX_Probe *, DMN_LNX_ProbeType_Count);
{
DMN_LNX_ProbeList probes = {0};
int dl_fd = open((char *)dl_path.str, O_RDONLY|O_CLOEXEC);
if(dl_fd >= 0)
{
probes = dmn_lnx_read_probes(process_arena, dl_fd, 0, auxv.base);
close(dl_fd);
}
for EachNode(n, DMN_LNX_ProbeNode, probes.first)
{
DMN_LNX_Probe *p = &n->v;
if(str8_match(p->provider, str8_lit("rtld"), 0))
{
#define X(_N,_A,_S) if(str8_match(p->name, str8_lit(_S), 0)) { AssertAlways(p->args.count == _A); known_probes[DMN_LNX_ProbeType_##_N] = p; continue ; }
DMN_LNX_Probe_XList
#undef X
}
}
}
// install DL probes
U64 probe_vaddrs[DMN_LNX_ProbeType_Count] = {0};
for EachIndex(i, DMN_LNX_ProbeType_Count)
{
if(known_probes[i] == 0) { continue; }
U8 og_byte = 0;
if(!dmn_lnx_read_struct(memory_fd, known_probes[i]->pc, &og_byte)) { Assert(0 && "failed to read original byte"); }
Assert(og_byte == 0x90);
U8 trap = 0xcc;
if(!dmn_lnx_write_struct(memory_fd, known_probes[i]->pc, &trap)) { Assert(0 && "failed to install probe"); }
probe_vaddrs[i] = known_probes[i]->pc;
}
// make process entity & push create event
DMN_LNX_Entity *process = &dmn_lnx_nil_entity;
{
process = dmn_lnx_entity_alloc(dmn_lnx_state->entities_base, DMN_LNX_EntityKind_Process);
process->arch = arch;
process->id = pid;
process->fd = memory_fd;
process->tracer_tid = gettid();
process->rdebug_vaddr = rdebug_vaddr;
process->rdebug_brk_vaddr = rdebug_brk_vaddr;
process->expect_rdebug_data_breakpoint = rdebug_vaddr != 0;
process->dl_class = dl_class;
process->arena = process_arena;
process->loaded_modules_ht = hash_table_init(process_arena, 0x1000);
process->probes = known_probes;
process->xcr0 = xcr0;
process->xsave_size = Max(xsave_size, sizeof(X64_XSave));
process->xsave_layout = xsave_layout;
MemoryCopyTyped(&process->probe_vaddrs[0], &probe_vaddrs[0], DMN_LNX_ProbeType_Count);
{
DMN_Event *e = dmn_event_list_push(dmn_lnx_state->deferred_events_arena, &dmn_lnx_state->deferred_events);
e->kind = DMN_EventKind_CreateProcess;
e->process = dmn_lnx_handle_from_entity(process);
e->arch = process->arch;
e->code = pid;
}
}
// make thread entity & push create event
DMN_LNX_Entity *main_thread = &dmn_lnx_nil_entity;
{
main_thread = dmn_lnx_entity_alloc(process, DMN_LNX_EntityKind_Thread);
main_thread->id = pid;
main_thread->arch = process->arch;
main_thread->reg_block = push_array(process->arena, U8, regs_block_size_from_arch(process->arch));
dmn_lnx_thread_read_reg_block(main_thread, main_thread->reg_block);
{
DMN_Event *e = dmn_event_list_push(dmn_lnx_state->deferred_events_arena, &dmn_lnx_state->deferred_events);
e->kind = DMN_EventKind_CreateThread;
e->process = dmn_lnx_handle_from_entity(process);
e->thread = dmn_lnx_handle_from_entity(main_thread);
e->arch = main_thread->arch;
e->code = main_thread->id;
}
}
// make main module & push load module event
{
U64 base_vaddr = (auxv.phdr & ~(auxv.pagesz-1));
U64 rebase = exe_ehdr.e_type == ELF_Type_Dyn ? base_vaddr : 0;
DMN_LNX_PhdrInfo phdr_info = dmn_lnx_phdr_info_from_memory(memory_fd, exe_ehdr.e_ident[ELF_Identifier_Class], rebase, auxv.phdr, auxv.phent, auxv.phnum);
DMN_LNX_Entity *module = dmn_lnx_entity_alloc(process, DMN_LNX_EntityKind_Module);
module->id = auxv.execfn;
module->base_vaddr = base_vaddr;
DMN_Event *e = dmn_event_list_push(dmn_lnx_state->deferred_events_arena, &dmn_lnx_state->deferred_events);
e->kind = DMN_EventKind_LoadModule;
e->process = dmn_lnx_handle_from_entity(process);
e->thread = dmn_lnx_handle_from_entity(main_thread);
e->module = dmn_lnx_handle_from_entity(module);
e->arch = process->arch;
e->address = base_vaddr;
e->size = dim_1u64(phdr_info.range);
e->string = dmn_lnx_read_string(dmn_lnx_state->deferred_events_arena, process->fd, auxv.execfn);
e->elf_phdr_vrange = r1u64(auxv.phdr, auxv.phdr + auxv.phent * auxv.phnum);
e->elf_phdr_entsize = auxv.phent;
hash_table_push_u64_raw(process->arena, process->loaded_modules_ht, 0, module);
hash_table_push_u64_raw(process->arena, process->loaded_modules_ht, base_vaddr, module);
}
// rjf: handshake event
{
DMN_Event *e = dmn_event_list_push(dmn_lnx_state->deferred_events_arena, &dmn_lnx_state->deferred_events);
e->kind = DMN_EventKind_HandshakeComplete;
e->process = dmn_lnx_handle_from_entity(process);
e->thread = dmn_lnx_handle_from_entity(main_thread);
e->arch = process->arch;
}
}break;
}
}
//- rjf: error case
goto success;
error:;
{
if(error__need_child_kill)
{
// TODO(rjf)
}
}
//- rjf: success
success:;
// clean up pipes
close(pipe_fds[0]);
close(pipe_fds[1]);
}
scratch_end(scratch);
return 0;
}
internal B32
dmn_ctrl_attach(DMN_CtrlCtx *ctx, U32 pid)
{
return 0;
}
internal B32
dmn_ctrl_kill(DMN_CtrlCtx *ctx, DMN_Handle process, U32 exit_code)
{
B32 result = 0;
DMN_LNX_Entity *process_entity = dmn_lnx_entity_from_handle(process);
if(process_entity != &dmn_lnx_nil_entity &&
kill(process_entity->id, SIGKILL) != -1)
{
result = 1;
}
return result;
}
internal B32
dmn_ctrl_detach(DMN_CtrlCtx *ctx, DMN_Handle process)
{
B32 result = 0;
DMN_LNX_Entity *process_entity = dmn_lnx_entity_from_handle(process);
if(process_entity != &dmn_lnx_nil_entity &&
ptrace(PTRACE_DETACH, process_entity->id, 0, 0) != -1)
{
result = 1;
}
return result;
}
internal void
dmn_lnx_wait_for_events(Arena *arena, DMN_EventList *evts, pid_t tid, B32 wait_for_group_stop, DMN_ActiveTrap *active_traps)
{
for(B32 done = 0; !done;)
{
//- rjf: wait for next event
int status = 0;
pid_t wait_id = waitpid(tid, &status, __WALL|__WNOTHREAD);
if(status == -1 && errno == EINTR) {continue;} // wait interrupted, try again
if(status == -1) {InvalidPath;} // TODO: graceful exit
//- rjf: unpack event
int wifexited = WIFEXITED(status);
int wifsignaled = WIFSIGNALED(status);
int wifstopped = WIFSTOPPED(status);
int wstopsig = WSTOPSIG(status);
int ptrace_event_code = (status>>16);
DMN_LNX_Entity *thread = dmn_lnx_thread_from_pid(wait_id);
DMN_LNX_Entity *process = thread->parent;
//printf("waitpid - pid %d wifexited %d, wifsignaled %d, wifstopped %d, wstopsig %d\n", wait_id, wifexited, wifsignaled, wifstopped, wstopsig);
// update thread registers
if(thread != &dmn_lnx_nil_entity)
{
if (!dmn_lnx_thread_read_reg_block(thread, thread->reg_block)) { Assert(0 && "failed to update thread's registers"); }
}
DMN_EventKind e_kind = DMN_EventKind_Null;
U64 exit_code = max_U64;
U64 address = 0;
DMN_Trap *hit_user_trap = 0;
pid_t new_pid = 0;
B32 is_group_stop = 0;
//- rjf: WIFEXITED(status) -> thread exit
if(wifexited)
{
e_kind = DMN_EventKind_ExitThread;
}
//- rjf: WIFEXITED(status) -> thread exit w/ exit code
else if(wifsignaled)
{
e_kind = DMN_EventKind_ExitThread;
exit_code = WTERMSIG(status);
}
//- rjf: SIGTRAP:PTRACE_EVENT_EXIT
else if(wifstopped && wstopsig == SIGTRAP && ptrace_event_code == PTRACE_EVENT_EXIT)
{
// TODO(rjf): verify
e_kind = DMN_EventKind_ExitThread;
}
//- rjf: SIGTRAP:PTRACE_EVENT_CLONE
else if(wifstopped && (status >> 8) == (SIGTRAP | PTRACE_EVENT_CLONE << 8))
{
if(ptrace(PTRACE_GETEVENTMSG, wait_id, 0, &new_pid) >= 0)
{
e_kind = DMN_EventKind_CreateThread;
}
else { Assert(0 && "failed to get new tid"); }
}
//- rjf: SIGTRAP:PTRACE_EVENT_FORK, or SIGTRAP:PTRACE_EVENT_VFORK
else if(wifstopped && wstopsig == SIGTRAP &&
(ptrace_event_code == PTRACE_EVENT_FORK ||
ptrace_event_code == PTRACE_EVENT_VFORK))
{
}
// group stop
else if(wifstopped && wstopsig == SIGTRAP && status >> 16 == PTRACE_EVENT_STOP)
{
is_group_stop = 1;
}
//- rjf: SIGTRAP
else if(wifstopped && wstopsig == SIGTRAP)
{
// translate signal code to DEMON event kind
siginfo_t siginfo = {0};
if(ptrace(PTRACE_GETSIGINFO, wait_id, 0, &siginfo) < 0) { Assert(0 && "failed to get signal info"); }
switch(siginfo.si_code)
{
case DMN_LNX_SigTrapCode_Brkpt:
{
e_kind = DMN_EventKind_Breakpoint;
}break;
case DMN_LNX_SigTrapCode_Trace:
{
e_kind = DMN_EventKind_SingleStep;
}break;
case DMN_LNX_SigTrapCode_HwBkpt:
{
if(thread->arch == Arch_Null) { }
else if(thread->arch == Arch_x64)
{
REGS_RegBlockX64 *regs_x64 = thread->reg_block;
if(regs_x64->dr6.u64 & X64_DebugStatusFlag_B0)
{
address = regs_x64->dr0.u64;
e_kind = DMN_EventKind_Breakpoint;
}
else if(regs_x64->dr6.u64 & X64_DebugStatusFlag_B1)
{
address = regs_x64->dr1.u64;
e_kind = DMN_EventKind_Breakpoint;
}
else if(regs_x64->dr6.u64 & X64_DebugStatusFlag_B2)
{
address = regs_x64->dr2.u64;
e_kind = DMN_EventKind_Breakpoint;
}
else if(regs_x64->dr6.u64 & X64_DebugStatusFlag_B3)
{
address = regs_x64->dr3.u64;
e_kind = DMN_EventKind_Breakpoint;
}
}
else
{
NotImplemented;
}
}break;
case SI_KERNEL:
{
e_kind = DMN_EventKind_Breakpoint;
}break;
case DMN_LNX_SigTrapCode_Unk: {NotImplemented;}break;
case DMN_LNX_SigTrapCode_Perf: {NotImplemented;}break;
default: {InvalidPath;} break;
}
}
//- rjf: WSTOPSIG(status) is SIGSTOP
else if(wifstopped && wstopsig == SIGSTOP)
{
//
// TODO(rjf): how do we tell the following apart?:
// - SIGSTOP All-Stop
// - SIGSTOP Halt
// - SIGSTOP "User"
//
// we are currently just assuming that, if we've queried a SIGSTOP to halt, then
// the first one that comes back is our "dummy" sigstop. this is likely not
// necessarily true.
//
if(thread->expecting_dummy_sigstop)
{
thread->expecting_dummy_sigstop = 0;
done = 0;
}
else if(dmn_lnx_state->has_halt_injection)
{
e_kind = DMN_EventKind_Halt;
}
else
{
// TODO(rjf): study this case; old notes:
//
// a signal we don't want to mess with (except to record that it
// happened maybe) we should "hand it back"
}
}
//- rjf: WSTOPSIG(status) is an unrecoverable exception (unless user does something to fix state first)
else if(wifstopped)
{
e_kind = DMN_EventKind_Exception;
}
else
{
Assert(0 && "unexpected stop code");
}
dmn_lnx_state->last_event_kind = e_kind;
dmn_lnx_state->last_stop_pid = wait_id;
dmn_lnx_state->last_sig_code = wstopsig;
if(wait_for_group_stop)
{
if(is_group_stop)
{
done = 1;
}
}
else
{
done = 1;
}
if(e_kind == DMN_EventKind_Breakpoint)
{
U64 ip = dmn_lnx_thread_read_ip(thread);
for EachNode(active_trap, DMN_ActiveTrap, active_traps)
{
if(active_trap->trap->vaddr == ip-1)
{
hit_user_trap = active_trap->trap;
break;
}
}
}
// is this a probe trap?
if(e_kind == DMN_EventKind_Breakpoint)
{
// find which probe was triggered
U64 ip = dmn_lnx_thread_read_ip(thread);
DMN_LNX_ProbeType probe_type = DMN_LNX_ProbeType_Null;
for EachIndex(i, ArrayCount(process->probe_vaddrs))
{
if(process->probe_vaddrs[i] == ip-1)
{
probe_type = i;
break;
}
}
if(probe_type == DMN_LNX_ProbeType_InitComplete)
{
U64 name_space_id = 0, rdebug_addr = 0;
DMN_LNX_Probe *probe = process->probes[DMN_LNX_ProbeType_InitComplete];
if(stap_read_arg_u(probe->args.v[0], process->arch, thread->reg_block, dmn_lnx_stap_memory_read, process, &name_space_id))
{
if(stap_read_arg_u(probe->args.v[1], process->arch, thread->reg_block, dmn_lnx_stap_memory_read, process, &rdebug_addr))
{
GNU_RDebugInfo64 rdebug = {0};
if(dmn_lnx_read_r_debug(process->fd, rdebug_addr, process->arch, &rdebug))
{
if(rdebug.r_version == 1)
{
dmn_lnx_process_loaded_modules(arena, evts, process, name_space_id, rdebug.r_map);
}
else { Assert(0 && "unexpected version number"); }
}
else { Assert(0 && "failed to read rdebug"); }
}
else { Assert(0 && "failed to parse second argument"); }
}
else { Assert(0 && "failed to parse first argument"); }
}
else if(probe_type == DMN_LNX_ProbeType_RelocComplete)
{
U64 name_space_id = 0, new_link_map_addr = 0;
DMN_LNX_Probe *probe = process->probes[DMN_LNX_ProbeType_RelocComplete];
if(stap_read_arg_u(probe->args.v[0], process->arch, thread->reg_block, dmn_lnx_stap_memory_read, process, &name_space_id))
{
if(stap_read_arg_u(probe->args.v[2], process->arch, thread->reg_block, dmn_lnx_stap_memory_read, process, &new_link_map_addr))
{
dmn_lnx_process_loaded_modules(arena, evts, process, name_space_id, new_link_map_addr);
}
else { Assert(0 && "failed to parse third argument"); }
}
else { Assert(0 && "failed to parse first argument"); }
}
else if(probe_type == DMN_LNX_ProbeType_UnmapComplete)
{
// read probe's arguments
U64 name_space_id = 0, rdebug_vaddr = 0;
DMN_LNX_Probe *probe = process->probes[DMN_LNX_ProbeType_UnmapComplete];
if(stap_read_arg_u(probe->args.v[0], process->arch, thread->reg_block, dmn_lnx_stap_memory_read, process, &name_space_id))
{
if(stap_read_arg_u(probe->args.v[1], process->arch, thread->reg_block, dmn_lnx_stap_memory_read, process, &rdebug_vaddr))
{
dmn_lnx_process_unloaded_modules(arena, evts, process, name_space_id, rdebug_vaddr);
}
else { Assert(0 && "failed to read second argument"); }
}
else { Assert(0 && "failed to read first argument"); }
}
if(probe_type != DMN_LNX_ProbeType_Null) {break;}
}
// rollback IP on user traps
if(hit_user_trap)
{
U64 ip = dmn_lnx_thread_read_ip(thread);
dmn_lnx_thread_write_ip(thread, ip - 1);
}
switch(e_kind)
{
case DMN_EventKind_COUNT:
case DMN_EventKind_Null: break;
case DMN_EventKind_Error:
case DMN_EventKind_HandshakeComplete:
case DMN_EventKind_LoadModule:
case DMN_EventKind_UnloadModule:
{InvalidPath;}break;
case DMN_EventKind_Trap:
case DMN_EventKind_Memory:
case DMN_EventKind_SetThreadName:
case DMN_EventKind_SetThreadColor:
case DMN_EventKind_SetBreakpoint:
case DMN_EventKind_UnsetBreakpoint:
case DMN_EventKind_SetVAddrRangeNote:
case DMN_EventKind_DebugString:
{
NotImplemented;
}break;
case DMN_EventKind_SingleStep:
{
// clear single step flag
dmn_lnx_set_single_step_flag(thread, 0);
DMN_Event *e = dmn_event_list_push(arena, evts);
e->kind = e_kind;
e->process = dmn_lnx_handle_from_entity(process);
e->thread = dmn_lnx_handle_from_entity(thread);
e->instruction_pointer = dmn_lnx_thread_read_ip(thread);
}break;
case DMN_EventKind_Breakpoint:
{
DMN_Event *e = dmn_event_list_push(arena, evts);
e->kind = e_kind;
e->process = dmn_lnx_handle_from_entity(process);
e->thread = dmn_lnx_handle_from_entity(thread);
e->instruction_pointer = dmn_lnx_thread_read_ip(thread);
}break;
case DMN_EventKind_Halt:
{
DMN_Event *e = dmn_event_list_push(arena, evts);
e->kind = DMN_EventKind_Halt;
e->process = dmn_lnx_handle_from_entity(process);
e->thread = dmn_lnx_handle_from_entity(thread);
}break;
case DMN_EventKind_Exception:
{
// TODO(rjf): possible cases:
// SIGABRT
// SIGFPE
// SIGSEGV
// SIGILL
DMN_Event *e = dmn_event_list_push(arena, evts);
e->kind = DMN_EventKind_Exception;
e->process = dmn_lnx_handle_from_entity(process);
e->thread = dmn_lnx_handle_from_entity(thread);
e->instruction_pointer = dmn_lnx_thread_read_ip(thread);
e->signo = wstopsig;
}break;
case DMN_EventKind_CreateProcess:
{
NotImplemented;
}break;
case DMN_EventKind_ExitProcess:
{
// rjf: generate exit-thread / unload-module events
for(DMN_LNX_Entity *child = process->first; child != &dmn_lnx_nil_entity; child = child->next)
{
switch(child->kind)
{
default:{}break;
case DMN_LNX_EntityKind_Thread:
{
DMN_Event *e = dmn_event_list_push(arena, evts);
e->kind = DMN_EventKind_ExitThread;
e->process = dmn_lnx_handle_from_entity(process);
e->thread = dmn_lnx_handle_from_entity(child);
}break;
case DMN_LNX_EntityKind_Module:
{
DMN_Event *e = dmn_event_list_push(arena, evts);
e->kind = DMN_EventKind_UnloadModule;
e->process = dmn_lnx_handle_from_entity(process);
e->module = dmn_lnx_handle_from_entity(child);
// TODO(rjf): e->string = ...;
}break;
}
}
// rjf: generate exit process event
{
DMN_Event *e = dmn_event_list_push(arena, evts);
e->kind = DMN_EventKind_ExitProcess;
e->process = dmn_lnx_handle_from_entity(process);
e->code = exit_code;
}
// rjf: eliminate entity tree
dmn_lnx_entity_release(process);
}break;
case DMN_EventKind_CreateThread:
{
DMN_LNX_Entity *thread = dmn_lnx_entity_alloc(process, DMN_LNX_EntityKind_Thread);
thread->id = new_pid;
thread->arch = process->arch;
thread->reg_block = push_array(process->arena, U8, regs_block_size_from_arch(process->arch));
dmn_lnx_thread_read_reg_block(thread, thread->reg_block);
DMN_Event *e = dmn_event_list_push(arena, evts);
e->kind = DMN_EventKind_CreateThread;
e->process = dmn_lnx_handle_from_entity(process);
e->thread = dmn_lnx_handle_from_entity(thread);
e->arch = thread->arch;
e->code = thread->id;
}break;
case DMN_EventKind_ExitThread:
{
DMN_Event *e = dmn_event_list_push(arena, evts);
e->kind = DMN_EventKind_ExitThread;
e->process = dmn_lnx_handle_from_entity(process);
e->thread = dmn_lnx_handle_from_entity(thread);
dmn_lnx_entity_release(thread);
}break;
}
}
}
internal DMN_EventList
dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls)
{
Temp scratch = scratch_begin(&arena, 1);
DMN_EventList evts = {0};
////////////////////////////
//- rjf: push any deferred events
//
{
for EachNode(n, DMN_EventNode, dmn_lnx_state->deferred_events.first)
{
DMN_Event *e_src = &n->v;
DMN_Event *e_dst = dmn_event_list_push(arena, &evts);
MemoryCopyStruct(e_dst, e_src);
e_dst->string = str8_copy(arena, e_dst->string);
}
MemoryZeroStruct(&dmn_lnx_state->deferred_events);
arena_clear(dmn_lnx_state->deferred_events_arena);
}
////////////////////////////
//- rjf: no processes, no output events -> not attached
//
if(evts.count == 0 && dmn_lnx_state->entities_base->first == &dmn_lnx_nil_entity)
{
DMN_Event *e = dmn_event_list_push(arena, &evts);
e->kind = DMN_EventKind_Error;
e->error_kind = DMN_ErrorKind_NotAttached;
}
////////////////////////////
//- rjf: determine if we need to wait for new events
//
B32 need_wait_on_events = (evts.count == 0);
////////////////////////////
//- rjf: write all traps into memory
//
DMN_ActiveTrap *active_trap_first = 0, *active_trap_last = 0;
ProfScope("write all traps into memory")
{
HashTable *ht = hash_table_init(scratch.arena, ctrls->traps.trap_count);
for EachNode(n, DMN_TrapChunkNode, ctrls->traps.first)
{
for EachIndex(n_idx, n->count)
{
DMN_Trap *trap = n->v+n_idx;
if(trap->flags == 0)
{
DMN_ActiveTrap *is_set = hash_table_search_u64_raw(ht, trap->vaddr);
if(is_set) { continue; }
U8 swap_byte = 0;
if(dmn_process_read(trap->process, r1u64(trap->vaddr, trap->vaddr+1), &swap_byte) > 0)
{
U8 int3 = 0xCC;
if(dmn_process_write(trap->process, r1u64(trap->vaddr, trap->vaddr+1), &int3))
{
DMN_ActiveTrap *active_trap = push_array(scratch.arena, DMN_ActiveTrap, 1);
active_trap->trap = trap;
active_trap->swap_byte = swap_byte;
SLLQueuePush(active_trap_first, active_trap_last, active_trap);
hash_table_push_u64_raw(scratch.arena, ht, trap->vaddr, active_trap);
} else { Assert(0 && "failed to write trap"); }
} else { Assert(0 && "failed to read original byte"); }
}
}
}
}
////////////////////////////
//- enable single stepping
if(!dmn_handle_match(ctrls->single_step_thread, dmn_handle_zero()))
{
DMN_LNX_Entity *single_step_thread = dmn_lnx_entity_from_handle(ctrls->single_step_thread);
dmn_lnx_set_single_step_flag(single_step_thread, 1);
}
////////////////////////////
//- rjf: gather all threads which we should run
//
DMN_LNX_EntityList run_threads = {0};
if(need_wait_on_events) ProfScope("gather all threads which we should run")
{
//- rjf: scan all processes
for(DMN_LNX_Entity *process = dmn_lnx_state->entities_base->first; process != &dmn_lnx_nil_entity; process = process->next)
{
if(process->kind != DMN_LNX_EntityKind_Process) { continue; }
//- rjf: determine if this process is frozen
B32 process_is_frozen = 0;
if(ctrls->run_entities_are_processes)
{
for EachIndex(idx, ctrls->run_entity_count)
{
if(dmn_handle_match(ctrls->run_entities[idx], dmn_lnx_handle_from_entity(process)))
{
process_is_frozen = 1;
break;
}
}
}
//- rjf: scan all threads in this process
for(DMN_LNX_Entity *thread = process->first; thread != &dmn_lnx_nil_entity; thread = thread->next)
{
if(thread->kind != DMN_LNX_EntityKind_Thread) { continue; }
//- rjf: determine if this thread is frozen
B32 is_frozen = 0;
{
// rjf: single-step? freeze if not the single-step thread.
if(!dmn_handle_match(dmn_handle_zero(), ctrls->single_step_thread))
{
is_frozen = !dmn_handle_match(dmn_lnx_handle_from_entity(thread), ctrls->single_step_thread);
}
// rjf: not single-stepping? determine based on run controls freezing info
else
{
if(ctrls->run_entities_are_processes)
{
is_frozen = process_is_frozen;
}
else
{
for EachIndex(idx, ctrls->run_entity_count)
{
if(dmn_handle_match(ctrls->run_entities[idx], dmn_lnx_handle_from_entity(thread)))
{
is_frozen = 1;
break;
}
}
}
if(ctrls->run_entities_are_unfrozen)
{
is_frozen ^= 1;
}
}
}
//- rjf: add to list
if(!is_frozen)
{
dmn_lnx_entity_list_push(scratch.arena, &run_threads, thread);
}
}
}
}
////////////////////////////
//- rjf: resume all threads we need to run
//
DMN_LNX_EntityList ran_threads = {0};
for EachNode(n, DMN_LNX_EntityNode, run_threads.first)
{
DMN_LNX_Entity *thread = n->v;
// update registers
if(!dmn_lnx_thread_write_reg_block(thread, thread->reg_block)) { Assert(0 && "failed to write thread's registers"); }
// pass signal to the child process
void *sig_code = 0;
if(dmn_lnx_state->last_event_kind == DMN_EventKind_Exception && dmn_lnx_state->last_stop_pid == thread->id)
{
sig_code = (void *)(uintptr_t)dmn_lnx_state->last_sig_code;
}
// resume thread
if (ptrace(PTRACE_CONT, (pid_t)thread->id, 0, (void *)sig_code) < 0) { Assert(0 && "failed to resume a thread"); }
dmn_lnx_entity_list_push(scratch.arena, &ran_threads, thread);
}
////////////////////////////
//- rjf: loop: wait for next stop, produce debug events
//
if(need_wait_on_events)
{
dmn_lnx_wait_for_events(arena, &evts, -1, 0, active_trap_first);
}
////////////////////////////
//- rjf: stop all threads
//
{
B32 was_interrupt_issued = 0;
for EachNode(n, DMN_LNX_EntityNode, ran_threads.first)
{
if(n->v->id != dmn_lnx_state->last_stop_pid)
{
if(ptrace(PTRACE_INTERRUPT, n->v->id, 0, 0) >= 0)
{
was_interrupt_issued = 1;
}
else { Assert(0 && "failed to interrupt thread"); }
}
}
if(was_interrupt_issued)
{
dmn_lnx_wait_for_events(arena, &evts, -1, 1, active_trap_first);
}
}
// update registers
for EachNode(n, DMN_LNX_EntityNode, ran_threads.first)
{
dmn_lnx_thread_read_reg_block(n->v, n->v->reg_block);
}
//////////////////////////
//- rjf: restore original memory at trap locations
//
ProfScope("restore original memory at trap locations")
{
for EachNode(active_trap, DMN_ActiveTrap, active_trap_first)
{
if(!dmn_process_write_struct(active_trap->trap->process, active_trap->trap->vaddr, &active_trap->swap_byte))
{
Assert(0 && "failed to restore original memory");
}
}
}
scratch_end(scratch);
return evts;
}
////////////////////////////////
//~ rjf: @dmn_os_hooks Halting (Implemented Per-OS)
internal void
dmn_halt(U64 code, U64 user_data)
{
if(!dmn_lnx_state->has_halt_injection)
{
DMN_LNX_Entity *process = dmn_lnx_state->entities_base->first;
if(process != &dmn_lnx_nil_entity)
{
union sigval sv = {0};
if(sigqueue(process->id, SIGSTOP, sv) != -1)
{
dmn_lnx_state->has_halt_injection = 1;
dmn_lnx_state->halt_code = code;
dmn_lnx_state->halt_user_data = user_data;
}
}
}
}
////////////////////////////////
//~ rjf: @dmn_os_hooks Introspection Functions (Implemented Per-OS)
//- rjf: non-blocking-control-thread access barriers
internal B32
dmn_access_open(void)
{
B32 result = 0;
if(dmn_lnx_ctrl_thread)
{
result = 1;
}
else
{
mutex_take(dmn_lnx_state->access_mutex);
result = !dmn_lnx_state->access_run_state;
}
return result;
}
internal void
dmn_access_close(void)
{
if(!dmn_lnx_ctrl_thread)
{
mutex_drop(dmn_lnx_state->access_mutex);
}
}
//- rjf: processes
internal U64
dmn_process_memory_reserve(DMN_Handle process, U64 vaddr, U64 size)
{
return 0;
}
internal void
dmn_process_memory_commit(DMN_Handle process, U64 vaddr, U64 size)
{
}
internal void
dmn_process_memory_decommit(DMN_Handle process, U64 vaddr, U64 size)
{
}
internal void
dmn_process_memory_release(DMN_Handle process, U64 vaddr, U64 size)
{
}
internal void
dmn_process_memory_protect(DMN_Handle process, U64 vaddr, U64 size, OS_AccessFlags flags)
{
}
internal U64
dmn_process_read(DMN_Handle process, Rng1U64 range, void *dst)
{
DMN_LNX_Entity *entity = dmn_lnx_entity_from_handle(process);
U64 result = dmn_lnx_read(entity->fd, range, dst);
return result;
}
internal B32
dmn_process_write(DMN_Handle process, Rng1U64 range, void *src)
{
DMN_LNX_Entity *entity = dmn_lnx_entity_from_handle(process);
B32 result = dmn_lnx_write(entity->fd, range, src);
return result;
}
//- rjf: threads
internal Arch
dmn_arch_from_thread(DMN_Handle handle)
{
DMN_LNX_Entity *thread = dmn_lnx_entity_from_handle(handle);
return thread->arch;
}
internal U64
dmn_stack_base_vaddr_from_thread(DMN_Handle handle)
{
return 0;
}
internal U64
dmn_tls_root_vaddr_from_thread(DMN_Handle handle)
{
return 0;
}
internal B32
dmn_thread_read_reg_block(DMN_Handle handle, void *reg_block)
{
B32 result = 0;
DMN_AccessScope
{
DMN_LNX_Entity *thread = dmn_lnx_entity_from_handle(handle);
U64 reg_block_size = regs_block_size_from_arch(thread->arch);
if(thread == &dmn_lnx_nil_entity)
{
MemoryZero(reg_block, reg_block_size);
}
else
{
MemoryCopy(reg_block, thread->reg_block, reg_block_size);
}
result = 1;
}
return result;
}
internal B32
dmn_thread_write_reg_block(DMN_Handle handle, void *reg_block)
{
B32 result = 0;
DMN_AccessScope
{
DMN_LNX_Entity *thread = dmn_lnx_entity_from_handle(handle);
U64 reg_block_size = regs_block_size_from_arch(thread->arch);
if(thread == &dmn_lnx_nil_entity)
{
MemoryZero(reg_block, reg_block_size);
}
else
{
MemoryCopy(thread->reg_block, reg_block, reg_block_size);
}
result = 1;
}
return result;
}
//- rjf: system process listing
internal void
dmn_process_iter_begin(DMN_ProcessIter *iter)
{
DIR *dir = opendir("/proc");
MemoryZeroStruct(iter);
iter->v[0] = IntFromPtr(dir);
}
internal B32
dmn_process_iter_next(Arena *arena, DMN_ProcessIter *iter, DMN_ProcessInfo *info_out)
{
// rjf: scan for the next process ID in the directory
B32 got_pid = 0;
String8 pid_string = {0};
{
DIR *dir = (DIR*)PtrFromInt(iter->v[0]);
if(dir != 0 && iter->v[1] == 0)
{
for(;;)
{
// rjf: get next entry
struct dirent *d = readdir(dir);
if(d == 0)
{
break;
}
// rjf: check file name is integer
String8 file_name = str8_cstring((char*)d->d_name);
B32 is_integer = str8_is_integer(file_name, 10);
// rjf: break on integers (which represent processes)
if(is_integer)
{
got_pid = 1;
pid_string = file_name;
break;
}
}
}
}
// rjf: if we found a process id, map id => info
B32 result = 0;
if(got_pid)
{
pid_t pid = u64_from_str8(pid_string, 10);
String8 name = dmn_lnx_exe_path_from_pid(arena, pid);
if(name.size == 0)
{
name = str8_lit("(unknown process)");
}
info_out->name = name;
info_out->pid = pid;
result = 1;
}
return result;
}
internal void
dmn_process_iter_end(DMN_ProcessIter *iter)
{
DIR *dir = (DIR*)PtrFromInt(iter->v[0]);
if(dir != 0)
{
closedir(dir);
}
MemoryZeroStruct(iter);
}