use STAP probes to generate load and unload module events

This commit is contained in:
Nikita Smith
2025-11-07 14:39:46 -08:00
committed by Ryan Fleury
parent c8c993d4e8
commit 66606b0327
3 changed files with 232 additions and 127 deletions
+219 -119
View File
@@ -590,7 +590,7 @@ exit:;
}
////////////////////////////////
//~ Probes
//~ SDT Probes
internal DMN_LNX_ProbeList
dmn_lnx_read_probes(Arena *arena, int fd, U64 offset, U64 image_base)
@@ -657,6 +657,7 @@ dmn_lnx_read_probes(Arena *arena, int fd, U64 offset, U64 image_base)
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)
@@ -701,7 +702,7 @@ dmn_lnx_read_probes(Arena *arena, int fd, U64 offset, U64 image_base)
probe.provider = provider;
probe.name = name;
probe.args = args;
probe.args = stap_arg_array_from_string(arena, arch, args);
probe.pc = pc + probe_rebase;
probe.semaphore = semaphore ? semaphore + probe_rebase : 0;
}
@@ -717,6 +718,17 @@ exit:;
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
@@ -1331,6 +1343,120 @@ dmn_lnx_set_single_step_flag(DMN_LNX_Entity *thread, B32 is_on)
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)
@@ -1626,31 +1752,34 @@ dmn_ctrl_launch(DMN_CtrlCtx *ctx, OS_ProcessLaunchParams *params)
}
AssertAlways(dl_path.size);
DMN_LNX_ProbeList probes = {0};
// 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);
if(dl_fd >= 0)
{
probes = dmn_lnx_read_probes(scratch.arena, dl_fd, 0, auxv.base);
probes = dmn_lnx_read_probes(process_arena, dl_fd, 0, auxv.base);
close(dl_fd);
}
}
DMN_LNX_Probe *known_probes[DMN_LNX_ProbeType_Count] = {0};
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,_S) if(str8_match(p->name, str8_lit(_S), 0)) { known_probes[DMN_LNX_ProbeType_##_N] = p; continue ; }
#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, ArrayCount(known_probes))
for EachIndex(i, DMN_LNX_ProbeType_Count)
{
if(known_probes[i] == 0) { continue; }
@@ -1676,8 +1805,9 @@ dmn_ctrl_launch(DMN_CtrlCtx *ctx, OS_ProcessLaunchParams *params)
process->rdebug_brk_vaddr = rdebug_brk_vaddr;
process->expect_rdebug_data_breakpoint = rdebug_vaddr != 0;
process->dl_class = dl_class;
process->arena = arena_alloc();
process->loaded_modules_ht = hash_table_init(process->arena, 0x1000);
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;
@@ -1697,7 +1827,7 @@ dmn_ctrl_launch(DMN_CtrlCtx *ctx, OS_ProcessLaunchParams *params)
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(dmn_lnx_state->entities_arena, U8, regs_block_size_from_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);
@@ -1840,6 +1970,7 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls)
{
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)
@@ -1848,6 +1979,9 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls)
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)
{
@@ -1858,6 +1992,8 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls)
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"); }
}
@@ -2168,6 +2304,78 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls)
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)
@@ -2175,114 +2383,6 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls)
U64 ip = dmn_lnx_thread_read_ip(thread);
dmn_lnx_thread_write_ip(thread, ip - 1);
}
}
// is this a probe trap?
if(!hit_user_trap && e_kind == DMN_EventKind_Breakpoint)
{
B32 skip_event = 0;
// find which probe was triggered
U64 ip = dmn_lnx_thread_read_ip(thread) - 1;
DMN_LNX_ProbeType probe_type = DMN_LNX_ProbeType_Null;
for EachIndex(i, ArrayCount(process->probe_vaddrs))
{
if(process->probe_vaddrs[i] == ip)
{
probe_type = i;
skip_event = 1;
break;
}
}
// handle the probe
if((probe_type == DMN_LNX_ProbeType_InitStart || probe_type == DMN_LNX_ProbeType_InitComplete))
{
GNU_RDebugInfo64 rdebug = {0};
dmn_lnx_read_r_debug(process->fd, process->rdebug_vaddr, process->arch, &rdebug);
switch(rdebug.r_state)
{
case GNU_RT_Add:
case GNU_RT_Delete:
case GNU_RT_Consistent:
{
// 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_active = 0;
}
for(U64 map_vaddr = rdebug.r_map; map_vaddr!= 0; )
{
// read out link map
GNU_LinkMap64 map = {0};
if(!dmn_lnx_read_linkmap(process->fd, map_vaddr, process->dl_class, &map)) { Assert(0 && "unable to read Link Map"); }
// make new module if never seen before
DMN_LNX_Entity *module = hash_table_search_u64_raw(process->loaded_modules_ht, map.addr_vaddr);
if(module == 0)
{
ELF_Hdr64 module_ehdr = {0};
if(!dmn_lnx_read_ehdr(process->fd, map.addr_vaddr, &module_ehdr)) { NotImplemented; }
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);
module = dmn_lnx_entity_alloc(process, DMN_LNX_EntityKind_Module);
module->id = map.name_vaddr;
module->base_vaddr = map.addr_vaddr;
if (!str8_match(module_name, str8_lit("linux-vdso.so.1"), 0))
{
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->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;
}
hash_table_push_u64_raw(process->arena, process->loaded_modules_ht, map.addr_vaddr, module);
}
// do not unload module
module->is_active = 1;
// advance to next link map
map_vaddr = map.next_vaddr;
}
// unload inactive modules
for(DMN_LNX_Entity *module = process->first, *module_next; module != &dmn_lnx_nil_entity; module = module_next)
{
module_next = module->next;
if(module->kind != DMN_LNX_EntityKind_Module) {continue;}
if(module->is_active) {continue;}
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);
}
} break;
default: { Assert(0 && "unexpected state"); } break;
}
}
if(skip_event) {break;}
}
switch(e_kind)
{
+18 -15
View File
@@ -86,14 +86,15 @@ struct DMN_LNX_UserX64
StaticAssert(sizeof(DMN_LNX_UserX64) == 912, g_dmn_lnx_user_x64_size_check);
////////////////////////////////
//~ Probes
//~ SDT Probes
typedef struct DMN_LNX_Probe DMN_LNX_Probe;
struct DMN_LNX_Probe
{
String8 provider;
String8 name;
String8 args;
String8 args_string;
STAP_ArgArray args;
U64 pc;
U64 semaphore;
};
@@ -114,22 +115,21 @@ struct DMN_LNX_ProbeList
};
#define DMN_LNX_Probe_XList \
X(InitStart, "init_start") \
X(InitComplete, "init_complete") \
X(RelocStart, "reloc_start") \
X(RelocComplete, "reloc_complete") \
X(MapStart, "map_start") \
X(MapComplete, "map_complete") \
X(UnmapStart, "unmap_start") \
X(UnmapComplete, "unmap_complete") \
X(LongJmp, "longjmp") \
X(LongJmpTarget, "longjmp_target") \
X(SetJmp, "setjmp")
X(InitStart, 2, "init_start") \
X(InitComplete, 2, "init_complete") \
X(RelocStart, 2, "reloc_start") \
X(RelocComplete, 3, "reloc_complete") \
X(MapStart, 2, "map_start") \
X(MapComplete, 3, "map_complete") \
X(UnmapStart, 2, "unmap_start") \
X(UnmapComplete, 2, "unmap_complete") \
X(LongJmp, 3, "longjmp") \
X(LongJmpTarget, 3, "longjmp_target") \
X(SetJmp, 3, "setjmp")
typedef enum
{
DMN_LNX_ProbeType_Null,
#define X(_N,...) DMN_LNX_ProbeType_##_N,
DMN_LNX_Probe_XList
#undef X
@@ -204,6 +204,7 @@ struct DMN_LNX_Entity
U64 rdebug_brk_vaddr;
ELF_Class dl_class;
HashTable *loaded_modules_ht;
DMN_LNX_Probe **probes;
U64 probe_vaddrs[DMN_LNX_ProbeType_Count];
// process x64
@@ -220,7 +221,7 @@ struct DMN_LNX_Entity
U64 phvaddr;
U64 phentsize;
U64 phcount;
B8 is_active;
B8 is_live;
};
typedef struct DMN_LNX_EntityNode DMN_LNX_EntityNode;
@@ -324,6 +325,8 @@ internal B32 dmn_lnx_thread_write_sp(DMN_LNX_Entity *thread, U64 sp);
internal B32 dmn_lnx_thread_read_reg_block(DMN_LNX_Entity *thread, void *reg_block);
internal B32 dmn_lnx_thread_write_reg_block(DMN_LNX_Entity *thread, void *reg_block);
////////////////////////////////
internal B32 dmn_lnx_set_single_step_flag(DMN_LNX_Entity *thread, B32 is_on);
#endif // DEMON_CORE_LINUX_H
+2
View File
@@ -230,6 +230,7 @@
#include "regs/rdi/regs_rdi.h"
#include "dbg_info/dbg_info.h"
#include "disasm/disasm.h"
#include "stap/stap_parse.h"
#include "demon/demon_inc.h"
#include "eval/eval_inc.h"
#include "eval_visualization/eval_visualization_inc.h"
@@ -280,6 +281,7 @@
#include "regs/rdi/regs_rdi.c"
#include "dbg_info/dbg_info.c"
#include "disasm/disasm.c"
#include "stap/stap_parse.c"
#include "demon/demon_inc.c"
#include "eval/eval_inc.c"
#include "eval_visualization/eval_visualization_inc.c"