mirror of
https://github.com/Ed94/raddebugger.git
synced 2026-09-24 02:00:05 +00:00
2837 lines
91 KiB
C
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);
|
|
}
|