connect DWARF virtual stack unwinder

This commit is contained in:
Nikita Smith
2025-11-07 14:39:45 -08:00
committed by Ryan Fleury
parent da38f266d9
commit 3b81e1a0e9
2 changed files with 332 additions and 15 deletions
+319 -15
View File
@@ -1782,6 +1782,17 @@ ctrl_raddbg_data_from_module(Arena *arena, CTRL_Handle module_handle)
return result; return result;
} }
////////////////////////////////
//~ Process Info Functions
internal Arch
ctrl_arch_from_process_handle(CTRL_Handle process_handle)
{
CTRL_EntityCtx *entity_ctx = &ctrl_state->ctrl_thread_entity_store->ctx;
CTRL_Entity *process_entity = ctrl_entity_from_handle(entity_ctx, process_handle);
return process_entity->arch;
}
//////////////////////////////// ////////////////////////////////
//~ rjf: Unwinding Functions //~ rjf: Unwinding Functions
@@ -1806,6 +1817,239 @@ ctrl_unwind_deep_copy(Arena *arena, Arch arch, CTRL_Unwind *src)
return dst; return dst;
} }
//- DWARF
typedef struct
{
CTRL_Handle process_handle;
U64 endt_us;
} CTRL_MemoryReadContextDwarfX64;
internal
DW_MEM_READ(ctrl_unwind_mem_read_dwarf_x64)
{
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);
DW_UnwindStatus status = DW_UnwindStatus_Fail;
if(is_stale && is_read)
{
status = DW_UnwindStatus_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 = &regs->_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 = &regs->_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;
}
return status;
}
internal CTRL_UnwindStepResult
ctrl_unwind_step__dwarf(CTRL_Handle process_handle, CTRL_Handle module_handle, Arch arch, void *regs, U64 endt_us)
{
CTRL_UnwindStepResult result = { .flags = CTRL_UnwindFlag_Error };
// gather context for virtual stack unwinder
U64 rebase = 0;
B32 is_unwind_data_eh = 0;
String8 unwind_data = {0};
EH_FrameHdr eh_frame_hdr = {0};
EH_PtrCtx eh_ptr_ctx = {0};
{
U64 hash = ctrl_hash_from_handle(module_handle);
U64 slot_idx = hash % ctrl_state->module_image_info_cache.slots_count;
U64 stripe_idx = slot_idx % ctrl_state->module_image_info_cache.stripes_count;
CTRL_ModuleImageInfoCacheSlot *slot = &ctrl_state->module_image_info_cache.slots[slot_idx];
CTRL_ModuleImageInfoCacheStripe *stripe = &ctrl_state->module_image_info_cache.stripes[stripe_idx];
MutexScopeR(stripe->rw_mutex) for EachNode(n, CTRL_ModuleImageInfoCacheNode, slot->first)
{
if(ctrl_handle_match(n->module, module_handle))
{
rebase = n->rebase;
is_unwind_data_eh = n->is_dwarf_unwind_data_eh;
unwind_data = n->dwarf_unwind_data;
eh_frame_hdr = n->eh_frame_hdr;
eh_ptr_ctx = n->eh_ptr_ctx;
break;
}
}
}
// rebase IP to the base used for linking the module
U64 ip = regs_rip_from_arch_block(arch, regs);
U64 cfi_ip = ip + rebase;
// use .eh_frame_hdr to quickly locate nearest FDE offset
U64 fde_offset = eh_find_nearest_fde(eh_frame_hdr, &eh_ptr_ctx, cfi_ip);
if(fde_offset < unwind_data.size)
{
// parse call frame info
DW_CIE cie = {0};
DW_FDE fde = {0};
B32 is_cfi_parsed = is_unwind_data_eh ?
eh_parse_cfi(unwind_data, fde_offset, arch, &eh_ptr_ctx, &cie, &fde) :
dw_parse_cfi(unwind_data, fde_offset, arch, &cie, &fde);
if(is_cfi_parsed)
{
Temp scratch = scratch_begin(0, 0);
// setup pointer decoder ops
DW_DecodePtr *decode_ptr_func = 0;
void *decode_ptr_ctx = 0;
if(is_unwind_data_eh)
{
EH_DecodePtrCtx *decode_ptr_ctx_eh = push_array(scratch.arena, EH_DecodePtrCtx, 1);
decode_ptr_ctx_eh->ptr_ctx = &eh_ptr_ctx;
decode_ptr_ctx_eh->addr_enc = cie.ext[EH_CIE_Ext_AddrEnc];
decode_ptr_func = eh_decode_ptr;
decode_ptr_ctx = decode_ptr_ctx_eh;
}
else
{
decode_ptr_func = dw_decode_ptr_debug_frame;
decode_ptr_ctx = &cie;
}
// find register rules for IP
DW_CFI_Row *cfi_row = dw_cfi_row_from_pc(scratch.arena, arch, &cie, &fde, decode_ptr_func, decode_ptr_ctx, cfi_ip);
if(cfi_row)
{
// setup machine ops
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;
switch(arch)
{
case Arch_Null: break;
case Arch_x64:
{
CTRL_MemoryReadContextDwarfX64 *mem_read_ctx_x64 = push_array(scratch.arena, CTRL_MemoryReadContextDwarfX64, 1);
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;
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;
}break;
case Arch_x86:
case Arch_arm64:
case Arch_arm32:
{
NotImplemented;
}break;
default: { InvalidPath; } break;
}
// apply register rules to the context
DW_UnwindStatus cfi_uw_status = dw_cfi_apply_register_rules(arch,
&cie,
cfi_row,
mem_read_func,
mem_read_ctx,
reg_read_func,
reg_read_ctx,
reg_write_func,
reg_write_ctx);
// translate unwind status code to control layer's result flags
switch(cfi_uw_status)
{
case DW_UnwindStatus_Ok:
{
result.flags &= ~(CTRL_UnwindFlag_Error|CTRL_UnwindFlag_Stale);
}break;
case DW_UnwindStatus_Fail:
{
result.flags |= CTRL_UnwindFlag_Error;
}break;
case DW_UnwindStatus_Maybe:
{
result.flags &= ~CTRL_UnwindFlag_Error;
result.flags |= CTRL_UnwindFlag_Stale;
}break;
default: { InvalidPath; } break;
}
}
scratch_end(scratch);
}
else
{
Assert(0 && "failed to parse CFI");
}
}
else
{
// TODO: if IP does not have FDE, does this mean function is a leaf?
}
return result;
}
//- rjf: [x64] //- rjf: [x64]
internal REGS_Reg64 * internal REGS_Reg64 *
@@ -2652,13 +2896,19 @@ internal CTRL_UnwindStepResult
ctrl_unwind_step(CTRL_Handle process, CTRL_Handle module, U64 module_base_vaddr, Arch arch, void *reg_block, U64 endt_us) ctrl_unwind_step(CTRL_Handle process, CTRL_Handle module, U64 module_base_vaddr, Arch arch, void *reg_block, U64 endt_us)
{ {
CTRL_UnwindStepResult result = {0}; CTRL_UnwindStepResult result = {0};
switch(arch)
result = ctrl_unwind_step__dwarf(process, module, arch, reg_block, endt_us);
if(result.flags != 0)
{ {
default:{}break; switch(arch)
case Arch_x64:
{ {
result = ctrl_unwind_step__pe_x64(process, module, module_base_vaddr, (REGS_RegBlockX64 *)reg_block, endt_us); default:{}break;
}break; case Arch_x64:
{
result = ctrl_unwind_step__pe_x64(process, module, module_base_vaddr, (REGS_RegBlockX64 *)reg_block, endt_us);
}break;
}
} }
return result; return result;
} }
@@ -3147,7 +3397,11 @@ ctrl_thread__entry_point(void *p)
scratch_end(scratch); scratch_end(scratch);
} }
//- rjf: gather all touched files by user breakpoints
//- rjf: gather all touched files by user breakpoints
for(CTRL_UserBreakpointNode *n = msg->user_bps.first; n != 0; n = n->next) for(CTRL_UserBreakpointNode *n = msg->user_bps.first; n != 0; n = n->next)
{ {
if(n->v.kind != CTRL_UserBreakpointKind_FileNameAndLineColNumber) if(n->v.kind != CTRL_UserBreakpointKind_FileNameAndLineColNumber)
@@ -3392,8 +3646,15 @@ ctrl_thread__module_open(CTRL_Handle process, CTRL_Handle module, Rng1U64 vaddr_
//- rjf: parse module image info //- rjf: parse module image info
// //
Arena *arena = arena_alloc(); Arena *arena = arena_alloc();
COFF_MachineType machine = COFF_MachineType_Unknown;
PE_ImageFileCharacteristics file_characteristics = 0;
PE_IntelPdata *pdatas = 0; PE_IntelPdata *pdatas = 0;
U64 pdatas_count = 0; U64 pdatas_count = 0;
U64 rebase = 0;
B32 is_dwarf_unwind_data_eh = 0;
String8 dwarf_unwind_data = {0};
EH_FrameHdr eh_frame_hdr = {0};
EH_PtrCtx eh_ptr_ctx = {0};
U64 entry_point_voff = 0; U64 entry_point_voff = 0;
Rng1U64 tls_vaddr_range = {0}; Rng1U64 tls_vaddr_range = {0};
U32 pdb_dbg_time = 0; U32 pdb_dbg_time = 0;
@@ -3438,7 +3699,12 @@ ctrl_thread__module_open(CTRL_Handle process, CTRL_Handle module, Rng1U64 vaddr_
COFF_FileHeader file_header = {0}; COFF_FileHeader file_header = {0};
if(is_valid) if(is_valid)
{ {
if(!dmn_process_read_struct(process.dmn_handle, vaddr_range.min + file_header_off, &file_header)) if(dmn_process_read_struct(process.dmn_handle, vaddr_range.min + file_header_off, &file_header))
{
machine = file_header.machine;
file_characteristics = file_header.flags;
}
else
{ {
is_valid = 0; is_valid = 0;
} }
@@ -3462,24 +3728,24 @@ ctrl_thread__module_open(CTRL_Handle process, CTRL_Handle module, Rng1U64 vaddr_
// rjf: read info // rjf: read info
U32 reported_data_dir_offset = 0; U32 reported_data_dir_offset = 0;
U32 reported_data_dir_count = 0; U32 reported_data_dir_count = 0;
switch(opt_ext_magic) switch(opt_ext_magic)
{ {
case PE_PE32_MAGIC: case PE_PE32_MAGIC:
{ {
PE_OptionalHeader32 pe_optional = {0}; PE_OptionalHeader32 pe_optional = {0};
dmn_process_read_struct(process.dmn_handle, vaddr_range.min + opt_ext_off_range.min, &pe_optional); dmn_process_read_struct(process.dmn_handle, vaddr_range.min + opt_ext_off_range.min, &pe_optional);
entry_point = pe_optional.entry_point_va; entry_point = pe_optional.entry_point_va;
reported_data_dir_offset = sizeof(pe_optional); reported_data_dir_offset = sizeof(pe_optional);
reported_data_dir_count = pe_optional.data_dir_count; reported_data_dir_count = pe_optional.data_dir_count;
}break; }break;
case PE_PE32PLUS_MAGIC: case PE_PE32PLUS_MAGIC:
{ {
PE_OptionalHeader32Plus pe_optional = {0}; PE_OptionalHeader32Plus pe_optional = {0};
dmn_process_read_struct(process.dmn_handle, vaddr_range.min + opt_ext_off_range.min, &pe_optional); dmn_process_read_struct(process.dmn_handle, vaddr_range.min + opt_ext_off_range.min, &pe_optional);
entry_point = pe_optional.entry_point_va; entry_point = pe_optional.entry_point_va;
reported_data_dir_offset = sizeof(pe_optional); reported_data_dir_offset = sizeof(pe_optional);
reported_data_dir_count = pe_optional.data_dir_count; reported_data_dir_count = pe_optional.data_dir_count;
}break; }break;
} }
@@ -3607,7 +3873,7 @@ ctrl_thread__module_open(CTRL_Handle process, CTRL_Handle module, Rng1U64 vaddr_
exe_dbg_path = path; exe_dbg_path = path;
} }
} }
// rjf: extract copy of module's raddbg data // rjf: extract copy of module's raddbg data
{ {
for EachIndex(idx, sec_count) for EachIndex(idx, sec_count)
@@ -3636,6 +3902,41 @@ ctrl_thread__module_open(CTRL_Handle process, CTRL_Handle module, Rng1U64 vaddr_
U8 new_value = 1; U8 new_value = 1;
dmn_process_write_struct(process.dmn_handle, vaddr_range.min + raddbg_is_attached_section_voff_range.min, &new_value); dmn_process_write_struct(process.dmn_handle, vaddr_range.min + raddbg_is_attached_section_voff_range.min, &new_value);
} }
#if 0
// TODO(ns): temporary hack to test DWARF unwinder on windows -- remove when done
{
for EachIndex(idx, sec_count)
{
String8 section_name = str8_cstring((char *)sec[idx].name);
if(str8_match(section_name, str8_lit("/130"), 0))
{
U64 restore_pos = arena_pos(arena);
Rng1U64 debug_frame_voff_range = rng_1u64(sec[idx].voff, sec[idx].voff + sec[idx].vsize);
Rng1U64 debug_frame_vaddr_range = shift_1u64(debug_frame_voff_range, vaddr_range.min);
void *debug_frame_buffer = push_array(arena, U8, sec[idx].vsize);
U64 debug_frame_read_size = dmn_process_read(process.dmn_handle, debug_frame_vaddr_range, debug_frame_buffer);
if(debug_frame_read_size == sec[idx].vsize)
{
U64 default_base = pe_get_default_base_addr(file_characteristics, machine);
Arch arch = ctrl_arch_from_process_handle(process);
String8 debug_frame = str8(debug_frame_buffer, debug_frame_read_size);
DW_CallFrameInfo dwarf_cfi = dw_call_frame_info_from_data(scratch.arena, arch, debug_frame);
String8 eh_frame_hdr_data = eh_frame_hdr_from_call_frame_info(arena, dwarf_cfi.fde_count, dwarf_cfi.fde_offsets, dwarf_cfi.fde);
rebase = default_base - vaddr_range.min; // DL overwrites image base in the optional header, if image is linked with a custom base this breaks
dwarf_unwind_data = debug_frame;
is_dwarf_unwind_data_eh = 0;
eh_frame_hdr = eh_parse_frame_hdr(eh_frame_hdr_data, byte_size_from_arch(arch), 0);
}
else
{
arena_pop_to(arena, restore_pos);
}
break;
}
}
}
#endif
scratch_end(scratch); scratch_end(scratch);
} }
@@ -3718,10 +4019,13 @@ ctrl_thread__module_open(CTRL_Handle process, CTRL_Handle module, Rng1U64 vaddr_
node->arena = arena; node->arena = arena;
node->pdatas = pdatas; node->pdatas = pdatas;
node->pdatas_count = pdatas_count; node->pdatas_count = pdatas_count;
node->rebase = rebase;
node->is_dwarf_unwind_data_eh = is_dwarf_unwind_data_eh;
node->dwarf_unwind_data = dwarf_unwind_data;
node->eh_frame_hdr = eh_frame_hdr;
node->eh_ptr_ctx = eh_ptr_ctx;
node->entry_point_voff = entry_point_voff; node->entry_point_voff = entry_point_voff;
node->initial_debug_info_path = initial_debug_info_path; node->initial_debug_info_path = initial_debug_info_path;
node->raddbg_section_voff_range = raddbg_section_voff_range;
node->raddbg_data = raddbg_data;
} }
} }
} }
+13
View File
@@ -597,6 +597,11 @@ struct CTRL_ModuleImageInfoCacheNode
Arena *arena; Arena *arena;
PE_IntelPdata *pdatas; PE_IntelPdata *pdatas;
U64 pdatas_count; U64 pdatas_count;
U64 rebase;
B32 is_dwarf_unwind_data_eh;
String8 dwarf_unwind_data;
EH_FrameHdr eh_frame_hdr;
EH_PtrCtx eh_ptr_ctx;
U64 entry_point_voff; U64 entry_point_voff;
Rng1U64 tls_vaddr_range; Rng1U64 tls_vaddr_range;
String8 initial_debug_info_path; String8 initial_debug_info_path;
@@ -915,12 +920,20 @@ internal Rng1U64 ctrl_tls_vaddr_range_from_module(CTRL_Handle module_handle);
internal String8 ctrl_initial_debug_info_path_from_module(Arena *arena, CTRL_Handle module_handle); internal String8 ctrl_initial_debug_info_path_from_module(Arena *arena, CTRL_Handle module_handle);
internal String8 ctrl_raddbg_data_from_module(Arena *arena, CTRL_Handle module_handle); internal String8 ctrl_raddbg_data_from_module(Arena *arena, CTRL_Handle module_handle);
////////////////////////////////
//~ Process Info Functions
internal Arch ctrl_arch_from_process_handle(CTRL_Handle process_handle);
//////////////////////////////// ////////////////////////////////
//~ rjf: Unwinding Functions //~ rjf: Unwinding Functions
//- rjf: unwind deep copier //- rjf: unwind deep copier
internal CTRL_Unwind ctrl_unwind_deep_copy(Arena *arena, Arch arch, CTRL_Unwind *src); internal CTRL_Unwind ctrl_unwind_deep_copy(Arena *arena, Arch arch, CTRL_Unwind *src);
//- DWARF
internal CTRL_UnwindStepResult ctrl_unwind_step__dwarf(CTRL_Handle process_handle, CTRL_Handle module_handle, Arch arch, void *regs, U64 endt_us);
//- rjf: [x64] //- rjf: [x64]
internal REGS_Reg64 *ctrl_unwind_reg_from_pe_gpr_reg__pe_x64(REGS_RegBlockX64 *regs, PE_UnwindGprRegX64 gpr_reg); internal REGS_Reg64 *ctrl_unwind_reg_from_pe_gpr_reg__pe_x64(REGS_RegBlockX64 *regs, PE_UnwindGprRegX64 gpr_reg);
internal CTRL_UnwindStepResult ctrl_unwind_step__pe_x64(CTRL_Handle process_handle, CTRL_Handle module_handle, U64 module_base_vaddr, REGS_RegBlockX64 *regs, U64 endt_us); internal CTRL_UnwindStepResult ctrl_unwind_step__pe_x64(CTRL_Handle process_handle, CTRL_Handle module_handle, U64 module_base_vaddr, REGS_RegBlockX64 *regs, U64 endt_us);