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

3146 lines
101 KiB
C

// Copyright (c) Epic Games Tools
// Licensed under the MIT license (https://opensource.org/license/mit/)
////////////////////////////////
//~ Includes
#include <sys/ptrace.h>
#include <sys/uio.h>
#include <elf.h>
////////////////////////////////
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 = OS_LNX_RETRY_ON_EINTR(pread(memory_fd, temp, sizeof(temp), cursor));
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 = OS_LNX_RETRY_ON_EINTR(pread(memory_fd, (U8 *)dst + cursor, to_read, range.min + cursor));
if(actual_read < 0) { break; }
if(actual_read == 0) { break; }
cursor += actual_read;
}
return cursor;
}
internal B32
dmn_lnx_write(int memory_fd, Rng1U64 range, void *src)
{
size_t cursor = 0, size = dim_1u64(range);
while(cursor < size)
{
size_t to_write = size - cursor;
ssize_t actual_write = OS_LNX_RETRY_ON_EINTR(pwrite(memory_fd, (U8 *)src + cursor, to_write, range.min + cursor));
if(actual_write <= 0) { break; }
cursor += actual_write;
}
B32 is_written = (cursor == size);
return is_written;
}
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(OS_LNX_RETRY_ON_EINTR(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);
OS_LNX_RETRY_ON_EINTR(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);
}
internal
MACHINE_OP_MEM_READ(dmn_lnx_machine_op_mem_read)
{
U64 read_size = dmn_lnx_read(*(int *)ud, r1u64(addr, addr + buffer_size), buffer);
return read_size == buffer_size ? MachineOpResult_Ok : MachineOpResult_Fail;
}
internal int
dmn_lnx_ptrace_seize(pid_t pid)
{
// TODO: PTRACE_O_TRACEVFORK | PTRACE_O_TRACEVFORKDONE | PTRACE_O_TRACEFORK
return OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_SEIZE, pid, 0, PTRACE_O_TRACEEXEC | PTRACE_O_EXITKILL | PTRACE_O_TRACECLONE));
}
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 String8
dmn_lnx_dl_path_from_pid(Arena *arena, pid_t pid, U64 auxv_base)
{
Temp scratch = scratch_begin(&arena, 1);
String8 dl_path = {0};
int maps_fd = open((char *)str8f(scratch.arena, "/proc/%d/maps", pid).str, O_RDONLY);
if(maps_fd != -1)
{
// read entire /proc/pid/maps
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);
// split map file on lines
String8List lines = str8_split_by_string_chars(scratch.arena, str8(maps_ptr, maps_size), str8_lit("\n"), 0);
// scan each line until a virtual mapping whose low part matches the DL base address is found
for EachNode(n, String8Node, lines.first)
{
String8 line = n->string;
// split the string while respecting escape sequences
String8List parts = {0};
for EachIndex(cursor, line.size)
{
if(parts.node_count > 5) { break; }
if(line.str[cursor] == ' ') { continue; }
// scan forward to the closing delimiter
U64 token_start = cursor;
for(; cursor < line.size; cursor += 1)
{
if(line.str[cursor] == '\\')
{
cursor += 1;
continue;
}
if(line.str[cursor] == ' ') { break; }
}
// push sub-string to the list
str8_list_push(scratch.arena, &parts, str8_substr(line, r1u64(token_start, cursor)));
}
// was line parsed correctly?
if(parts.node_count < 5) { Assert(0 && "failed to parse map line"); continue; }
// parse map virtual range
String8List vaddr_list = str8_split_by_string_chars(scratch.arena, parts.first->string, str8_lit("-"), 0);
if(vaddr_list.node_count != 2) { Assert(0 && "failed to parse virtual range portion of map line"); continue; }
// does the low part match DL base address?
U64 lo_vaddr = u64_from_str8(vaddr_list.first->string, 16);
if(lo_vaddr == auxv_base)
{
dl_path = parts.node_count == 5 ? str8_zero() : parts.last->string;
dl_path = push_str8_copy(arena, dl_path);
break;
}
}
close(maps_fd);
}
else { Assert(0 && "failed to open DL fd"); }
scratch_end(scratch);
Assert(dl_path.size);
return dl_path;
}
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 = OS_LNX_RETRY_ON_EINTR(open((char *)exe_path.str, O_RDONLY));
if(exe_fd != -1)
{
is_read = elf_read_ehdr(dmn_lnx_machine_op_mem_read, &exe_fd, 0, &exe);
close(exe_fd);
}
}
Assert(is_read);
scratch_end(scratch);
return exe;
}
internal DMN_LNX_Auxv
dmn_lnx_auxv_from_pid(pid_t pid, ELF_Class elf_class)
{
Temp scratch = scratch_begin(0, 0);
DMN_LNX_Auxv result = {0};
// rjf: open aux data
String8 auxv_path = push_str8f(scratch.arena, "/proc/%d/auxv", pid);
int auxv_fd = OS_LNX_RETRY_ON_EINTR(open((char*)auxv_path.str, O_RDONLY));
// 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_Thread *
dmn_lnx_thread_from_pid(pid_t tid)
{
return hash_table_search_u64_raw(dmn_lnx_state->tid_ht, tid);
}
internal DMN_LNX_Process *
dmn_lnx_process_from_pid(pid_t pid)
{
return hash_table_search_u64_raw(dmn_lnx_state->pid_ht, pid);
}
internal Rng1U64
dmn_lnx_compute_image_vrange(int memory_fd, ELF_Class elf_class, U64 rebase, U64 e_phaddr, U64 e_phentsize, U64 e_phnum)
{
Rng1U64 result = { .min = max_U64 };
for(U64 ph_cursor = e_phaddr, ph_opl = (e_phaddr + e_phentsize * e_phnum); ph_cursor < ph_opl; ph_cursor += e_phentsize)
{
ELF_Phdr64 phdr = {0};
if(elf_read_phdr(dmn_lnx_machine_op_mem_read, &memory_fd, ph_cursor, elf_class, &phdr) != MachineOpResult_Ok)
{
Assert(0 && "unable to read a program header");
}
if(phdr.p_type == ELF_PType_Load)
{
U64 min = rebase + phdr.p_vaddr;
U64 max = rebase + phdr.p_vaddr + phdr.p_memsz;
result.min = Min(result.min, min);
result.max = Max(result.max, max);
}
}
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(elf_read_dyn(dmn_lnx_machine_op_mem_read, &memory_fd, dynamic_cursor, elf_class, &dyn) != MachineOpResult_Ok) { 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_find_dynamic_phdr(int memory_fd, ELF_Class elf_class, U64 rebase, U64 e_phaddr, U64 e_phentsize, U64 e_phnum)
{
U64 result = max_U64;
for(U64 ph_cursor = e_phaddr, ph_opl = (e_phaddr + e_phentsize * e_phnum); ph_cursor < ph_opl; ph_cursor += e_phentsize)
{
ELF_Phdr64 phdr = {0};
if(elf_read_phdr(dmn_lnx_machine_op_mem_read, &memory_fd, ph_cursor, elf_class, &phdr) != MachineOpResult_Ok)
{
Assert(0 && "unable to read a program header");
}
if(phdr.p_type == ELF_PType_Dynamic)
{
result = rebase + phdr.p_vaddr;
break;
}
}
return result;
}
internal U64
dmn_lnx_rdebug_vaddr_from_memory(int memory_fd, U64 loader_vbase, B32 is_rebased)
{
Temp scratch = scratch_begin(0, 0);
U64 rdebug_vaddr = 0;
// load DL's header
ELF_Hdr64 ehdr = {0};
if(elf_read_ehdr(dmn_lnx_machine_op_mem_read, &memory_fd, loader_vbase, &ehdr) != MachineOpResult_Ok) { 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 phdr_vaddr = loader_vbase + ehdr.e_phoff;
U64 dynamic_vaddr = dmn_lnx_find_dynamic_phdr(memory_fd, elf_class, rebase, phdr_vaddr, ehdr.e_phentsize, ehdr.e_phentsize);
// extract necessary info out of dynamic program header
U64 dynamic_info_rebase = is_rebased ? 0 : rebase;
DMN_LNX_DynamicInfo dynamic_info = dmn_lnx_dynamic_info_from_memory(memory_fd, elf_class, dynamic_info_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(elf_read_symbol(dmn_lnx_machine_op_mem_read, &memory_fd, dynamic_info.symtab_vaddr + symbol_idx * dynamic_info.symtab_entry_size, elf_class, &symbol) != MachineOpResult_Ok)
{
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;
}
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(elf_read_ehdr(dmn_lnx_machine_op_mem_read, &fd, offset, &ehdr) != MachineOpResult_Ok) { goto exit; }
U64 strtab_shdr_offset = offset + ehdr.e_shoff + ehdr.e_shstrndx * ehdr.e_shentsize;
ELF_Shdr64 strtab_shdr = {0};
if(elf_read_shdr(dmn_lnx_machine_op_mem_read, &fd, strtab_shdr_offset, ehdr.e_ident[ELF_Identifier_Class], &strtab_shdr) != MachineOpResult_Ok) { 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(elf_read_shdr(dmn_lnx_machine_op_mem_read, &fd, shdr_off, ehdr.e_ident[ELF_Identifier_Class], &shdr) != MachineOpResult_Ok) { 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;
}
internal
STAP_MEMORY_READ(dmn_lnx_stap_memory_read)
{
DMN_LNX_Process *process = raw_ctx;
U64 bytes_read = dmn_lnx_read(process->fd, r1u64(addr, addr + read_size), buffer);
return bytes_read == read_size;
}
internal DMN_LNX_Entity *
dmn_lnx_entity_alloc(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;
entity->gen += 1;
entity->kind = kind;
return entity;
}
internal DMN_LNX_Process *
dmn_lnx_process_alloc(pid_t pid, DMN_LNX_ProcessState state, DMN_LNX_Process *parent_process, B32 debug_subprocesses, B32 is_cow)
{
Temp scratch = scratch_begin(0, 0);
DMN_LNX_Process *process = &dmn_lnx_entity_alloc(DMN_LNX_EntityKind_Process)->process;
process->pid = pid;
process->fd = open((char*)str8f(scratch.arena, "/proc/%d/mem", pid).str, O_RDWR);
process->state = state;
process->debug_subprocesses = debug_subprocesses;
process->is_cow = is_cow;
process->parent_process = parent_process;
// update pending process tracker
if(state != DMN_LNX_ProcessState_Normal)
{
dmn_lnx_state->process_pending_creation += 1;
}
// add process to the list
DLLPushBack(dmn_lnx_state->first_process, dmn_lnx_state->last_process, process);
dmn_lnx_state->process_count += 1;
// push pid -> DMN_LNX_Process mapping
hash_table_push_u64_raw(dmn_lnx_state->arena, dmn_lnx_state->pid_ht, pid, process);
scratch_end(scratch);
return process;
}
internal DMN_LNX_ProcessCtx *
dmn_lnx_process_ctx_alloc(DMN_LNX_Process *process, B32 is_rebased)
{
DMN_LNX_ProcessCtx *ctx = &dmn_lnx_entity_alloc(DMN_LNX_EntityKind_ProcessCtx)->process_ctx;
ELF_Hdr64 exe_ehdr = dmn_lnx_ehdr_from_pid(process->pid);
DMN_LNX_Auxv auxv = dmn_lnx_auxv_from_pid(process->pid, exe_ehdr.e_ident[ELF_Identifier_Class]);
Arch arch = arch_from_elf_machine(exe_ehdr.e_machine);
U64 rdebug_vaddr = dmn_lnx_rdebug_vaddr_from_memory(process->fd, auxv.base, is_rebased);
U64 base_vaddr = (auxv.phdr & ~(auxv.pagesz-1));
U64 rebase = exe_ehdr.e_type == ELF_Type_Dyn ? base_vaddr : 0;
Rng1U64 image_vrange = dmn_lnx_compute_image_vrange(process->fd, exe_ehdr.e_ident[ELF_Identifier_Class], rebase, auxv.phdr, auxv.phent, auxv.phnum);
Arena *ctx_arena = arena_alloc();
ELF_Class dl_class;
{
ELF_Hdr64 ehdr = {0};
if(elf_read_ehdr(dmn_lnx_machine_op_mem_read, &process->fd, auxv.base, &ehdr) != MachineOpResult_Ok) { Assert(0 && "failed to read interp's header"); }
dl_class = ehdr.e_ident[ELF_Identifier_Class];
}
// query xsave layout
U64 xcr0 = 0;
U64 xsave_size = 0;
X64_XSaveLayout xsave_layout = {0};
if(arch == Arch_x64)
{
X64_XSave xsave = {0};
if(OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_GETREGSET, process->pid, (void *)NT_X86_XSTATE, &(struct iovec){.iov_base = &xsave, .iov_len = sizeof(xsave) }) >= 0))
{
// Linux stores xcr0 bits in fxstate padding,
// see https://github.com/torvalds/linux/blob/6548d364a3e850326831799d7e3ea2d7bb97ba08/arch/x86/include/asm/user.h#L25
xcr0 = *(U64 *)((U8 *)&xsave + 464);
xsave_size = x64_get_xsave_size();
xsave_layout = x64_get_xsave_layout(xcr0);
}
else { Assert(0 && "failed to get xstate"); }
}
// gather probes
DMN_LNX_Probe **known_probes = push_array(ctx_arena, DMN_LNX_Probe *, DMN_LNX_ProbeType_Count);
{
Temp scratch = scratch_begin(0, 0);
String8 dl_path = dmn_lnx_dl_path_from_pid(scratch.arena, process->pid, auxv.base);
int dl_fd = open((char *)dl_path.str, O_RDONLY);
DMN_LNX_ProbeList probes = {0};
if(dl_fd >= 0)
{
probes = dmn_lnx_read_probes(ctx_arena, dl_fd, 0, auxv.base);
close(dl_fd);
}
for EachNode(n, DMN_LNX_ProbeNode, probes.first)
{
DMN_LNX_Probe *p = &n->v;
if(str8_match(p->provider, str8_lit("rtld"), 0))
{
#define X(_N,_A,_S) if(str8_match(p->name, str8_lit(_S), 0)) { AssertAlways(p->args.count == _A); known_probes[DMN_LNX_ProbeType_##_N] = p; continue ; }
DMN_LNX_Probe_XList
#undef X
}
}
scratch_end(scratch);
}
ctx = &dmn_lnx_entity_alloc(DMN_LNX_EntityKind_ProcessCtx)->process_ctx;
ctx->arena = arena_alloc();
ctx->arch = arch;
ctx->rdebug_vaddr = rdebug_vaddr;
ctx->dl_class = dl_class;
ctx->loaded_modules_ht = hash_table_init(ctx_arena, 0x1000);
ctx->probes = known_probes;
ctx->xcr0 = xcr0;
ctx->xsave_size = Max(xsave_size, sizeof(X64_XSave));
ctx->xsave_layout = xsave_layout;
// create main module
DMN_LNX_Module *main_module = dmn_lnx_module_alloc(ctx, process->fd, base_vaddr, auxv.execfn, 1, 1);
// glibc has a shortcut mapping for the main module
hash_table_push_u64_raw(ctx->arena, ctx->loaded_modules_ht, 0, main_module);
return ctx;
}
internal DMN_LNX_Thread *
dmn_lnx_thread_alloc(DMN_LNX_Process *process, DMN_LNX_ThreadState thread_state, pid_t tid)
{
void *reg_block;
if(process->ctx->free_reg_blocks.node_count)
{
String8Node *n = str8_list_pop_front(&process->ctx->free_reg_blocks);
reg_block = n->string.str;
str8_list_push_node(&process->ctx->free_reg_block_nodes, n);
}
else
{
U64 reg_block_size = regs_block_size_from_arch(process->ctx->arch);
reg_block = push_array(process->ctx->arena, U8, reg_block_size);
}
DMN_LNX_Thread *thread = &dmn_lnx_entity_alloc(DMN_LNX_EntityKind_Thread)->thread;
thread->tid = tid;
thread->state = thread_state;
thread->process = process;
thread->reg_block = reg_block;
if(thread_state == DMN_LNX_ThreadState_Stopped)
{
thread->is_reg_block_dirty = !dmn_lnx_thread_read_reg_block(thread);
}
// add thread to the list
DLLPushBack(process->first_thread, process->last_thread, thread);
process->thread_count += 1;
// push tid -> thread mapping
hash_table_push_u64_raw(dmn_lnx_state->arena, dmn_lnx_state->tid_ht, thread->tid, thread);
// update global thread counter
if(thread_state == DMN_LNX_ThreadState_PendingCreation)
{
dmn_lnx_state->threads_pending_creation += 1;
}
return thread;
}
internal DMN_LNX_Module *
dmn_lnx_module_alloc(DMN_LNX_ProcessCtx *ctx, int memory_fd, U64 base_vaddr, U64 name_vaddr, U64 name_space_id, B32 is_main)
{
DMN_LNX_Module *module = hash_table_search_u64_raw(ctx->loaded_modules_ht, base_vaddr);
if(module) { goto exit; }
// parse out module's ELF header
ELF_Hdr64 module_ehdr = {0};
if(elf_read_ehdr(dmn_lnx_machine_op_mem_read, &memory_fd, base_vaddr, &module_ehdr) != MachineOpResult_Ok) { goto exit; }
// gather info about module
U64 module_rebase = module_ehdr.e_type == ELF_Type_Dyn ? base_vaddr : 0;
U64 module_phdr_vaddr = module_rebase + module_ehdr.e_phoff;
Rng1U64 module_vrange = dmn_lnx_compute_image_vrange(memory_fd, module_ehdr.e_ident[ELF_Identifier_Class], module_rebase, module_phdr_vaddr, module_ehdr.e_phentsize, module_ehdr.e_phnum);
// read TLS index and TLS offset
U64 tls_index = max_U64;
U64 tls_offset = max_U64;
if(is_main)
{
tls_index = 1;
tls_offset = 0;
}
else
{
if(dmn_lnx_state->is_tls_detected)
{
Rng1U64 tls_modid_range = r1u64(dmn_lnx_state->tls_modid_desc.offset, dmn_lnx_state->tls_modid_desc.offset + dmn_lnx_state->tls_modid_desc.bit_size / 8);
Rng1U64 tls_offset_range = r1u64(dmn_lnx_state->tls_offset_desc.offset, dmn_lnx_state->tls_offset_desc.offset + dmn_lnx_state->tls_offset_desc.bit_size / 8);
tls_modid_range = shift_1u64(tls_modid_range, base_vaddr);
tls_offset_range = shift_1u64(tls_offset_range, base_vaddr);
if(!dmn_lnx_read(memory_fd, tls_modid_range, &tls_index)) { Assert(0 && "failed to read TLS index"); }
if(!dmn_lnx_read(memory_fd, tls_offset_range, &tls_offset)) { Assert(0 && "failed to read TLS offset"); }
}
}
module = &dmn_lnx_entity_alloc(DMN_LNX_EntityKind_Module)->module;
module->base_vaddr = base_vaddr;
module->name_vaddr = name_vaddr;
module->name_space_id = name_space_id;
module->size = dim_1u64(module_vrange);
module->phvaddr = base_vaddr + module_ehdr.e_phoff;
module->phcount = module_ehdr.e_phnum;
module->phentsize = module_ehdr.e_phentsize;
module->tls_index = tls_index;
module->tls_offset = tls_offset;
module->is_main = is_main;
// add module to the list
DLLPushBack(ctx->first_module, ctx->last_module, module);
ctx->module_count += 1;
// push base address -> module mapping
hash_table_push_u64_raw(ctx->arena, ctx->loaded_modules_ht, base_vaddr, module);
exit:;
return module;
}
internal void
dmn_lnx_entity_release(DMN_LNX_Entity *entity)
{
U32 gen = entity->gen + 1;
MemoryZeroStruct(entity);
entity->gen = gen;
SLLStackPush(dmn_lnx_state->free_entity, entity);
}
internal void
dmn_lnx_process_release(DMN_LNX_Process *process)
{
// update global state
AssertAlways(dmn_lnx_state->process_count > 0);
DLLRemove(dmn_lnx_state->first_process, dmn_lnx_state->last_process, process);
dmn_lnx_state->process_count -= 1;
// update pending process tracker
if(process->state != DMN_LNX_ProcessState_Normal)
{
Assert(dmn_lnx_state->process_pending_creation > 0);
dmn_lnx_state->process_pending_creation -= 1;
}
// close memory handle
if(close(process->fd) < 0) { Assert(0 && "failed to close memory descriptor"); }
// remove pid mapping
hash_table_purge_u64(dmn_lnx_state->pid_ht, process->pid);
// release the context
if(process->ctx)
{
dmn_lnx_process_ctx_release(process->ctx);
}
// release process entity
dmn_lnx_entity_release((DMN_LNX_Entity *)process);
}
internal void
dmn_lnx_process_ctx_release(DMN_LNX_ProcessCtx *ctx)
{
Assert(ctx->ref_count > 0);
ctx->ref_count -= 1;
if(ctx->ref_count == 0)
{
arena_release(ctx->arena);
dmn_lnx_entity_release((DMN_LNX_Entity *)ctx);
}
}
internal void
dmn_lnx_thread_release(DMN_LNX_Thread *thread)
{
DMN_LNX_Process *process = thread->process;
// purge tid mapping
hash_table_purge_u64(dmn_lnx_state->tid_ht, thread->tid);
// update global thread counter
if(thread->state == DMN_LNX_ThreadState_PendingCreation)
{
AssertAlways(dmn_lnx_state->threads_pending_creation > 0);
dmn_lnx_state->threads_pending_creation -= 1;
}
// remove thread from the list
Assert(process->thread_count > 0);
DLLRemove(process->first_thread, process->last_thread, thread);
process->thread_count -= 1;
// push reg block to the free list
String8Node *reg_block_node;
if(process->ctx->free_reg_block_nodes.node_count)
{
reg_block_node = str8_list_pop_front(&process->ctx->free_reg_block_nodes);
}
else
{
reg_block_node = push_array(process->ctx->arena, String8Node, 1);
}
reg_block_node->string = str8(thread->reg_block, 0);
str8_list_push_node(&process->ctx->free_reg_blocks, reg_block_node);
dmn_lnx_entity_release((DMN_LNX_Entity *)thread);
}
internal void
dmn_lnx_module_release(DMN_LNX_ProcessCtx *ctx, DMN_LNX_Module *module)
{
// remove module from the list
Assert(ctx->module_count > 0);
DLLRemove(ctx->first_module, ctx->last_module, module);
ctx->module_count -= 1;
// purge base addr -> module mapping
hash_table_purge_u64(ctx->loaded_modules_ht, module->base_vaddr);
dmn_lnx_entity_release((DMN_LNX_Entity *)module);
}
internal DMN_LNX_ProcessCtx *
dmn_lnx_process_ctx_clone(DMN_LNX_Process *new_owner, DMN_LNX_ProcessCtx *ctx)
{
DMN_LNX_ProcessCtx *result = &dmn_lnx_entity_alloc(DMN_LNX_EntityKind_ProcessCtx)->process_ctx;
result->arena = arena_alloc();
result->arch = ctx->arch;
result->rdebug_vaddr = ctx->rdebug_vaddr;
result->dl_class = ctx->dl_class;
result->loaded_modules_ht = hash_table_init(result->arena, ctx->loaded_modules_ht->cap);
result->xcr0 = ctx->xcr0;
result->xsave_size = ctx->xsave_size;
result->xsave_layout = ctx->xsave_layout;
// clone probes
result->probes = push_array(result->arena, DMN_LNX_Probe *, DMN_LNX_ProbeType_Count);
for EachIndex(probe_idx, DMN_LNX_ProbeType_Count)
{
DMN_LNX_Probe *dst = result->probes[probe_idx];
DMN_LNX_Probe *src = ctx->probes[probe_idx];
dst->provider = str8_copy(result->arena, src->provider);
dst->name = str8_copy(result->arena, src->name);
dst->args_string = str8_copy(result->arena, src->args_string);
dst->args = stap_arg_array_copy(result->arena, src->args);
dst->pc = src->pc;
dst->semaphore = src->semaphore;
}
// clone probe traps
for EachNode(src, DMN_ActiveTrap, ctx->first_probe_trap)
{
DMN_Trap *src_trap = src->trap;
DMN_Trap *dst_trap = push_array(result->arena, DMN_Trap, 1);
dst_trap->process = dmn_lnx_handle_from_process(new_owner);
dst_trap->vaddr = src_trap->vaddr;
dst_trap->id = src_trap->id;
dst_trap->flags = src_trap->flags;
dst_trap->size = src_trap->size;
DMN_ActiveTrap *dst = push_array(result->arena, DMN_ActiveTrap, 1);
dst->trap = dst_trap;
dst->swap_bytes = str8_copy(result->arena, src->swap_bytes);
SLLQueuePush(result->first_probe_trap, result->last_probe_trap, dst);
}
// clone modules
for EachNode(module, DMN_LNX_Module, ctx->first_module)
{
dmn_lnx_module_clone(result, module);
}
return result;
}
internal DMN_LNX_Module *
dmn_lnx_module_clone(DMN_LNX_ProcessCtx *process_ctx, DMN_LNX_Module *module)
{
DMN_LNX_Module *result = &dmn_lnx_entity_alloc(DMN_LNX_EntityKind_Module)->module;
*result = *module;
result->next = result->prev = 0;
// clone base addr mapping
hash_table_push_u64_raw(process_ctx->arena, process_ctx->loaded_modules_ht, result->base_vaddr, result);
// push module to the list
DLLPushBack(process_ctx->first_module, process_ctx->last_module, module);
process_ctx->module_count += 1;
return result;
}
internal DMN_Handle
dmn_lnx_handle_from_entity(DMN_LNX_Entity *entity)
{
DMN_Handle handle = {0};
U64 index = IntFromPtr(entity - dmn_lnx_state->entities_base);
if(index <= max_U32)
{
handle.u32[0] = index;
handle.u32[1] = entity->gen;
}
else
{
Assert(0 && "failed to make a handle for the entity");
}
return handle;
}
internal DMN_Handle
dmn_lnx_handle_from_process(DMN_LNX_Process *process)
{
return dmn_lnx_handle_from_entity((DMN_LNX_Entity *)process);
}
internal DMN_Handle
dmn_lnx_handle_from_process_ctx(DMN_LNX_ProcessCtx *process_ctx)
{
return dmn_lnx_handle_from_entity((DMN_LNX_Entity *)process_ctx);
}
internal DMN_Handle
dmn_lnx_handle_from_thread(DMN_LNX_Thread *thread)
{
return dmn_lnx_handle_from_entity((DMN_LNX_Entity *)thread);
}
internal DMN_Handle
dmn_lnx_handle_from_module(DMN_LNX_Module *module)
{
return dmn_lnx_handle_from_entity((DMN_LNX_Entity *)module);
}
internal DMN_LNX_Entity *
dmn_lnx_entity_from_handle(DMN_Handle handle, DMN_LNX_EntityKind expected_kind)
{
DMN_LNX_Entity *result = 0;
U32 index = handle.u32[0];
U32 gen = handle.u32[1];
if(index < dmn_lnx_state->entities_count && dmn_lnx_state->entities_base[index].gen == gen)
{
if(dmn_lnx_state->entities_base[index].kind == expected_kind)
{
result = &dmn_lnx_state->entities_base[index];
}
}
return result;
}
internal DMN_LNX_Process *
dmn_lnx_process_from_handle(DMN_Handle process_handle)
{
return (DMN_LNX_Process *)dmn_lnx_entity_from_handle(process_handle, DMN_LNX_EntityKind_Process);
}
internal DMN_LNX_ProcessCtx *
dmn_lnx_process_ctx_from_handle(DMN_Handle process_ctx_handle)
{
return (DMN_LNX_ProcessCtx *)dmn_lnx_entity_from_handle(process_ctx_handle, DMN_LNX_EntityKind_ProcessCtx);
}
internal DMN_LNX_Thread *
dmn_lnx_thread_from_handle(DMN_Handle thread_handle)
{
return (DMN_LNX_Thread *)dmn_lnx_entity_from_handle(thread_handle, DMN_LNX_EntityKind_Thread);
}
internal DMN_LNX_Module *
dmn_lnx_module_from_handle(DMN_Handle module_handle)
{
return (DMN_LNX_Module *)dmn_lnx_entity_from_handle(module_handle, DMN_LNX_EntityKind_Module);
}
internal void
dmn_lnx_process_trap_probes(DMN_LNX_Process *process)
{
for EachIndex(i, DMN_LNX_ProbeType_Count)
{
if(process->ctx->probes[i] == 0) { continue; }
DMN_Trap *trap = push_array(process->ctx->arena, DMN_Trap, 1);
trap->process = dmn_lnx_handle_from_process(process);
trap->vaddr = process->ctx->probes[i]->pc;
trap->id = i;
DMN_ActiveTrap *active_trap = dmn_set_trap(process->ctx->arena, trap);
SLLQueuePush(process->ctx->first_probe_trap, process->ctx->last_probe_trap, active_trap);
if(BUILD_DEBUG && process->ctx->arch == Arch_x64)
{
Assert(active_trap->swap_bytes.size == 1 && active_trap->swap_bytes.str[0] == 0x90);
}
}
}
internal void
dmn_lnx_process_untrap_probes(DMN_LNX_Process *process)
{
NotImplemented;
}
internal U64
dmn_lnx_thread_read_ip(DMN_LNX_Thread *thread)
{
return regs_rip_from_arch_block(thread->process->ctx->arch, thread->reg_block);
}
internal U64
dmn_lnx_thread_read_sp(DMN_LNX_Thread *thread)
{
return regs_rsp_from_arch_block(thread->process->ctx->arch, thread->reg_block);
}
internal void
dmn_lnx_thread_write_ip(DMN_LNX_Thread *thread, U64 ip)
{
regs_arch_block_write_rip(thread->process->ctx->arch, thread->reg_block, ip);
thread->is_reg_block_dirty = 1;
}
internal void
dmn_lnx_thread_write_sp(DMN_LNX_Thread *thread, U64 sp)
{
regs_arch_block_write_rsp(thread->process->ctx->arch, thread->reg_block, sp);
thread->is_reg_block_dirty = 1;
}
internal B32
dmn_lnx_thread_read_reg_block(DMN_LNX_Thread *thread)
{
AssertAlways(thread->state == DMN_LNX_ThreadState_Stopped);
B32 is_reg_block_read = 0;
switch(thread->process->ctx->arch)
{
case Arch_Null: {} break;
case Arch_x64:
{
DMN_LNX_ProcessCtx *process_ctx = thread->process->ctx;
REGS_RegBlockX64 *dst = thread->reg_block;
// general purpose registers
{
OS_LNX_GprsX64 src;
int ptrace_result = OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_GETREGSET, thread->tid, (void *)NT_PRSTATUS, &(struct iovec){ .iov_len = sizeof(src), .iov_base = &src }));
if(ptrace_result < 0) { goto exit; }
dst->r15.u64 = src.r15;
dst->r14.u64 = src.r14;
dst->r13.u64 = src.r13;
dst->r12.u64 = src.r12;
dst->rbp.u64 = src.rbp;
dst->rbx.u64 = src.rbx;
dst->r11.u64 = src.r11;
dst->r10.u64 = src.r10;
dst->r9.u64 = src.r9;
dst->r8.u64 = src.r8;
dst->rax.u64 = src.rax;
dst->rcx.u64 = src.rcx;
dst->rdx.u64 = src.rdx;
dst->rsi.u64 = src.rsi;
dst->rdi.u64 = src.rdi;
dst->rip.u64 = src.rip;
dst->cs.u16 = src.cs;
dst->rflags.u64 = src.rflags;
dst->rsp.u64 = src.rsp;
dst->ss.u16 = src.ss;
dst->fsbase.u64 = src.fsbase;
dst->gsbase.u64 = src.gsbase;
dst->ds.u16 = src.ds;
dst->es.u16 = src.es;
dst->fs.u16 = src.fs;
dst->gs.u16 = src.gs;
thread->orig_rax = src.orig_rax;
}
// xsave
{
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_ctx->xsave_size);
int ptrace_result = OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_GETREGSET, thread->tid, (void *)NT_X86_XSTATE, &(struct iovec){ .iov_len = process_ctx->xsave_size, .iov_base = xsave_raw }));
if(ptrace_result < 0) { goto exit; }
xsave = xsave_raw;
fxsave = &xsave->fxsave;
}
// get fxsave
if(fxsave == 0)
{
fxsave = push_array(scratch.arena, X64_FXSave, 1);
int ptrace_result = OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_GETREGSET, thread->tid, (void *)NT_FPREGSET, &(struct iovec){ .iov_len = sizeof(*fxsave), .iov_base = fxsave }));
if(ptrace_result < 0) { goto exit; }
fxsave = 0;
}
// copy fxsave registers
if(fxsave)
{
X64_FXSave *src = fxsave;
// copy x87 registers
dst->fcw.u16 = src->fcw;
dst->fsw.u16 = src->fsw;
dst->ftw = src->ftw;
dst->fop.u16 = src->fop;
dst->fip.u64 = src->fip;
dst->fdp.u64 = src->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)
{
// compact register layout is not supported
AssertAlways(xsave->header.xcomp_bv == 0);
if(xsave->header.xstate_bv & X64_XStateComponentFlag_AVX)
{
AssertAlways(process_ctx->xsave_layout.avx_offset + 16*sizeof(REGS_Reg128) <= process_ctx->xsave_size);
REGS_Reg128 *avx_s = (REGS_Reg128 *)((U8 *)xsave + process_ctx->xsave_layout.avx_offset);
REGS_Reg512 *zmm_d = &dst->zmm0;
for EachIndex(n, 16)
{
MemoryCopy(&zmm_d[n].v[16], &avx_s[n], sizeof(REGS_Reg128));
}
}
if(xsave->header.xstate_bv & X64_XStateComponentFlag_OPMASK)
{
AssertAlways(process_ctx->xsave_layout.opmask_offset + sizeof(REGS_Reg64) * 8 <= process_ctx->xsave_size);
REGS_Reg64 *kmask_s = (REGS_Reg64 *)((U8 *)xsave + process_ctx->xsave_layout.opmask_offset);
REGS_Reg64 *kmask_d = &dst->k0;
for EachIndex(n, 8)
{
MemoryCopy(&kmask_d[n], &kmask_s[n], sizeof(REGS_Reg64));
}
}
if(xsave->header.xstate_bv & X64_XStateComponentFlag_ZMM_H)
{
AssertAlways(process_ctx->xsave_layout.zmm_h_offset + sizeof(REGS_Reg256) * 16 <= process_ctx->xsave_size);
REGS_Reg256 *avx512h_s = (REGS_Reg256 *)((U8 *)xsave + process_ctx->xsave_layout.zmm_h_offset);
REGS_Reg512 *zmmh_d = &dst->zmm0;
for EachIndex(n, 16)
{
MemoryCopy(&zmmh_d[n].v[32], &avx512h_s[n], sizeof(REGS_Reg256));
}
}
if(xsave->header.xstate_bv & X64_XStateComponentFlag_ZMM)
{
AssertAlways(process_ctx->xsave_layout.zmm_offset + sizeof(REGS_Reg512) * 16 <= process_ctx->xsave_size);
REGS_Reg512 *avx512_s = (REGS_Reg512 *)((U8 *)xsave + process_ctx->xsave_layout.zmm_offset);
REGS_Reg512 *zmm_d = &dst->zmm16;
for EachIndex(n, 16)
{
MemoryCopy(&zmm_d[n], &avx512_s[n], sizeof(REGS_Reg512));
}
}
if(xsave->header.xstate_bv & X64_XStateComponentFlag_CETU)
{
AssertAlways(process_ctx->xsave_layout.cet_u_offset + sizeof(U64)*2 <= process_ctx->xsave_size);
U64 *cet_u = (U64 *)((U8 *)xsave + process_ctx->xsave_layout.cet_u_offset);
dst->cetmsr.u64 = cet_u[0];
dst->cetssp.u64 = cet_u[1];
}
}
scratch_end(scratch);
}
// debug registers
{
REGS_Reg64 *dr_d = &dst->dr0;
for EachIndex(n, 8)
{
if(n != 4 && n != 5)
{
U64 offset = OffsetOf(OS_LNX_UserX64, u_debugreg[n]);
errno = 0;
long peek_result = OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_PEEKUSER, thread->tid, PtrFromInt(offset), 0));
if(errno != 0) { goto exit; }
dr_d[n].u64 = peek_result;
}
}
}
is_reg_block_read = 1;
} break;
case Arch_x86:
case Arch_arm64:
case Arch_arm32: { NotImplemented; } break;
default: { InvalidPath; } break;
}
exit:;
return is_reg_block_read;
}
internal B32
dmn_lnx_thread_write_reg_block(DMN_LNX_Thread *thread)
{
AssertAlways(thread->state == DMN_LNX_ThreadState_Stopped);
B32 is_reg_block_written = 0;
switch(thread->process->ctx->arch)
{
case Arch_Null: {} break;
case Arch_x64:
{
DMN_LNX_ProcessCtx *process_ctx = thread->process->ctx;
REGS_RegBlockX64 *src = thread->reg_block;
// general purpose registers
{
OS_LNX_GprsX64 dst;
dst.r15 = src->r15.u64;
dst.r14 = src->r14.u64;
dst.r13 = src->r13.u64;
dst.r12 = src->r12.u64;
dst.rbp = src->rbp.u64;
dst.rbx = src->rbx.u64;
dst.r11 = src->r11.u64;
dst.r10 = src->r10.u64;
dst.r9 = src->r9.u64;
dst.r8 = src->r8.u64;
dst.rax = src->rax.u64;
dst.rcx = src->rcx.u64;
dst.rdx = src->rdx.u64;
dst.rsi = src->rsi.u64;
dst.rdi = src->rdi.u64;
dst.orig_rax = thread->orig_rax;
dst.rip = src->rip.u64;
dst.cs = src->cs.u16;
dst.rflags = src->rflags.u64;
dst.rsp = src->rsp.u64;
dst.ss = src->ss.u16;
dst.fsbase = src->fsbase.u64;
dst.gsbase = src->gsbase.u64;
dst.ds = src->ds.u16;
dst.es = src->es.u16;
dst.fs = src->fs.u16;
dst.gs = src->gs.u16;
int ptrace_result = OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_SETREGSET, thread->tid, (void *)NT_PRSTATUS, &(struct iovec){ .iov_base = &dst, .iov_len = sizeof(dst) }));
if(ptrace_result < 0) { goto exit; }
}
// xsave
{
Temp scratch = scratch_begin(0, 0);
X64_FXSave dst_fxsave = {0};
{
dst_fxsave.fcw = src->fcw.u16;
dst_fxsave.fsw = src->fsw.u16;
dst_fxsave.ftw = src->ftw;
dst_fxsave.fop = src->fop.u16;
dst_fxsave.fip = src->fip.u64;
dst_fxsave.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_ctx->xsave_size);
X64_XSave *dst = (X64_XSave *)xsave_raw;
dst->fxsave = dst_fxsave;
dst->header.xstate_bv |= X64_XStateComponentFlag_FP;
dst->header.xstate_bv |= X64_XStateComponentFlag_SSE;
if(process_ctx->xsave_layout.avx_offset)
{
if(process_ctx->xsave_layout.avx_offset + sizeof(REGS_Reg128) * 16 <= process_ctx->xsave_size)
{
REGS_Reg128 *avx_d = (REGS_Reg128 *)(xsave_raw + process_ctx->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));
}
dst->header.xstate_bv |= X64_XStateComponentFlag_AVX;
}
}
if(process_ctx->xsave_layout.opmask_offset)
{
if(process_ctx->xsave_layout.opmask_offset + sizeof(REGS_Reg64) * 8 <= process_ctx->xsave_size)
{
REGS_Reg64 *kmask_d = (REGS_Reg64 *)(xsave_raw + process_ctx->xsave_layout.opmask_offset);
REGS_Reg64 *kmask_s = &src->k0;
for EachIndex(n, 8)
{
MemoryCopy(&kmask_d[n], &kmask_s[n], sizeof(REGS_Reg64));
}
dst->header.xstate_bv |= X64_XStateComponentFlag_OPMASK;
}
else { Assert(0 && "invalid xsave size"); goto exit; }
}
if(process_ctx->xsave_layout.zmm_h_offset)
{
if(process_ctx->xsave_layout.zmm_h_offset + sizeof(REGS_Reg256) * 16 <= process_ctx->xsave_size)
{
REGS_Reg256 *avx512h_d = (REGS_Reg256 *)(xsave_raw + process_ctx->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));
}
dst->header.xstate_bv |= X64_XStateComponentFlag_ZMM_H;
}
else { Assert(0 && "invalid xsave size"); goto exit; }
}
if(process_ctx->xsave_layout.zmm_offset)
{
if(process_ctx->xsave_layout.zmm_offset + sizeof(REGS_Reg512) * 16 <= process_ctx->xsave_size)
{
REGS_Reg512 *avx512_d = (REGS_Reg512 *)(xsave_raw + process_ctx->xsave_layout.zmm_offset);
REGS_Reg512 *zmm_s = &src->zmm16;
for EachIndex(n, 16)
{
MemoryCopy(&avx512_d[n], &zmm_s[n], sizeof(REGS_Reg512));
}
dst->header.xstate_bv |= X64_XStateComponentFlag_ZMM;
}
else { Assert(0 && "invalid xsave size"); goto exit; }
}
if(process_ctx->xsave_layout.cet_u_offset)
{
if(process_ctx->xsave_layout.cet_u_offset + sizeof(REGS_Reg64) * 2 <= process_ctx->xsave_size)
{
REGS_Reg64 *cet_u = (REGS_Reg64 *)(xsave_raw + process_ctx->xsave_layout.cet_u_offset);
cet_u[0] = src->cetmsr;
cet_u[1] = src->cetssp;
dst->header.xstate_bv |= X64_XStateComponentFlag_CETU;
}
else { Assert(0 && "invalid xsave size"); goto exit; }
}
// xsave
Assert(dst->header.xcomp_bv == 0); // must always be zero
int ptrace_result = OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_SETREGSET, thread->tid, (void *)NT_X86_XSTATE, &(struct iovec){ .iov_base = dst, .iov_len = process_ctx->xsave_size }));
if(ptrace_result < 0) { goto exit; }
}
scratch_end(scratch);
}
// debug registers
{
src->dr7.u64 |= (1 << 10);
REGS_Reg64 *dr_s = &src->dr0;
for EachIndex(n, 8)
{
if(n != 4 && n != 5)
{
U64 offset = OffsetOf(OS_LNX_UserX64, u_debugreg[n]);
int ptrace_result = OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_POKEUSER, thread->tid, PtrFromInt(offset), (void*)(uintptr_t)dr_s[n].u64));
if(ptrace_result < 0) { goto exit; }
}
}
}
is_reg_block_written = 1;
} break;
case Arch_arm64:
case Arch_arm32:
case Arch_x86: { NotImplemented; }break;
default: { InvalidPath; } break;
}
exit:;
return is_reg_block_written;
}
internal B32
dmn_lnx_set_single_step_flag(DMN_LNX_Thread *thread, B32 is_on)
{
B32 is_flag_set = 0;
switch(thread->process->ctx->arch)
{
case Arch_Null: {} break;
case Arch_x64:
{
REGS_RegBlockX64 *reg_block = thread->reg_block;
if(is_on) { reg_block->rflags.u64 |= X64_RFlag_Trap; }
else { reg_block->rflags.u64 &= ~X64_RFlag_Trap; }
thread->is_reg_block_dirty = 1;
is_flag_set = 1;
} break;
case Arch_x86:
case Arch_arm32:
case Arch_arm64: { NotImplemented; }break;
default: { InvalidPath; } break;
}
Assert(is_flag_set);
return is_flag_set;
}
internal void
dmn_lnx_thread_ptr_list_push_node(DMN_LNX_ThreadPtrList *list, DMN_LNX_ThreadPtrNode *n)
{
DLLPushBack(list->first, list->last, n);
list->count += 1;
}
internal DMN_LNX_ThreadPtrNode *
dmn_lnx_thread_ptr_list_push(Arena *arena, DMN_LNX_ThreadPtrList *list, DMN_LNX_Thread *v)
{
DMN_LNX_ThreadPtrNode *n = push_array(arena, DMN_LNX_ThreadPtrNode, 1);
n->v = v;
dmn_lnx_thread_ptr_list_push_node(list, n);
return n;
}
internal void
dmn_lnx_thread_ptr_list_remove(DMN_LNX_ThreadPtrList *list, DMN_LNX_ThreadPtrNode *n)
{
DLLRemove(list->first, list->last, n);
list->count -= 1;
}
internal DMN_LNX_ModulePtrNode *
dmn_lnx_module_ptr_list_push(Arena *arena, DMN_LNX_ModulePtrList *list, DMN_LNX_Module *v)
{
DMN_LNX_ModulePtrNode *n = push_array(arena, DMN_LNX_ModulePtrNode, 1);
n->v = v;
SLLQueuePush(list->first, list->last, n);
list->count += 1;
return n;
}
internal DMN_LNX_ProcessPtrNode *
dmn_lnx_process_ptr_list_push(Arena *arena, DMN_LNX_ProcessPtrList *list, DMN_LNX_Process *v)
{
DMN_LNX_ProcessPtrNode *n = push_array(arena, DMN_LNX_ProcessPtrNode, 1);
n->v = v;
SLLQueuePush(list->first, list->last, n);
list->count += 1;
return n;
}
internal void
dmn_lnx_push_event_create_process(Arena *arena, DMN_EventList *events, DMN_LNX_Process *process)
{
// push create process event
DMN_Event *e = dmn_event_list_push(arena, events);
e->kind = DMN_EventKind_CreateProcess;
e->process = dmn_lnx_handle_from_process(process);
e->arch = process->ctx->arch;
e->code = process->pid;
e->tls_model = DMN_TlsModel_Gnu; // TODO: use dynamic linker path to figure out correct enum here
}
internal void
dmn_lnx_push_event_exit_process(Arena *arena, DMN_EventList *events, DMN_LNX_Process *process)
{
DMN_Event *e = dmn_event_list_push(arena, events);
e->kind = DMN_EventKind_ExitProcess;
e->process = dmn_lnx_handle_from_process(process);
e->code = process->main_thread_exit_code;
}
internal void
dmn_lnx_push_event_create_thread(Arena *arena, DMN_EventList *events, DMN_LNX_Thread *thread)
{
DMN_Event *e = dmn_event_list_push(arena, events);
e->kind = DMN_EventKind_CreateThread;
e->process = dmn_lnx_handle_from_process(thread->process);
e->thread = dmn_lnx_handle_from_thread(thread);
e->arch = thread->process->ctx->arch;
e->code = thread->tid;
}
internal void
dmn_lnx_push_event_exit_thread(Arena *arena, DMN_EventList *events, DMN_LNX_Thread *thread, U64 exit_code)
{
DMN_Event *e = dmn_event_list_push(arena, events);
e->kind = DMN_EventKind_ExitThread;
e->process = dmn_lnx_handle_from_process(thread->process);
e->thread = dmn_lnx_handle_from_thread(thread);
e->code = exit_code;
}
internal void
dmn_lnx_push_event_load_module(Arena *arena, DMN_EventList *events, DMN_LNX_Thread *thread, DMN_LNX_Module *module)
{
// TODO: reporting this module breaks ctrl thread
String8 module_name = dmn_lnx_read_string(arena, thread->process->fd, module->name_vaddr);
if(!str8_match(module_name, str8_lit("linux-vdso.so.1"), 0))
{
DMN_Event *e = dmn_event_list_push(arena, events);
e->kind = DMN_EventKind_LoadModule;
e->process = dmn_lnx_handle_from_process(thread->process);
e->thread = dmn_lnx_handle_from_thread(thread);
e->module = dmn_lnx_handle_from_module(module);
e->arch = thread->process->ctx->arch;
e->address = module->base_vaddr;
e->size = module->size;
e->string = module_name;
e->elf_phdr_vrange = r1u64(module->phvaddr, module->phvaddr + module->phentsize * module->phcount);
e->elf_phdr_entsize = module->phentsize;
e->tls_index = module->tls_index;
e->tls_offset = module->tls_offset;
}
}
internal void
dmn_lnx_push_event_unload_module(Arena *arena, DMN_EventList *events, DMN_LNX_Process *process, DMN_LNX_Module *module)
{
DMN_Event *e = dmn_event_list_push(arena, events);
e->kind = DMN_EventKind_UnloadModule;
e->process = dmn_lnx_handle_from_process(process);
e->module = dmn_lnx_handle_from_module(module);
}
internal void
dmn_lnx_push_event_handshake_complete(Arena *arena, DMN_EventList *events, DMN_LNX_Process *process)
{
DMN_Event *e = dmn_event_list_push(arena, events);
e->kind = DMN_EventKind_HandshakeComplete;
e->process = dmn_lnx_handle_from_process(process);
e->thread = dmn_lnx_handle_from_thread(process->first_thread);
e->arch = process->ctx->arch;
}
internal void
dmn_lnx_push_event_breakpoint(Arena *arena, DMN_EventList *events, DMN_LNX_Thread *thread, U64 address)
{
DMN_Event *e = dmn_event_list_push(arena, events);
e->kind = DMN_EventKind_Breakpoint;
e->process = dmn_lnx_handle_from_process(thread->process);
e->thread = dmn_lnx_handle_from_thread(thread);
e->instruction_pointer = address;
}
internal void
dmn_lnx_push_event_single_step(Arena *arena, DMN_EventList *events, DMN_LNX_Thread *thread)
{
DMN_Event *e = dmn_event_list_push(arena, events);
e->kind = DMN_EventKind_SingleStep;
e->process = dmn_lnx_handle_from_process(thread->process);
e->thread = dmn_lnx_handle_from_thread(thread);
e->instruction_pointer = dmn_lnx_thread_read_ip(thread);
}
internal void
dmn_lnx_push_event_exception(Arena *arena, DMN_EventList *events, DMN_LNX_Thread *thread, U64 signo)
{
local_persist B8 is_repeatable[] =
{
0, // null
1, // 1 SIGHUP
1, // 2 SIGINT
1, // 3 SIGQUIT
1, // 4 SIGTRAP
1, // 5 SIGABRT
1, // 6 SIGIOT
1, // 7 SIGBUS
1, // 8 SIGFPE
1, // 9 SIGKILL
1, // 10 SIGUSR1
1, // 11 SIGSEGV
1, // 12 SIGUSR2
1, // 13 SIGPIPE
1, // 14 SIGALRM
1, // 15 SIGTERM
1, // 16 SIGTKFLT
0, // 17 SIGCHLD
0, // 18 SIGCONT
1, // 19 SIGSTOP
1, // 20 SIGTSP
1, // 21 SIGTTIN
1, // 22 SIGTTOU
0, // 23 SIGURG
1, // 24 SIGXCPU
1, // 25 SIGXFSZ
1, // 26 SIGVTALRM
1, // 27 SIGPROF
0, // 28 SIGWINCH
1, // 29 SIGIO
1, // 30 SIGPWR
1, // 31 SIGSYS
1, // 32 SIGUNUSED
};
DMN_Event *e = dmn_event_list_push(arena, events);
e->kind = DMN_EventKind_Exception;
e->process = dmn_lnx_handle_from_process(thread->process);
e->thread = dmn_lnx_handle_from_thread(thread);
e->instruction_pointer = dmn_lnx_thread_read_ip(thread);
e->signo = signo;
e->exception_repeated = signo < ArrayCount(is_repeatable) ? is_repeatable[signo] : 0;
if(signo == SIGSEGV)
{
siginfo_t si = {0};
OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_GETSIGINFO, thread->tid, 0, &si));
e->address = (U64)si.si_addr;
}
}
internal void
dmn_lnx_push_event_halt(Arena *arena, DMN_EventList *events)
{
DMN_Event *e = dmn_event_list_push(arena, events);
e->kind = DMN_EventKind_Halt;
}
internal void
dmn_lnx_push_event_not_attached(Arena *arena, DMN_EventList *events)
{
DMN_Event *e = dmn_event_list_push(arena, events);
e->kind = DMN_EventKind_Error;
e->error_kind = DMN_ErrorKind_NotAttached;
}
internal DMN_LNX_Thread *
dmn_lnx_event_create_thread(Arena *arena, DMN_EventList *events, DMN_LNX_Process *process, pid_t tid)
{
DMN_LNX_Thread *thread = dmn_lnx_thread_alloc(process, DMN_LNX_ThreadState_Stopped, tid);
dmn_lnx_push_event_create_thread(arena, events, thread);
return thread;
}
internal void
dmn_lnx_event_exit_thread(Arena *arena, DMN_EventList *events, pid_t tid, U64 exit_code)
{
DMN_LNX_Thread *thread = dmn_lnx_thread_from_pid(tid);
DMN_LNX_Process *process = thread->process;
// store main thread's exit code
if(thread->tid == thread->process->pid)
{
thread->process->main_thread_exit_code = exit_code;
}
// push exit event
dmn_lnx_push_event_exit_thread(arena, events, thread, exit_code);
// release entity
dmn_lnx_thread_release(thread);
// auto exit process on last thread
if(process->thread_count == 0)
{
dmn_lnx_event_exit_process(arena, events, process->pid);
}
}
internal DMN_LNX_Process *
dmn_lnx_event_create_process(Arena *arena, DMN_EventList *events, pid_t pid, DMN_LNX_Process *parent_process, DMN_LNX_CreateProcessFlags flags)
{
DMN_LNX_Process *process = dmn_lnx_process_alloc(pid, DMN_LNX_ProcessState_Normal, parent_process, !!(flags & DMN_LNX_CreateProcessFlag_DebugSubprocesses), !!(flags & DMN_LNX_CreateProcessFlag_Cow));
if(flags & DMN_LNX_CreateProcessFlag_ClonedMemory)
{
process->ctx = parent_process->ctx;
}
else
{
process->ctx = dmn_lnx_process_ctx_alloc(process, !!(flags & DMN_LNX_CreateProcessFlag_Rebased));
}
process->ctx->ref_count += 1;
// install probes in a process that does not have a cloned memory
if(!(flags & DMN_LNX_CreateProcessFlag_ClonedMemory))
{
dmn_lnx_process_trap_probes(process);
}
// create main thread
dmn_lnx_thread_alloc(process, DMN_LNX_ThreadState_Stopped, pid);
// push events
dmn_lnx_push_event_create_process(arena, events, process);
for EachNode(thread, DMN_LNX_Thread, process->first_thread)
{
dmn_lnx_push_event_create_thread(arena, events, thread);
}
for EachNode(module, DMN_LNX_Module, process->ctx->first_module)
{
dmn_lnx_push_event_load_module(arena, events, process->first_thread, module);
}
dmn_lnx_push_event_handshake_complete(arena, events, process);
return process;
}
internal void
dmn_lnx_event_exit_process(Arena *arena, DMN_EventList *events, pid_t pid)
{
DMN_LNX_Process *process = dmn_lnx_process_from_pid(pid);
AssertAlways(process->thread_count == 0);
// push module events
for EachNode(module, DMN_LNX_Module, process->ctx->first_module)
{
dmn_lnx_push_event_unload_module(arena, events, process, module);
}
// push process exit event
dmn_lnx_push_event_exit_process(arena, events, process);
// release process
dmn_lnx_process_release(process);
}
internal void
dmn_lnx_event_load_module(Arena *arena, DMN_EventList *events, DMN_LNX_Thread *thread, U64 name_space_id, U64 new_link_map_vaddr)
{
DMN_LNX_Process *process = thread->process;
GNU_LinkMap64 map = {0};
for(U64 map_vaddr = new_link_map_vaddr; map_vaddr != 0; map_vaddr = map.next_vaddr)
{
// read out new link map item
if(gnu_read_link_map(dmn_lnx_machine_op_mem_read, &process->fd, map_vaddr, process->ctx->dl_class, &map) != MachineOpResult_Ok) { break; }
// was module already loaded?
DMN_LNX_Module *module = hash_table_search_u64_raw(process->ctx->loaded_modules_ht, map.addr_vaddr);
if(module) { continue; }
// clone process ctx
if(process->is_cow)
{
process->is_cow = 0;
process->ctx = dmn_lnx_process_ctx_clone(process, process->ctx);
}
// alloc module
module = dmn_lnx_module_alloc(process->ctx, process->fd, map.addr_vaddr, map.name_vaddr, name_space_id, 0);
// push load module event
dmn_lnx_push_event_load_module(arena, events, thread, module);
}
}
internal void
dmn_lnx_event_unload_module(Arena *arena, DMN_EventList *events, DMN_LNX_Process *process, U64 rdebug_vaddr)
{
Temp scratch = scratch_begin(&arena, 1);
DMN_LNX_ProcessCtx *ctx = process->ctx;
// flag every module as inactive
for EachNode(module, DMN_LNX_Module, ctx->first_module)
{
module->is_live = 0;
}
// mark live modules
B32 is_64bit = ctx->dl_class == ELF_Class_64;
GNU_RDebugInfoList rdebug_list = gnu_parse_rdebug(scratch.arena, is_64bit, rdebug_vaddr, dmn_lnx_machine_op_mem_read, &process->fd);
for EachNode(rdebug_n, GNU_RDebugInfoNode, rdebug_list.first)
{
GNU_LinkMapList link_map_list = gnu_parse_link_map_list(scratch.arena, is_64bit, rdebug_n->v.r_map, dmn_lnx_machine_op_mem_read, &process->fd);
for EachNode(link_map_n, GNU_LinkMapNode, link_map_list.first)
{
DMN_LNX_Module *module = hash_table_search_u64_raw(ctx->loaded_modules_ht, link_map_n->v.addr_vaddr);
module->is_live = 1;
}
}
// collect unloaded modules
DMN_LNX_ModulePtrList to_release = {0};
for EachNode(module, DMN_LNX_Module, ctx->first_module)
{
if(!module->is_live)
{
dmn_lnx_module_ptr_list_push(scratch.arena, &to_release, module);
}
}
// clone process context
if(to_release.count > 0)
{
if(process->is_cow)
{
process->is_cow = 0;
process->ctx = dmn_lnx_process_ctx_clone(process, process->ctx);
}
}
// push events and clean up unloaded modules
for EachNode(module_n, DMN_LNX_ModulePtrNode, to_release.first)
{
dmn_lnx_push_event_unload_module(arena, events, process, module_n->v);
dmn_lnx_module_release(process->ctx, module_n->v);
}
scratch_end(scratch);
}
internal void
dmn_lnx_event_breakpoint(Arena *arena, DMN_EventList *events, DMN_ActiveTrap *user_traps, pid_t tid)
{
DMN_LNX_Thread *thread = dmn_lnx_thread_from_pid(tid);
DMN_LNX_Process *process = thread->process;
U64 ip = dmn_lnx_thread_read_ip(thread);
// is this user trap?
DMN_ActiveTrap *hit_user_trap = 0;
{
DMN_Handle process_handle = dmn_lnx_handle_from_process(process);
for EachNode(active_trap, DMN_ActiveTrap, user_traps)
{
if(MemoryCompare(&active_trap->trap->process, &process_handle, sizeof(DMN_Handle)) == 0)
{
if(active_trap->trap->vaddr == ip-1)
{
hit_user_trap = active_trap;
break;
}
}
}
}
// is this a probe trap?
DMN_LNX_ProbeType probe_type = DMN_LNX_ProbeType_Null;
if(hit_user_trap == 0)
{
for EachNode(active_trap, DMN_ActiveTrap, process->ctx->first_probe_trap)
{
if(active_trap->trap->vaddr == ip-1)
{
probe_type = active_trap->trap->id;
break;
}
}
}
if(probe_type == DMN_LNX_ProbeType_InitComplete)
{
B32 is_init_completed = 0;
DMN_LNX_Probe *probe = process->ctx->probes[DMN_LNX_ProbeType_InitComplete];
U64 name_space_id = 0, rdebug_addr = 0;
if(!stap_read_arg_u(probe->args.v[0], process->ctx->arch, thread->reg_block, dmn_lnx_stap_memory_read, process, &name_space_id)) { goto init_complete_exit; }
if(!stap_read_arg_u(probe->args.v[1], process->ctx->arch, thread->reg_block, dmn_lnx_stap_memory_read, process, &rdebug_addr)) { goto init_complete_exit; }
GNU_RDebugInfo64 rdebug = {0};
if(gnu_read_r_debug(dmn_lnx_machine_op_mem_read, &process->fd, rdebug_addr, process->ctx->arch, &rdebug) != MachineOpResult_Ok) { goto init_complete_exit; }
if(rdebug.r_version < 1) { goto init_complete_exit; }
dmn_lnx_event_load_module(arena, events, thread, name_space_id, rdebug.r_map);
is_init_completed = 1;
init_complete_exit:;
AssertAlways(is_init_completed);
}
else if(probe_type == DMN_LNX_ProbeType_RelocComplete)
{
B32 is_reloc_completed = 0;
DMN_LNX_Probe *probe = process->ctx->probes[DMN_LNX_ProbeType_RelocComplete];
U64 name_space_id = 0, new_link_map_addr = 0;
if(!stap_read_arg_u(probe->args.v[0], process->ctx->arch, thread->reg_block, dmn_lnx_stap_memory_read, process, &name_space_id)) { goto reloc_complete_exit; }
if(!stap_read_arg_u(probe->args.v[2], process->ctx->arch, thread->reg_block, dmn_lnx_stap_memory_read, process, &new_link_map_addr)) { goto reloc_complete_exit; }
dmn_lnx_event_load_module(arena, events, thread, name_space_id, new_link_map_addr);
is_reloc_completed = 1;
reloc_complete_exit:;
AssertAlways(is_reloc_completed);
}
else if(probe_type == DMN_LNX_ProbeType_UnmapComplete)
{
B32 is_unmap_completed = 0;
DMN_LNX_Probe *probe = process->ctx->probes[DMN_LNX_ProbeType_UnmapComplete];
U64 name_space_id = 0, rdebug_vaddr = 0;
if(!stap_read_arg_u(probe->args.v[0], process->ctx->arch, thread->reg_block, dmn_lnx_stap_memory_read, process, &name_space_id)) { goto unmap_complete_exit; }
if(!stap_read_arg_u(probe->args.v[1], process->ctx->arch, thread->reg_block, dmn_lnx_stap_memory_read, process, &rdebug_vaddr)) { goto unmap_complete_exit; }
dmn_lnx_event_unload_module(arena, events, process, rdebug_vaddr);
is_unmap_completed = 1;
unmap_complete_exit:;
AssertAlways(is_unmap_completed);
}
if(probe_type == DMN_LNX_ProbeType_Null)
{
// 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);
}
DMN_Event *e = dmn_event_list_push(arena, events);
e->kind = DMN_EventKind_Breakpoint;
e->process = dmn_lnx_handle_from_process(process);
e->thread = dmn_lnx_handle_from_thread(thread);
e->instruction_pointer = dmn_lnx_thread_read_ip(thread);
}
}
internal void
dmn_lnx_event_data_breakpoint(Arena *arena, DMN_EventList *events, pid_t tid)
{
DMN_LNX_Thread *thread = dmn_lnx_thread_from_pid(tid);
B32 is_valid = 1;
U64 address = 0;
switch(thread->process->ctx->arch)
{
case Arch_Null: {} break;
case Arch_x64:
{
REGS_RegBlockX64 *regs_x64 = thread->reg_block;
if(regs_x64->dr6.u64 & X64_DebugStatusFlag_B0)
{
address = regs_x64->dr0.u64;
}
else if(regs_x64->dr6.u64 & X64_DebugStatusFlag_B1)
{
address = regs_x64->dr1.u64;
}
else if(regs_x64->dr6.u64 & X64_DebugStatusFlag_B2)
{
address = regs_x64->dr2.u64;
}
else if(regs_x64->dr6.u64 & X64_DebugStatusFlag_B3)
{
address = regs_x64->dr3.u64;
}
else
{
is_valid = 0;
}
} break;
case Arch_x86:
case Arch_arm32:
case Arch_arm64:
{ NotImplemented; } break;
default: { InvalidPath; } break;
}
if(is_valid)
{
dmn_lnx_push_event_breakpoint(arena, events, thread, address);
}
}
internal void
dmn_lnx_event_halt(Arena *arena, DMN_EventList *events)
{
dmn_lnx_push_event_halt(arena, events);
}
internal void
dmn_lnx_event_single_step(Arena *arena, DMN_EventList *events, pid_t tid)
{
DMN_LNX_Thread *thread = dmn_lnx_thread_from_pid(tid);
// clear single step flag
dmn_lnx_set_single_step_flag(thread, 0);
// push event
dmn_lnx_push_event_single_step(arena, events, thread);
}
internal void
dmn_lnx_event_exception(Arena *arena, DMN_EventList *events, pid_t tid, U64 signo)
{
DMN_LNX_Thread *thread = dmn_lnx_thread_from_pid(tid);
thread->pass_through_signal = 1;
thread->pass_through_signo = signo;
dmn_lnx_push_event_exception(arena, events, thread, signo);
}
internal DMN_LNX_Process *
dmn_lnx_event_attach(Arena *arena, DMN_EventList *events, pid_t pid)
{
Temp scratch = scratch_begin(&arena, 1);
// create process
DMN_LNX_Process *process = dmn_lnx_event_create_process(arena, events, pid, 0, DMN_LNX_CreateProcessFlag_DebugSubprocesses|DMN_LNX_CreateProcessFlag_Rebased);
// extract threads from /proc/pid/task
{
String8 task_path = push_str8f(scratch.arena, "/proc/%d/task", pid);
DIR *task_dirp = opendir((char *)task_path.str);
if(task_dirp)
{
for(;;)
{
struct dirent *dirent = readdir(task_dirp);
if(dirent == 0) { break; }
String8 tid_str = str8_cstring_capped(dirent->d_name, dirent->d_name + NAME_MAX);
if(str8_match(tid_str, str8_lit(".."), 0) || str8_match(tid_str, str8_lit("."), 0)) { continue; }
U64 tid_64 = u64_from_str8(tid_str, 10);
pid_t tid = (pid_t)tid_64;
AssertAlways(tid == tid_64);
if(tid == pid) { continue; } // main thread was created during create process sequence
dmn_lnx_event_create_thread(arena, events, process, tid);
}
OS_LNX_RETRY_ON_EINTR(closedir(task_dirp));
}
}
// extract modules from r_debug
{
B32 is_64bit = process->ctx->dl_class == ELF_Class_64;
GNU_RDebugInfoList rdebug_list = gnu_parse_rdebug(scratch.arena, is_64bit, process->ctx->rdebug_vaddr, dmn_lnx_machine_op_mem_read, &process->fd);
U64 name_space_id = 0;
for EachNode(rdebug_n, GNU_RDebugInfoNode, rdebug_list.first)
{
dmn_lnx_event_load_module(arena, events, process->first_thread, name_space_id, rdebug_n->v.r_map);
name_space_id += 1;
}
}
// handshake complete
dmn_lnx_push_event_handshake_complete(arena, events, process);
scratch_end(scratch);
return process;
}
////////////////////////////////
//~ rjf: @dmn_os_hooks Main Layer Initialization (Implemented Per-OS)
internal void
dmn_init(void)
{
if(dmn_lnx_state == 0)
{
local_persist DMN_LNX_State state;
dmn_lnx_state = &state;
dmn_lnx_state->arena = arena_alloc();
dmn_lnx_state->access_mutex = mutex_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_state->tid_ht = hash_table_init(dmn_lnx_state->arena, 0x2000);
dmn_lnx_state->pid_ht = hash_table_init(dmn_lnx_state->arena, 0x400);
dmn_lnx_state->halter_mutex = mutex_alloc();
dmn_lnx_entity_alloc(DMN_LNX_EntityKind_Null);
// find offsets of TLS index and TLS offset in the link_map struct
//
// TODO: assuming that target is using same libc version as debugger
{
DMN_LNX_DbDesc *tls_modid_desc = dlsym(RTLD_DEFAULT, "_thread_db_link_map_l_tls_modid");
DMN_LNX_DbDesc *tls_offset_desc = dlsym(RTLD_DEFAULT, "_thread_db_link_map_l_tls_offset");
if(tls_modid_desc && tls_offset_desc)
{
if(tls_modid_desc->bit_size <= 64 && tls_offset_desc->bit_size <= 64)
{
dmn_lnx_state->tls_modid_desc = *tls_modid_desc;
dmn_lnx_state->tls_offset_desc = *tls_offset_desc;
dmn_lnx_state->is_tls_detected = 1;
}
else { Assert(0 && "invalid TLS desc"); }
}
}
}
}
////////////////////////////////
//~ 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);
// setup target command line
U64 argc = params->cmd_line.node_count + 1;
char **argv = push_array(scratch.arena, char *, argc);
{
U64 idx = 0;
for EachNode(n, String8Node, params->cmd_line.first)
{
argv[idx++] = (char *)push_str8_copy(scratch.arena, n->string).str;
}
}
// setup target environment
U64 envc = os_lnx_state.default_env_count + params->env.node_count + 1;
char **envp = push_array(scratch.arena, char *, envc);
{
// copy default environment
MemoryCopyTyped(envp, os_lnx_state.default_env, os_lnx_state.default_env_count);
// copy user environment
U64 idx = os_lnx_state.default_env_count;
for EachNode(n, String8Node, params->env.first)
{
envp[idx++] = (char *)push_str8_copy(scratch.arena, n->string).str;
}
}
// create zero-terminated work directory path
char *work_dir_path = (char *)push_str8_copy(scratch.arena, params->path).str;
// fork process
pid_t pid = fork();
// child process
if(pid == 0)
{
// wait for seize
if(OS_LNX_RETRY_ON_EINTR(raise(SIGSTOP)) < 0) { goto child_exit; }
// change work directory to tracee
if(OS_LNX_RETRY_ON_EINTR(chdir(work_dir_path)) < 0) { goto child_exit; }
// replace process with target program
if(OS_LNX_RETRY_ON_EINTR(execve(argv[0], argv, envp)) < 0) { goto child_exit; }
child_exit:;
exit(0);
}
// parent process
else if(pid > 0)
{
// try to seize child process
if(dmn_lnx_ptrace_seize(pid) >= 0)
{
// tracee was successfully seized, create process
dmn_lnx_process_alloc(pid, DMN_LNX_ProcessState_Launch, 0, params->debug_subprocesses, 0);
}
else
{
Assert(0 && "failed to ptrace child process");
OS_LNX_RETRY_ON_EINTR(kill(SIGKILL, pid));
OS_LNX_RETRY_ON_EINTR(waitpid(pid, 0, WNOHANG));
pid = 0;
}
}
scratch_end(scratch);
return pid;
}
internal B32
dmn_ctrl_attach(DMN_CtrlCtx *ctx, U32 pid)
{
B32 is_attached = 0;
if(dmn_lnx_ptrace_seize(pid) >= 0)
{
if(OS_LNX_RETRY_ON_EINTR(kill(pid, SIGSTOP)) >= 0)
{
dmn_lnx_process_alloc(pid, DMN_LNX_ProcessState_Attach, 0, 1, 0);
is_attached = 1;
}
else
{
OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_DETACH, pid, 0, 0));
}
}
return is_attached;
}
internal B32
dmn_ctrl_kill(DMN_CtrlCtx *ctx, DMN_Handle process_handle, U32 exit_code)
{
B32 result = 0;
mutex_take(dmn_lnx_state->halter_mutex);
DMN_LNX_Process *process = dmn_lnx_process_from_handle(process_handle);
if(process)
{
result = OS_LNX_RETRY_ON_EINTR(kill(process->pid, SIGKILL)) >= 0;
}
mutex_drop(dmn_lnx_state->halter_mutex);
return result;
}
internal B32
dmn_ctrl_detach(DMN_CtrlCtx *ctx, DMN_Handle process_handle)
{
B32 result = 0;
mutex_take(dmn_lnx_state->halter_mutex);
DMN_LNX_Process *process = dmn_lnx_process_from_handle(process_handle);
if(process)
{
result = OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_DETACH, process->pid, 0, 0)) >= 0;
}
mutex_drop(dmn_lnx_state->halter_mutex);
return result;
}
internal DMN_EventList
dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls)
{
Temp scratch = scratch_begin(&arena, 1);
DMN_EventList events = {0};
mutex_take(dmn_lnx_state->halter_mutex);
// wait for signals from the running threads
if(dmn_lnx_state->process_count > 0)
{
// write traps to memory
DMN_ActiveTrap *active_trap_first = 0, *active_trap_last = 0;
{
HashTable *process_ht = hash_table_init(scratch.arena, dmn_lnx_state->process_count);
for EachNode(n, DMN_TrapChunkNode, ctrls->traps.first)
{
for EachIndex(n_idx, n->count)
{
// skip hardware breakpoints
DMN_Trap *trap = n->v+n_idx;
if(trap->flags) { continue; }
HashTable *active_trap_ht = hash_table_search_u64_raw(process_ht, trap->process.u64[0]);
if(active_trap_ht == 0)
{
active_trap_ht = hash_table_init(scratch.arena, ctrls->traps.trap_count);
hash_table_push_u64_raw(scratch.arena, process_ht, trap->process.u64[0], active_trap_ht);
}
// TODO: ctrl sends down duplicate traps
DMN_ActiveTrap *is_set = hash_table_search_u64_raw(active_trap_ht, trap->vaddr);
if(is_set) { continue; }
// TODO: ctrl sends down traps for exited process
DMN_LNX_Process *process = dmn_lnx_process_from_handle(trap->process);
if(!process) { continue; }
// trap instruction
DMN_ActiveTrap *active_trap = dmn_set_trap(scratch.arena, trap);
// add trap to the active list
SLLQueuePush(active_trap_first, active_trap_last, active_trap);
// add (address -> trap)
hash_table_push_u64_raw(scratch.arena, active_trap_ht, trap->vaddr, active_trap);
}
}
}
// enable single stepping
if(!dmn_handle_match(ctrls->single_step_thread, dmn_handle_zero()))
{
DMN_LNX_Thread *single_step_thread = dmn_lnx_thread_from_handle(ctrls->single_step_thread);
if(single_step_thread)
{
dmn_lnx_set_single_step_flag(single_step_thread, 1);
}
else
{
Assert(0 && "invalid single_step_thread handle");
}
}
// schedule threads to run
DMN_LNX_ThreadPtrList running_threads = {0};
{
for EachNode(process, DMN_LNX_Process, dmn_lnx_state->first_process)
{
//- rjf: determine if this process is frozen
B32 process_is_frozen = 0;
if(ctrls->run_entities_are_processes)
{
for EachIndex(idx, ctrls->run_entity_count)
{
if(dmn_handle_match(ctrls->run_entities[idx], dmn_lnx_handle_from_process(process)))
{
process_is_frozen = 1;
break;
}
}
}
for EachNode(thread, DMN_LNX_Thread, process->first_thread)
{
//- rjf: determine if this thread is frozen
B32 is_frozen = 0;
// rjf: not single-stepping? determine based on run controls freezing info
if(dmn_handle_match(dmn_handle_zero(), ctrls->single_step_thread))
{
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_thread(thread)))
{
is_frozen = 1;
break;
}
}
}
if(ctrls->run_entities_are_unfrozen)
{
is_frozen ^= 1;
}
}
// rjf: single-step? freeze if not the single-step thread.
else
{
is_frozen = !dmn_handle_match(dmn_lnx_handle_from_thread(thread), ctrls->single_step_thread);
}
// resume thread
if(!is_frozen)
{
AssertAlways(thread->state == DMN_LNX_ThreadState_Stopped);
// write registers
if(thread->is_reg_block_dirty)
{
thread->is_reg_block_dirty = !dmn_lnx_thread_write_reg_block(thread);
}
// pass signal to the child process
void *sig_code = 0;
if(thread->pass_through_signal)
{
thread->pass_through_signal = 0;
if(!ctrls->ignore_previous_exception)
{
sig_code = (void *)(uintptr_t)thread->pass_through_signo;
}
}
// resume thread
if(OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_CONT, thread->tid, 0, sig_code)) >= 0)
{
thread->state = DMN_LNX_ThreadState_Running;
thread->is_reg_block_dirty = 1;
dmn_lnx_thread_ptr_list_push(scratch.arena, &running_threads, thread);
}
else
{
if(errno == ESRCH)
{
// Race note: In multithreaded programs, ptrace(PTRACE_CONT) may fail if a running thread calls
// exit_group while we are resuming threads. The kernel begins tearing down the thread group so
// the target might not be in a ptrace-soppped state anymore. Handle this by ignoring, on waitpid
// we will get chance to report exit event.
//
// TODO: unfortunatley, kernel also sends us ESRCH whenever ptrace(PTRACE_CONT) is sent to a tracee
// that is not in a ptace-stop, so how can we tell these errors apart?
}
else
{
InvalidPath;
}
}
}
}
}
}
// hash running threads tids
HashTable *running_threads_ht = hash_table_init(scratch.arena, running_threads.count * 2);
for EachNode(n, DMN_LNX_ThreadPtrNode, running_threads.first)
{
hash_table_push_u64_raw(scratch.arena, running_threads_ht, n->v->tid, n);
}
B32 is_halt_done = 0;
DMN_LNX_ThreadPtrList stopped_threads = {0};
do
{
// wait for a signal
int status = 0;
pid_t wait_id = 0;
{
mutex_drop(dmn_lnx_state->halter_mutex);
for(;;)
{
wait_id = OS_LNX_RETRY_ON_EINTR(waitpid(-1, &status, __WALL|__WNOTHREAD));
if(wait_id == -1) { InvalidPath; } // TODO: graceful exit
break;
}
mutex_take(dmn_lnx_state->halter_mutex);
}
// unpack status
int wifexited = WIFEXITED(status);
int wifsignaled = WIFSIGNALED(status);
int wifstopped = WIFSTOPPED(status);
int wstopsig = WSTOPSIG(status);
int event_code = (status >> 16);
// intercept initing processes
{
DMN_LNX_Process *process = dmn_lnx_process_from_pid(wait_id);
if(process && process->state != DMN_LNX_ProcessState_Normal)
{
switch(process->state)
{
case DMN_LNX_ProcessState_Null:
case DMN_LNX_ProcessState_Normal:
{
InvalidPath;
} break;
case DMN_LNX_ProcessState_Launch:
{
if(wstopsig == SIGSTOP)
{
if(OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_CONT, process->pid, 0, 0)) >= 0)
{
process->state = DMN_LNX_ProcessState_WaitForExec;
goto wait_for_signal;
}
else { Assert(0 && "failed to resume tracee"); }
}
// else { Assert(0 && "unexpected signal"); }
} break;
case DMN_LNX_ProcessState_WaitForExec:
{
if(wstopsig == SIGTRAP && event_code == PTRACE_EVENT_EXEC)
{
DMN_LNX_CreateProcessFlags create_flags = process->debug_subprocesses ? DMN_LNX_CreateProcessFlag_DebugSubprocesses : 0;
dmn_lnx_process_release(process);
dmn_lnx_event_create_process(arena, &events, wait_id, 0, create_flags);
goto wait_for_signal;
}
//else { Assert(0 && "unexpected signal"); }
} break;
case DMN_LNX_ProcessState_ExecFailedDoExit:
{
if(wifexited)
{
dmn_lnx_process_release(process);
goto wait_for_signal;
}
else { Assert(0 && "unexpected signal"); }
} break;
case DMN_LNX_ProcessState_Attach:
{
if(wstopsig == SIGSTOP)
{
DMN_LNX_CreateProcessFlags create_flags = process->debug_subprocesses ? DMN_LNX_CreateProcessFlag_DebugSubprocesses : 0;
dmn_lnx_process_release(process);
dmn_lnx_event_create_process(arena, &events, wait_id, 0, create_flags);
goto wait_for_signal;
}
else { Assert(0 && "unexpected signal"); }
} break;
}
// shutdown process
{
if(OS_LNX_RETRY_ON_EINTR(kill(wait_id, SIGKILL)) >= 0)
{
process->state = DMN_LNX_ProcessState_ExecFailedDoExit;
}
else
{
//Assert(0 && "failed to send kill signal to the tracee");
dmn_lnx_process_release(process);
}
}
wait_for_signal:;
continue;
}
}
if(wifstopped || wifsignaled || wifexited)
{
DMN_LNX_ThreadPtrNode *thread_n = hash_table_search_u64_raw(running_threads_ht, wait_id);
if(thread_n)
{
DMN_LNX_Thread *thread = thread_n->v;
AssertAlways(thread->state == DMN_LNX_ThreadState_Running);
// remove mapping
hash_table_purge_u64(running_threads_ht, thread->tid);
// move thread to the stopped list
dmn_lnx_thread_ptr_list_remove(&running_threads, thread_n);
dmn_lnx_thread_ptr_list_push_node(&stopped_threads, thread_n);
// update thread state
if(wifstopped && !wifsignaled && !wifexited)
{
thread->state = DMN_LNX_ThreadState_Stopped;
}
else if(!wifstopped && (wifsignaled || wifexited))
{
thread->state = DMN_LNX_ThreadState_Exited;
}
else { InvalidPath; }
// stop all other threads
if(stopped_threads.count == 1)
{
for EachNode(n, DMN_LNX_ThreadPtrNode, running_threads.first)
{
if(OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_INTERRUPT, n->v->tid, 0, 0)) < 0) { Assert(0 && "failed to interrupt process"); }
}
}
}
}
// normal child exit via _exit or exit()
if(wifexited)
{
dmn_lnx_event_exit_thread(arena, &events, wait_id, WEXITSTATUS(status));
}
// exit because child did not handle a signal
else if(wifsignaled)
{
dmn_lnx_event_exit_thread(arena, &events, wait_id, WTERMSIG(status));
}
// thread or group stop
else if(wifstopped)
{
// read thread registers
{
DMN_LNX_Thread *thread = dmn_lnx_thread_from_pid(wait_id);
if(thread && thread->state != DMN_LNX_ThreadState_PendingCreation)
{
thread->is_reg_block_dirty = !dmn_lnx_thread_read_reg_block(thread);
Assert(!thread->is_reg_block_dirty);
}
}
if(wstopsig == SIGTRAP)
{
switch(event_code)
{
case 0:
{
// translate signal code to DEMON event
siginfo_t siginfo = {0};
if(OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_GETSIGINFO, wait_id, 0, &siginfo)) >= 0)
{
switch(siginfo.si_code)
{
case SI_KERNEL: { dmn_lnx_event_breakpoint(arena, &events, active_trap_first, wait_id); } break;
case TRAP_BRKPT: { dmn_lnx_event_breakpoint(arena, &events, active_trap_first, wait_id); } break;
case TRAP_TRACE: { dmn_lnx_event_single_step(arena, &events, wait_id); } break;
case TRAP_HWBKPT: { dmn_lnx_event_data_breakpoint(arena, &events, wait_id); } break;
case TRAP_BRANCH: { NotImplemented; }break;
case TRAP_UNK: { NotImplemented; }break;
default: { InvalidPath; } break;
}
} else { Assert(0 && "failed to get signal info"); }
}break;
case PTRACE_EVENT_FORK:
{
NotImplemented;
}break;
case PTRACE_EVENT_VFORK:
{
NotImplemented;
}break;
case PTRACE_EVENT_VFORK_DONE:
{
NotImplemented;
}break;
case PTRACE_EVENT_CLONE:
{
// kernel stopped the parent just before scheduling the child to
// give us a chance to prepare to trace it; next event for the child
// will be a PTRACE_EVENT_STOP
pid_t new_tid;
if(OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_GETEVENTMSG, wait_id, 0, &new_tid)) >= 0)
{
DMN_LNX_Thread *thread = dmn_lnx_thread_from_pid(wait_id);
// create a new partially inited thread
dmn_lnx_thread_alloc(thread->process, DMN_LNX_ThreadState_PendingCreation, new_tid);
}
else { Assert(0 && "failed to get new tid"); }
}break;
case PTRACE_EVENT_EXEC:
{
DMN_LNX_Thread *thread = dmn_lnx_thread_from_pid(wait_id);
DMN_LNX_Process *process = thread->process;
if(process->debug_subprocesses)
{
dmn_lnx_event_exit_thread(arena, &events, wait_id, 0);
dmn_lnx_event_create_process(arena, &events, wait_id, 0, DMN_LNX_CreateProcessFlag_DebugSubprocesses);
}
else
{
if(OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_DETACH, wait_id, 0, 0)) >= 0)
{
dmn_lnx_event_exit_thread(arena, &events, wait_id, 0);
}
else { Assert(0 && "failed to detach"); }
}
}break;
case PTRACE_EVENT_EXIT:
{
AssertAlways(0 && "exit tracing is disabled, this event should not be delivered");
}break;
case PTRACE_EVENT_SECCOMP:
{
NotImplemented;
}break;
case PTRACE_EVENT_STOP:
{
DMN_LNX_Thread *thread = dmn_lnx_thread_from_pid(wait_id);
if(thread->state == DMN_LNX_ThreadState_PendingCreation)
{
DMN_LNX_Process *process = thread->process;
dmn_lnx_thread_release(thread);
thread = dmn_lnx_event_create_thread(arena, &events, process, wait_id);
}
else
{
AssertAlways(thread->state == DMN_LNX_ThreadState_Stopped);
}
}break;
default: { Assert(0 && "unexpected ptrace code"); } break;
}
}
else if(wstopsig == SIGSTOP)
{
siginfo_t siginfo = {0};
if(OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_GETSIGINFO, wait_id, 0, &siginfo)) >= 0)
{
// finish halting if this is our SIGSTOP
if(siginfo.si_pid == dmn_lnx_state->halter_tid)
{
is_halt_done = 1;
}
else
{
// TODO: handle SIGSTOP
}
}
}
else
{
dmn_lnx_event_exception(arena, &events, wait_id, wstopsig);
}
}
else { Assert(0 && "unexpected stop code"); }
} while(running_threads.count > 0 || dmn_lnx_state->process_pending_creation > 0 || dmn_lnx_state->threads_pending_creation > 0);
// finalize halter state
if(is_halt_done)
{
// push event
dmn_lnx_event_halt(arena, &events);
// reset state
dmn_lnx_state->halter_tid = 0;
dmn_lnx_state->halt_code = 0;
dmn_lnx_state->halt_user_data = 0;
dmn_lnx_state->is_halting = 0;
}
// restore original instruction bytes
for EachNode(active_trap, DMN_ActiveTrap, active_trap_first)
{
// skip process that exited during the wait
DMN_LNX_Process *process = dmn_lnx_process_from_handle(active_trap->trap->process);
if(!process) { continue; }
if(!dmn_process_write(active_trap->trap->process, r1u64(active_trap->trap->vaddr, active_trap->trap->vaddr + active_trap->swap_bytes.size), active_trap->swap_bytes.str))
{
Assert(0 && "failed to restore original instruction bytes");
}
}
}
if(events.count == 0 && dmn_lnx_state->process_count == 0)
{
dmn_lnx_push_event_not_attached(arena, &events);
}
mutex_drop(dmn_lnx_state->halter_mutex);
scratch_end(scratch);
return events;
}
////////////////////////////////
//~ rjf: @dmn_os_hooks Halting (Implemented Per-OS)
internal void
dmn_halt(U64 code, U64 user_data)
{
mutex_take(dmn_lnx_state->halter_mutex);
if(dmn_lnx_state->process_count)
{
dmn_lnx_state->halter_tid = gettid();
dmn_lnx_state->halt_code = code;
dmn_lnx_state->halt_user_data = user_data;
for EachNode(process, DMN_LNX_Process, dmn_lnx_state->first_process)
{
if(OS_LNX_RETRY_ON_EINTR(kill(process->pid, SIGSTOP)) < 0) { Assert(0 && "failed to send SIGSTOP"); }
}
}
mutex_drop(dmn_lnx_state->halter_mutex);
}
////////////////////////////////
//~ 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_handle, Rng1U64 range, void *dst)
{
DMN_LNX_Process *process = dmn_lnx_process_from_handle(process_handle);
U64 result = 0;
if(process)
{
result = dmn_lnx_read(process->fd, range, dst);
}
return result;
}
internal B32
dmn_process_write(DMN_Handle process_handle, Rng1U64 range, void *src)
{
DMN_LNX_Process *process = dmn_lnx_process_from_handle(process_handle);
B32 result = 0;
if(process)
{
result = dmn_lnx_write(process->fd, range, src);
}
return result;
}
//- rjf: threads
internal Arch
dmn_arch_from_thread(DMN_Handle thread_handle)
{
Arch arch = Arch_Null;
DMN_LNX_Thread *thread = dmn_lnx_thread_from_handle(thread_handle);
if(thread)
{
arch = thread->process->ctx->arch;
}
return arch;
}
internal U64
dmn_stack_base_vaddr_from_thread(DMN_Handle thread_handle)
{
return 0;
}
internal U64
dmn_tls_root_vaddr_from_thread(DMN_Handle thread_handle)
{
U64 tls_root_vaddr = max_U64;
DMN_AccessScope
{
DMN_LNX_Thread *thread = dmn_lnx_thread_from_handle(thread_handle);
if(thread)
{
switch(thread->process->ctx->arch)
{
case Arch_Null: {} break;
case Arch_x64:
{
REGS_RegBlockX64 *reg_block = thread->reg_block;
U64 dtv_pointer = 0;
if(dmn_lnx_read_struct(thread->process->fd, reg_block->fsbase.u64 + 8, &dtv_pointer))
{
tls_root_vaddr = dtv_pointer;
}
} break;
case Arch_x86:
case Arch_arm32:
case Arch_arm64:
{
NotImplemented;
} break;
default: { InvalidPath; } break;
}
}
}
return tls_root_vaddr;
}
internal B32
dmn_thread_read_reg_block(DMN_Handle thread_handle, void *reg_block)
{
B32 result = 0;
DMN_AccessScope
{
DMN_LNX_Thread *thread = dmn_lnx_thread_from_handle(thread_handle);
if(thread)
{
U64 reg_block_size = regs_block_size_from_arch(thread->process->ctx->arch);
MemoryCopy(reg_block, thread->reg_block, reg_block_size);
}
result = 1;
}
return result;
}
internal B32
dmn_thread_write_reg_block(DMN_Handle thread_handle, void *reg_block)
{
B32 result = 0;
DMN_AccessScope
{
DMN_LNX_Thread *thread = dmn_lnx_thread_from_handle(thread_handle);
if(thread)
{
U64 reg_block_size = regs_block_size_from_arch(thread->process->ctx->arch);
MemoryCopy(thread->reg_block, reg_block, reg_block_size);
thread->is_reg_block_dirty = 1;
result = 1;
}
}
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);
}