From 952afd49a7da60bcd92826a7c185467d6a5e7a1e Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Tue, 16 Dec 2025 10:40:48 -0800 Subject: [PATCH] generalize machine ops --- src/base/base_core.h | 23 ++++++ src/ctrl/ctrl_core.c | 111 ++++++++--------------------- src/demon/linux/demon_core_linux.c | 18 ++--- src/dwarf/dwarf_unwind.c | 32 ++++----- src/dwarf/dwarf_unwind.h | 2 +- src/gnu/gnu_parse.c | 14 ++-- src/gnu/gnu_parse.h | 8 +-- 7 files changed, 89 insertions(+), 119 deletions(-) diff --git a/src/base/base_core.h b/src/base/base_core.h index 80ffc909..3455562a 100644 --- a/src/base/base_core.h +++ b/src/base/base_core.h @@ -678,6 +678,29 @@ union Guid }; StaticAssert(sizeof(Guid) == 16, g_guid_size_check); +//////////////////////////////// +//~ Machine Ops + +typedef enum MachineOpResult +{ + MachineOpResult_Null, + MachineOpResult_Ok, + MachineOpResult_Fail, + MachineOpResult_Maybe, +} MachineOpResult; + +#define MACHINE_OP_REG_READ(name) MachineOpResult name(U64 reg_id, void *buffer, U64 buffer_max, void *ud) +typedef MACHINE_OP_REG_READ(MachineOp_RegRead); + +#define MACHINE_OP_REG_WRITE(name) MachineOpResult name(U64 reg_id, void *value, U64 value_size, void *ud) +typedef MACHINE_OP_REG_WRITE(MachineOp_RegWrite); + +#define MACHINE_OP_MEM_READ(name) MachineOpResult name(U64 addr, void *buffer, U64 buffer_size, void *ud) +typedef MACHINE_OP_MEM_READ(MachineOp_MemRead); + +#define MACHINE_OP_MEM_WRITE(name) MachineOpResult name(U64 addr, void *value, U64 value_size, void *ud) +typedef MACHINE_OP_MEM_WRITE(MachineOp_MemWrite); + //////////////////////////////// //~ rjf: Basic Constants diff --git a/src/ctrl/ctrl_core.c b/src/ctrl/ctrl_core.c index 883e6358..9477b835 100644 --- a/src/ctrl/ctrl_core.c +++ b/src/ctrl/ctrl_core.c @@ -1833,93 +1833,42 @@ typedef struct } CTRL_MemoryReadContextDwarfX64; internal -DW_MEM_READ(ctrl_unwind_mem_read_dwarf_x64) +MACHINE_OP_MEM_READ(ctrl_machine_mem_read) { CTRL_MemoryReadContextDwarfX64 *ctx = ud; B32 is_stale = 0; - B32 is_read = ctrl_process_memory_read(ctx->process_handle, r1u64(addr, addr + size), &is_stale, buffer, ctx->endt_us); + B32 is_read = ctrl_process_memory_read(ctx->process_handle, r1u64(addr, addr + buffer_size), &is_stale, buffer, ctx->endt_us); - DW_UnwindStatus status = DW_UnwindStatus_Fail; + MachineOpResult status = MachineOpResult_Fail; if(is_stale) { - status = DW_UnwindStatus_Maybe; + status = MachineOpResult_Maybe; } else if(is_read) { - status = DW_UnwindStatus_Ok; - } - - return status; -} - -internal -DW_REG_READ(ctrl_unwind_reg_read_dwarf_x64) -{ - // map DWARF register to -> the internal register - REGS_RegBlockX64 *regs = ud; - U64 reg_size = 0; void *reg_bytes = 0; - switch(reg_id) - { -#define X(_N, _ID, _MAP_N, ...) case _ID: { reg_size = sizeof(regs->_MAP_N); reg_bytes = ®s->_MAP_N; } break; - DW_Regs_X64_XList(X) -#undef X - default: { InvalidPath; } break; - } - - // copy out register value - DW_UnwindStatus status = DW_UnwindStatus_Fail; - if(reg_size > 0) - { - AssertAlways(reg_size == buffer_max); - MemoryCopy(buffer, reg_bytes, reg_size); - status = DW_UnwindStatus_Ok; - } - - return status; -} - -internal -DW_REG_WRITE(ctrl_unwind_reg_write_dwarf_x64) -{ - // map DWARF register to -> the internal register - REGS_RegBlockX64 *regs = ud; - U64 reg_size = 0; void *reg_bytes = 0; - switch(reg_id) - { -#define X(_N, _ID, _MAP_N, ...) case _ID: { reg_size = sizeof(regs->_MAP_N); reg_bytes = ®s->_MAP_N; } break; - DW_Regs_X64_XList(X) -#undef X - default: { InvalidPath; } break; - } - - // write value to the register - DW_UnwindStatus status = DW_UnwindStatus_Fail; - if(reg_size > 0) - { - AssertAlways(value_size <= reg_size); - MemoryCopy(reg_bytes, value, value_size); - status = DW_UnwindStatus_Ok; + status = MachineOpResult_Ok; } return status; } internal CTRL_UnwindStepResult -ctrl_unwind_step_result_from_dwarf_status(DW_UnwindStatus s) +ctrl_unwind_step_result_from_machine_op_result(MachineOpResult s) { CTRL_UnwindStepResult result = {0}; switch(s) { - case DW_UnwindStatus_Ok: + case MachineOpResult_Null: {} break; + case MachineOpResult_Ok: { result.flags &= ~(CTRL_UnwindFlag_Error|CTRL_UnwindFlag_Stale); }break; - case DW_UnwindStatus_Fail: + case MachineOpResult_Fail: { result.flags |= CTRL_UnwindFlag_Error; }break; - case DW_UnwindStatus_Maybe: + case MachineOpResult_Maybe: { result.flags &= ~CTRL_UnwindFlag_Error; result.flags |= CTRL_UnwindFlag_Stale; @@ -2083,8 +2032,8 @@ ctrl_establish_frame_unwind_context__dwarf(Arena *arena, CTRL_Handle process_han // setup machine ops void *mem_read_ctx = 0; void *reg_read_ctx = 0; - DW_MemRead *mem_read_func = 0; - DW_RegRead *reg_read_func = 0; + MachineOp_MemRead *mem_read_func = 0; + MachineOp_RegRead *reg_read_func = 0; switch(arch) { case Arch_Null: break; @@ -2094,11 +2043,11 @@ ctrl_establish_frame_unwind_context__dwarf(Arena *arena, CTRL_Handle process_han mem_read_ctx_x64->process_handle = process_handle; mem_read_ctx_x64->endt_us = endt_us; - mem_read_ctx = mem_read_ctx_x64; - reg_read_ctx = regs; + mem_read_ctx = mem_read_ctx_x64; + reg_read_ctx = regs; - mem_read_func = ctrl_unwind_mem_read_dwarf_x64; - reg_read_func = ctrl_unwind_reg_read_dwarf_x64; + mem_read_func = ctrl_machine_mem_read; + reg_read_func = regs_read_dwarf_x64; }break; case Arch_x86: case Arch_arm64: @@ -2111,10 +2060,10 @@ ctrl_establish_frame_unwind_context__dwarf(Arena *arena, CTRL_Handle process_han // compute CFA for the row U64 cfa = 0; - DW_UnwindStatus unwind_status = dw_compute_cfa(arch, cfi_row, mem_read_func, mem_read_ctx, reg_read_func, reg_read_ctx, &cfa); + MachineOpResult unwind_status = dw_compute_cfa(arch, cfi_row, mem_read_func, mem_read_ctx, reg_read_func, reg_read_ctx, &cfa); // on success fill out output - if(unwind_status == DW_UnwindStatus_Ok) + if(unwind_status == MachineOpResult_Ok) { ctx_out->cfa = cfa; ctx_out->cfi_row = cfi_row; @@ -2122,7 +2071,7 @@ ctrl_establish_frame_unwind_context__dwarf(Arena *arena, CTRL_Handle process_han } // translate unwind status code - result = ctrl_unwind_step_result_from_dwarf_status(unwind_status); + result = ctrl_unwind_step_result_from_machine_op_result(unwind_status); } } } @@ -2142,9 +2091,9 @@ ctrl_unwind_step__dwarf(CTRL_Handle process_handle, Arch arch, void *regs, CTRL_ void *mem_read_ctx = 0; void *reg_read_ctx = 0; void *reg_write_ctx = 0; - DW_MemRead *mem_read_func = 0; - DW_RegRead *reg_read_func = 0; - DW_RegWrite *reg_write_func = 0; + MachineOp_MemRead *mem_read_func = 0; + MachineOp_RegRead *reg_read_func = 0; + MachineOp_RegWrite *reg_write_func = 0; switch(arch) { case Arch_Null: break; @@ -2158,9 +2107,9 @@ ctrl_unwind_step__dwarf(CTRL_Handle process_handle, Arch arch, void *regs, CTRL_ reg_read_ctx = regs; reg_write_ctx = regs; - mem_read_func = ctrl_unwind_mem_read_dwarf_x64; - reg_read_func = ctrl_unwind_reg_read_dwarf_x64; - reg_write_func = ctrl_unwind_reg_write_dwarf_x64; + mem_read_func = ctrl_machine_mem_read; + reg_read_func = regs_read_dwarf_x64; + reg_write_func = regs_write_dwarf_x64; }break; case Arch_x86: case Arch_arm64: @@ -2172,7 +2121,7 @@ ctrl_unwind_step__dwarf(CTRL_Handle process_handle, Arch arch, void *regs, CTRL_ } // apply register rules to the context - DW_UnwindStatus unwind_status = dw_cfi_apply_register_rules(arch, + MachineOpResult unwind_status = dw_cfi_apply_register_rules(arch, frame_ctx->cfa, frame_ctx->cfi_row, mem_read_func, @@ -2189,7 +2138,7 @@ ctrl_unwind_step__dwarf(CTRL_Handle process_handle, Arch arch, void *regs, CTRL_ } // translate unwind status code - result = ctrl_unwind_step_result_from_dwarf_status(unwind_status); + result = ctrl_unwind_step_result_from_machine_op_result(unwind_status); scratch_end(scratch); return result; @@ -3812,10 +3761,10 @@ ctrl_thread__append_program_defined_bp_traps(Arena *arena, CTRL_Entity *bp, DMN_ //- rjf: module lifetime open/close work internal -DW_MEM_READ(ctrl_dmn_mem_read) +MACHINE_OP_MEM_READ(ctrl_dmn_mem_read) { - U64 read = dmn_process_read(((CTRL_Handle *)ud)->dmn_handle, r1u64(addr, addr + size), buffer); - return read == size ? DW_UnwindStatus_Ok : DW_UnwindStatus_Fail; + U64 read = dmn_process_read(((CTRL_Handle *)ud)->dmn_handle, r1u64(addr, addr + buffer_size), buffer); + return read == buffer_size ? MachineOpResult_Ok : MachineOpResult_Fail; } internal void diff --git a/src/demon/linux/demon_core_linux.c b/src/demon/linux/demon_core_linux.c index 9488dda6..56d4602e 100644 --- a/src/demon/linux/demon_core_linux.c +++ b/src/demon/linux/demon_core_linux.c @@ -117,6 +117,12 @@ dmn_lnx_read_string(Arena *arena, int memory_fd, U64 vaddr) return dmn_lnx_read_string_capped(arena, memory_fd, vaddr, 4096); } +internal +MACHINE_OP_MEM_READ(dmn_lnx_machine_op_mem_read) +{ + return dmn_lnx_read(((DMN_LNX_Entity *)ud)->fd, r1u64(addr, addr + buffer_size), buffer); +} + internal int dmn_lnx_ptrace_seize(pid_t pid) { @@ -673,12 +679,6 @@ dmn_lnx_dl_path_from_pid(Arena *arena, pid_t pid, U64 auxv_base) return dl_path; } -internal -GNU_MEM_READ(dmn_lnx_gnu_mem_read) -{ - return dmn_lnx_read(((DMN_LNX_Entity *)ud)->fd, r1u64(addr, addr + size), buffer); -} - //////////////////////////////// //~ SDT Probes @@ -1799,9 +1799,9 @@ dmn_lnx_hanlde_unload_module(Arena *arena, DMN_EventList *events, DMN_LNX_Entity // mark live modules B32 is_64bit = process->dl_class == ELF_Class_64; - GNU_RDebugInfoList rdebug_list = gnu_parse_rdebug(scratch.arena, is_64bit, rdebug_vaddr, dmn_lnx_gnu_mem_read, process); + GNU_RDebugInfoList rdebug_list = gnu_parse_rdebug(scratch.arena, is_64bit, rdebug_vaddr, dmn_lnx_machine_op_mem_read, process); for EachNode(rdebug_n, GNU_RDebugInfoNode, rdebug_list.first) { - GNU_LinkMapList link_map_list = gnu_parse_link_map_list(scratch.arena, is_64bit, rdebug_n->v.r_map, dmn_lnx_gnu_mem_read, process); + GNU_LinkMapList link_map_list = gnu_parse_link_map_list(scratch.arena, is_64bit, rdebug_n->v.r_map, dmn_lnx_machine_op_mem_read, process); for EachNode(link_map_n, GNU_LinkMapNode, link_map_list.first) { DMN_LNX_Entity *module = hash_table_search_u64_raw(process->loaded_modules_ht, link_map_n->v.addr_vaddr); module->is_live = 1; @@ -2078,7 +2078,7 @@ dmn_lnx_handle_attach(Arena *arena, DMN_EventList *events, pid_t pid) // extract modules from r_debug B32 is_64bit = process->dl_class == ELF_Class_64; - GNU_RDebugInfoList rdebug_list = gnu_parse_rdebug(scratch.arena, is_64bit, process->rdebug_vaddr, dmn_lnx_gnu_mem_read, process); + GNU_RDebugInfoList rdebug_list = gnu_parse_rdebug(scratch.arena, is_64bit, process->rdebug_vaddr, dmn_lnx_machine_op_mem_read, process); U64 name_space_id = 0; for EachNode(rdebug_n, GNU_RDebugInfoNode, rdebug_list.first) { diff --git a/src/dwarf/dwarf_unwind.c b/src/dwarf/dwarf_unwind.c index 27c37e66..d0130373 100644 --- a/src/dwarf/dwarf_unwind.c +++ b/src/dwarf/dwarf_unwind.c @@ -236,14 +236,14 @@ dw_cfi_row_from_pc(Arena *arena, Arch arch, DW_CIE *cie, DW_FDE *fde, DW_DecodeP return result; } -internal DW_UnwindStatus +internal MachineOpResult dw_compute_cfa(Arch arch, DW_CFI_Row *row, - DW_MemRead *mem_read_func, void *mem_read_ud, - DW_RegRead *reg_read_func, void *reg_read_ud, + MachineOp_MemRead *mem_read_func, void *mem_read_ud, + MachineOp_RegRead *reg_read_func, void *reg_read_ud, U64 *cfa_out) { - DW_UnwindStatus unwind_status = DW_UnwindStatus_Fail; + MachineOpResult unwind_status = MachineOpResult_Fail; switch (row->cfa.rule) { case DW_CFA_Rule_Null: break; @@ -253,7 +253,7 @@ dw_compute_cfa(Arch arch, U64 reg_size = dw_reg_size_from_code(arch, row->cfa.reg); AssertAlways(reg_size <= sizeof(cfa_reg_value)); unwind_status = reg_read_func(row->cfa.reg, &cfa_reg_value, reg_size, reg_read_ud); - if (unwind_status != DW_UnwindStatus_Ok) { break; } + if (unwind_status != MachineOpResult_Ok) { break; } *cfa_out = cfa_reg_value + row->cfa.off; } break; case DW_CFA_Rule_Expression: { @@ -265,16 +265,16 @@ dw_compute_cfa(Arch arch, return unwind_status; } -internal DW_UnwindStatus +internal MachineOpResult dw_cfi_apply_register_rules(Arch arch, U64 cfa, DW_CFI_Row *row, - DW_MemRead *mem_read_func, void *mem_read_ud, - DW_RegRead *reg_read_func, void *reg_read_ud, - DW_RegWrite *reg_write_func, void *reg_write_ud) + MachineOp_MemRead *mem_read_func, void *mem_read_ud, + MachineOp_RegRead *reg_read_func, void *reg_read_ud, + MachineOp_RegWrite *reg_write_func, void *reg_write_ud) { Temp scratch = scratch_begin(0,0); - DW_UnwindStatus unwind_status = DW_UnwindStatus_Ok; + MachineOpResult unwind_status = MachineOpResult_Ok; U64 max_reg_size = dw_reg_max_size_from_arch(arch); void *reg_buffer = push_array(scratch.arena, U8, max_reg_size); @@ -289,12 +289,12 @@ dw_cfi_apply_register_rules(Arch arch, // read register value from memory U64 addr = cfa + reg->n; U64 reg_size = dw_reg_size_from_code(arch, reg_idx); - unwind_status = mem_read_func(addr, reg_size, reg_buffer, mem_read_ud); - if (unwind_status != DW_UnwindStatus_Ok) { goto exit; } + unwind_status = mem_read_func(addr, reg_buffer, reg_size, mem_read_ud); + if (unwind_status != MachineOpResult_Ok) { goto exit; } // write register value to the thread context unwind_status = reg_write_func(reg_idx, reg_buffer, reg_size, reg_write_ud); - if (unwind_status != DW_UnwindStatus_Ok) { goto exit; } + if (unwind_status != MachineOpResult_Ok) { goto exit; } } break; case DW_CFI_RegisterRule_ValOffset: { // compute register value @@ -303,17 +303,17 @@ dw_cfi_apply_register_rules(Arch arch, // write register value to the thread context U64 reg_size = dw_reg_size_from_code(arch, reg_idx); unwind_status = reg_write_func(reg_idx, ®_value, reg_size, reg_write_ud); - if (unwind_status != DW_UnwindStatus_Ok) { goto exit; } + if (unwind_status != MachineOpResult_Ok) { goto exit; } } break; case DW_CFI_RegisterRule_Register: { // read register value from another register U64 reg_size = dw_reg_size_from_code(arch, reg_idx); unwind_status = reg_read_func(reg->n, reg_buffer, reg_size, reg_read_ud); - if (unwind_status != DW_UnwindStatus_Ok) { goto exit; } + if (unwind_status != MachineOpResult_Ok) { goto exit; } // write register value to the thread context unwind_status = reg_write_func(reg_idx, reg_buffer, reg_size, reg_write_ud); - if (unwind_status != DW_UnwindStatus_Ok) { goto exit; } + if (unwind_status != MachineOpResult_Ok) { goto exit; } } break; case DW_CFI_RegisterRule_Expression: { // TODO: evaluate expression diff --git a/src/dwarf/dwarf_unwind.h b/src/dwarf/dwarf_unwind.h index 904b7df4..510057e2 100644 --- a/src/dwarf/dwarf_unwind.h +++ b/src/dwarf/dwarf_unwind.h @@ -74,7 +74,7 @@ internal DW_CFI_Unwind * dw_cfi_unwind_init(Arena *arena, Arch arch, DW_CIE *cie internal B32 dw_cfi_next_row(Arena *arena, DW_CFI_Unwind *uw); internal DW_CFI_Row * dw_cfi_row_from_pc(Arena *arena, Arch arch, struct DW_CIE *cie, struct DW_FDE *fde, DW_DecodePtr *decode_ptr_func, void *decode_ptr_ctx, U64 pc); -internal DW_UnwindStatus dw_cfi_apply_register_rules(Arch arch, U64 cfa, DW_CFI_Row *row, DW_MemRead *mem_read_func, void *mem_read_ud, DW_RegRead *reg_read_func, void *reg_read_ud, DW_RegWrite *reg_write_func, void *reg_write_ud); +internal MachineOpResult dw_cfi_apply_register_rules(Arch arch, U64 cfa, DW_CFI_Row *row, MachineOp_MemRead *mem_read_func, void *mem_read_ud, MachineOp_RegRead *reg_read_func, void *reg_read_ud, MachineOp_RegWrite *reg_write_func, void *reg_write_ud); #endif // DWARF_UNWIND_H diff --git a/src/gnu/gnu_parse.c b/src/gnu/gnu_parse.c index 5ea387a9..f68dc5d5 100644 --- a/src/gnu/gnu_parse.c +++ b/src/gnu/gnu_parse.c @@ -2,24 +2,24 @@ // Licensed under the MIT license (https://opensource.org/license/mit/) internal GNU_RDebugInfoList -gnu_parse_rdebug(Arena *arena, B32 is_64bit, U64 first_rdebug_vaddr, GNU_MemRead *mem_read_func, void *mem_read_ud) +gnu_parse_rdebug(Arena *arena, B32 is_64bit, U64 first_rdebug_vaddr, MachineOp_MemRead *mem_read_func, void *mem_read_ud) { GNU_RDebugInfoList result = {0}; for (U64 rdebug_vaddr = first_rdebug_vaddr, rdebug_next = 0; rdebug_vaddr != 0; rdebug_vaddr = rdebug_next) { GNU_RDebugInfo64 rdebug; if (is_64bit) { - U64 rdebug_read_size = mem_read_func(rdebug_vaddr, sizeof(rdebug), &rdebug, mem_read_ud); + U64 rdebug_read_size = mem_read_func(rdebug_vaddr, &rdebug, sizeof(rdebug),mem_read_ud); if (rdebug_read_size != sizeof(rdebug)) { goto exit; } } else { GNU_RDebugInfo32 rdebug32; - U64 rdebug_read_size = mem_read_func(rdebug_vaddr, sizeof(rdebug32), &rdebug32, mem_read_ud); + U64 rdebug_read_size = mem_read_func(rdebug_vaddr, &rdebug32, sizeof(rdebug32), mem_read_ud); if (rdebug_read_size != sizeof(rdebug32)) { goto exit; } } if (rdebug.r_version < 1) { goto exit; } if (rdebug.r_version > 1) { - U64 r_next_size = mem_read_func(rdebug_vaddr + sizeof(rdebug), sizeof(rdebug_next), &rdebug_next, mem_read_ud); + U64 r_next_size = mem_read_func(rdebug_vaddr + sizeof(rdebug), &rdebug_next, sizeof(rdebug_next), mem_read_ud); if (r_next_size != sizeof(rdebug_next)) { goto exit; } } @@ -34,18 +34,18 @@ gnu_parse_rdebug(Arena *arena, B32 is_64bit, U64 first_rdebug_vaddr, GNU_MemRead } internal GNU_LinkMapList -gnu_parse_link_map_list(Arena *arena, B32 is_64bit, U64 first_link_map_vaddr, GNU_MemRead *mem_read_func, void *mem_read_ud) +gnu_parse_link_map_list(Arena *arena, B32 is_64bit, U64 first_link_map_vaddr, MachineOp_MemRead *mem_read_func, void *mem_read_ud) { GNU_LinkMapList result = {0}; GNU_LinkMap64 link_map = {0}; for (U64 link_map_vaddr = first_link_map_vaddr; link_map_vaddr != 0; link_map_vaddr = link_map.next_vaddr) { if (is_64bit) { - U64 link_map_size = mem_read_func(link_map_vaddr, sizeof(link_map), &link_map, mem_read_ud); + U64 link_map_size = mem_read_func(link_map_vaddr, &link_map, sizeof(link_map), mem_read_ud); if (link_map_size != sizeof(link_map)) { goto exit; } } else { GNU_LinkMap32 link_map32 = {0}; - U64 link_map_size = mem_read_func(link_map_vaddr, sizeof(link_map32), &link_map32, mem_read_ud); + U64 link_map_size = mem_read_func(link_map_vaddr, &link_map32, sizeof(link_map32), mem_read_ud); if (link_map_size != sizeof(link_map32)) { goto exit; } gnu_linkmap64_from_linkmap32(link_map32); } diff --git a/src/gnu/gnu_parse.h b/src/gnu/gnu_parse.h index 79f9a9ec..57fd74e3 100644 --- a/src/gnu/gnu_parse.h +++ b/src/gnu/gnu_parse.h @@ -30,12 +30,10 @@ typedef struct GNU_RDebugInfoList GNU_RDebugInfoNode *last; } GNU_RDebugInfoList; -#define GNU_MEM_READ(name) U64 name(U64 addr, U64 size, void *buffer, void *ud) -typedef GNU_MEM_READ(GNU_MemRead); - //////////////////////////////// -internal GNU_RDebugInfoList gnu_parse_rdebug(Arena *arena, B32 is_64bit, U64 first_rdebug_vaddr, GNU_MemRead *mem_read_func, void *mem_read_ud); -internal GNU_LinkMapList gnu_parse_link_map_list(Arena *arena, B32 is_64bit, U64 first_link_map_vaddr, GNU_MemRead *mem_read_func, void *mem_read_ud); + +internal GNU_RDebugInfoList gnu_parse_rdebug(Arena *arena, B32 is_64bit, U64 first_rdebug_vaddr, MachineOp_MemRead *mem_read_func, void *mem_read_ud); +internal GNU_LinkMapList gnu_parse_link_map_list(Arena *arena, B32 is_64bit, U64 first_link_map_vaddr, MachineOp_MemRead *mem_read_func, void *mem_read_ud); #endif