From da38f266d9983ee037e30652832512c5f8c99b1d Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Mon, 13 Oct 2025 10:59:41 -0700 Subject: [PATCH] parser for .eh_frame versions of CIE and FDE and new helper for mapping PC -> CFI Row --- src/dwarf/dwarf_dump.c | 3 +- src/dwarf/dwarf_dump.h | 1 - src/dwarf/dwarf_inc.c | 1 + src/dwarf/dwarf_inc.h | 1 + src/dwarf/dwarf_parse.c | 45 ++++- src/dwarf/dwarf_parse.h | 7 +- src/dwarf/dwarf_unwind.c | 102 +++++++--- src/dwarf/dwarf_unwind.h | 24 +-- src/eh/eh_frame.c | 418 ++++++++++++++++++++++++++++----------- src/eh/eh_frame.h | 47 ++++- src/raddbg/raddbg_main.c | 4 +- 11 files changed, 487 insertions(+), 166 deletions(-) diff --git a/src/dwarf/dwarf_dump.c b/src/dwarf/dwarf_dump.c index d18da87d..4a7ccc32 100644 --- a/src/dwarf/dwarf_dump.c +++ b/src/dwarf/dwarf_dump.c @@ -2237,7 +2237,8 @@ dw_dump_list_from_sections(Arena *arena, String8 cfi_regs_str = {0}; { String8List cfi_regs_list = {0}; - for EachIndex(reg_idx, cfi_unwind->reg_count) { + U64 reg_count = dw_reg_count_from_arch(arch); + for EachIndex(reg_idx, reg_count) { DW_CFI_Register *cfi_reg = &cfi_unwind->row->regs[reg_idx]; String8 rule_str = str8_lit("???"); switch (cfi_reg->rule) { diff --git a/src/dwarf/dwarf_dump.h b/src/dwarf/dwarf_dump.h index 0882061b..4ff7c1c0 100644 --- a/src/dwarf/dwarf_dump.h +++ b/src/dwarf/dwarf_dump.h @@ -61,7 +61,6 @@ read_only global String8 dw_name_title_from_dump_subset_table[] = internal String8 dw_string_from_reg_off(Arena *arena, Arch arch, DW_Reg reg_idx, S64 reg_off); internal String8List dw_string_list_from_expression (Arena *arena, String8 raw_data, U64 cu_base, U64 address_size, Arch arch, DW_Version ver, DW_Ext ext, DW_Format format); internal String8 dw_single_line_string_from_expression(Arena *arena, String8 raw_data, U64 cu_base, U64 address_size, Arch arch, DW_Version ver, DW_Ext ext, DW_Format format); -internal String8 dw_string_from_eh_ptr_enc (Arena *arena, EH_PtrEnc enc); #if 0 internal void dw_string_from_cfi_program (Arena *arena, String8List *out, String8 indent, String8 raw_data, DW_CIE *cie, EH_PtrCtx *ptr_ctx, Arch arch, DW_Version ver, DW_Ext ext, DW_Format format); diff --git a/src/dwarf/dwarf_inc.c b/src/dwarf/dwarf_inc.c index 4293d911..b4ab74b0 100644 --- a/src/dwarf/dwarf_inc.c +++ b/src/dwarf/dwarf_inc.c @@ -8,3 +8,4 @@ #include "dwarf/dwarf_elf.c" #include "dwarf/dwarf_unwind.c" #include "dwarf/dwarf_dump.c" +#include "dwarf/dwarf_help.c" diff --git a/src/dwarf/dwarf_inc.h b/src/dwarf/dwarf_inc.h index 61f03873..26c24302 100644 --- a/src/dwarf/dwarf_inc.h +++ b/src/dwarf/dwarf_inc.h @@ -11,5 +11,6 @@ #include "dwarf/dwarf_elf.h" #include "dwarf/dwarf_unwind.h" #include "dwarf/dwarf_dump.h" +#include "dwarf/dwarf_help.h" #endif // DWARF_INC_H diff --git a/src/dwarf/dwarf_parse.c b/src/dwarf/dwarf_parse.c index ae6d8741..0998b75f 100644 --- a/src/dwarf/dwarf_parse.c +++ b/src/dwarf/dwarf_parse.c @@ -3383,18 +3383,18 @@ dw_parse_cie(String8 data, DW_Format format, Arch arch, DW_CIE *cie_out) B32 is_parsed = 0; U64 cursor = format == DW_Format_32Bit ? 4 : 12; - U64 cie_id = 0; + U64 cie_id = 0; U64 cie_id_size = str8_deserial_read_dwarf_uint(data, cursor, format, &cie_id); if (cie_id_size == 0) { goto exit; } cursor += cie_id_size; - U8 version = 0; + U8 version = 0; U64 version_size = str8_deserial_read_struct(data, cursor, &version); if (version_size == 0) { goto exit; } cursor += version_size; - String8 aug_string = {0}; - U64 aug_string_size = str8_deserial_read_cstr(data, cursor, &aug_string); + String8 aug_string = {0}; + U64 aug_string_size = str8_deserial_read_cstr(data, cursor, &aug_string); if (aug_string_size == 0) { goto exit; } cursor += aug_string_size; @@ -3447,11 +3447,11 @@ exit:; } internal B32 -dw_parse_fde(String8 data, +dw_parse_fde(String8 data, DW_Format format, DW_CIEFromOffsetFunc *cie_from_offset_func, void *cie_from_offset_ud, - DW_FDE *fde_out) + DW_FDE *fde_out) { B32 is_parsed = 0; U64 cursor = format == DW_Format_32Bit ? 4 : 12; @@ -3485,6 +3485,7 @@ dw_parse_fde(String8 data, // commit values to out fde_out->format = format; fde_out->cie_pointer = cie_pointer; + fde_out->cie = cie; fde_out->pc_range = rng_1u64(pc_begin, pc_begin + pc_range); fde_out->insts = str8_skip(data, cursor); @@ -3493,6 +3494,38 @@ exit:; return is_parsed; } +internal +DW_CIE_FROM_OFFSET_FUNC(dw_parse_cfi_cie_from_offset) +{ + return ud; +} + +internal B32 +dw_parse_cfi(String8 data, U64 fde_offset, Arch arch, DW_CIE *cie_out, DW_FDE *fde_out) +{ + B32 is_parsed = 0; + DW_DescriptorEntry fde_desc = {0}; + dw_parse_descriptor_entry_header(data, fde_offset, &fde_desc); + if (fde_desc.type == DW_DescriptorEntryType_FDE) { + U64 cie_pointer_off = fde_desc.format == DW_Format_32Bit ? 4 : 12; + U64 cie_pointer = 0; + U64 cie_pointer_size = str8_deserial_read_dwarf_uint(data, fde_offset + cie_pointer_off, fde_desc.format, &cie_pointer); + if (cie_pointer_size) { + DW_DescriptorEntry cie_desc = {0}; + dw_parse_descriptor_entry_header(data, cie_pointer, &cie_desc); + if (cie_desc.type == DW_DescriptorEntryType_CIE) { + if (dw_parse_cie(str8_substr(data, cie_desc.entry_range), cie_desc.format, arch, cie_out)) { + if (dw_parse_fde(str8_substr(data, fde_desc.entry_range), fde_desc.format, dw_parse_cfi_cie_from_offset, cie_out, fde_out)) { + is_parsed = 1; + } + } + } + } + } + + return is_parsed; +} + internal DW_CFA_ParseErrorCode dw_parse_cfa_inst(String8 data, U64 code_align_factor, diff --git a/src/dwarf/dwarf_parse.h b/src/dwarf/dwarf_parse.h index c691c38b..fc8ce759 100644 --- a/src/dwarf/dwarf_parse.h +++ b/src/dwarf/dwarf_parse.h @@ -378,6 +378,7 @@ typedef struct DW_CIE U64 code_align_factor; S64 data_align_factor; U64 ret_addr_reg; + U64 ext[4]; DW_Format format; U8 version; U8 address_size; @@ -388,6 +389,7 @@ typedef struct DW_FDE { DW_Format format; U64 cie_pointer; + DW_CIE *cie; Rng1U64 pc_range; String8 insts; } DW_FDE; @@ -529,8 +531,8 @@ internal DW_TagNode * dw_tag_node_from_info_off(DW_CompUnit *cu, U64 info_off); // line info -internal U64 dw_read_line_file(String8 line_data, U64 line_off, DW_Input *input, DW_Version unit_version, DW_Format unit_format, DW_Ext ext, U64 address_size, DW_ListUnit *str_offsets, U64 enc_count, U64 *enc_arr, DW_LineFile *line_file_out); -internal U64 dw_read_line_vm_header(Arena *arena, String8 line_data, U64 line_off, DW_Input *input, String8 cu_dir, String8 cu_name, U8 cu_address_size, DW_ListUnit *cu_str_offsets, DW_LineVMHeader *header_out); +internal U64 dw_read_line_file(String8 line_data, U64 line_off, DW_Input *input, DW_Version unit_version, DW_Format unit_format, DW_Ext ext, U64 address_size, DW_ListUnit *str_offsets, U64 enc_count, U64 *enc_arr, DW_LineFile *line_file_out); +internal U64 dw_read_line_vm_header(Arena *arena, String8 line_data, U64 line_off, DW_Input *input, String8 cu_dir, String8 cu_name, U8 cu_address_size, DW_ListUnit *cu_str_offsets, DW_LineVMHeader *header_out); internal void dw_line_vm_reset(DW_LineVMState *state, B32 default_is_stmt); internal void dw_line_vm_advance(DW_LineVMState *state, U64 advance, U64 min_inst_len, U64 max_ops_for_inst); internal DW_LineSeqNode * dw_push_line_seq(Arena* arena, DW_LineTableParseResult *parsed_tbl); @@ -558,6 +560,7 @@ internal U64 dw_read_debug_frame_ptr(String8 data, DW_CIE *cie, U64 *ptr_out); internal U64 dw_parse_descriptor_entry_header(String8 data, U64 off, DW_DescriptorEntry *desc_out); internal B32 dw_parse_cie(String8 data, DW_Format format, Arch arch, DW_CIE *cie_out); internal B32 dw_parse_fde(String8 data, DW_Format format, DW_CIEFromOffsetFunc *cie_from_offset_func, void *cie_from_offset_ud, DW_FDE *fde_out); +internal B32 dw_parse_cfi(String8 data, U64 fde_offset, Arch arch, DW_CIE *cie_out, DW_FDE *fde_out); internal DW_CFA_ParseErrorCode dw_parse_cfa_inst(String8 data, U64 code_align_factor, S64 data_align_factor, DW_DecodePtr *decode_ptr_func, void *decode_ptr_ud, U64 *bytes_read_out, DW_CFA_Inst *inst_out); internal DW_CFA_InstList dw_parse_cfa_inst_list(Arena *arena, String8 data, U64 code_align_factor, S64 data_align_factor, DW_DecodePtr *decode_ptr_func, void *decode_ptr_ud); diff --git a/src/dwarf/dwarf_unwind.c b/src/dwarf/dwarf_unwind.c index e899d676..de765be6 100644 --- a/src/dwarf/dwarf_unwind.c +++ b/src/dwarf/dwarf_unwind.c @@ -1,10 +1,10 @@ // Copyright (c) Epic Games Tools // Licensed under the MIT license (https://opensource.org/license/mit/) -internal DW_CFA_Row * -dw_make_cfa_row(Arena *arena, U64 reg_count) +internal DW_CFI_Row * +dw_make_cfi_row(Arena *arena, U64 reg_count) { - DW_CFA_Row *row = push_array(arena, DW_CFA_Row, 1); + DW_CFI_Row *row = push_array(arena, DW_CFI_Row, 1); row->regs = push_array(arena, DW_CFI_Register, reg_count); for EachIndex(reg_idx, reg_count) { row->regs[reg_idx].rule = DW_CFI_RegisterRule_SameValue; @@ -12,12 +12,18 @@ dw_make_cfa_row(Arena *arena, U64 reg_count) return row; } -internal DW_CFA_Row * -dw_copy_cfa_row(Arena *arena, U64 reg_count, DW_CFA_Row *row) +internal void +dw_memcpy_cfi_row(DW_CFI_Row *dst, DW_CFI_Row *src, U64 reg_count) { - DW_CFA_Row *new_row = dw_make_cfa_row(arena, reg_count); - new_row->cfa = row->cfa; - MemoryCopyTyped(new_row->regs, row->regs, reg_count); + dst->cfa = src->cfa; + MemoryCopyTyped(dst->regs, src->regs, reg_count); +} + +internal DW_CFI_Row * +dw_copy_cfi_row(Arena *arena, DW_CFI_Row *row, U64 reg_count) +{ + DW_CFI_Row *new_row = dw_make_cfi_row(arena, reg_count); + dw_memcpy_cfi_row(new_row, row, reg_count); return new_row; } @@ -35,19 +41,21 @@ dw_cfi_unwind_init(Arena *arena, uw->insts = dw_parse_cfa_inst_list(arena, fde->insts, cie->code_align_factor, cie->data_align_factor, decode_ptr_func, decode_ptr_ud); uw->cie = cie; uw->fde = fde; - uw->reg_count = dw_reg_count_from_arch(arch); uw->pc = fde->pc_range.min; uw->arch = arch; + uw->reg_count = dw_reg_count_from_arch(arch); // setup initial register rules DW_CFA_InstList initial_insts = dw_parse_cfa_inst_list(scratch.arena, cie->insts, cie->code_align_factor, cie->data_align_factor, decode_ptr_func, decode_ptr_ud); - uw->row = dw_make_cfa_row(arena, uw->reg_count); + uw->row = dw_make_cfi_row(arena, uw->reg_count); uw->curr_inst = initial_insts.first; - dw_cfi_next_row(arena, uw); + if (!dw_cfi_next_row(arena, uw)) { + AssertAlways(0 && "unable to interpret initial row instructions"); + } // make first row from initial rules uw->initial_row = uw->row; - uw->row = dw_copy_cfa_row(arena, uw->reg_count, uw->initial_row); + uw->row = dw_copy_cfi_row(arena, uw->initial_row, uw->reg_count); uw->curr_inst = uw->insts.first; scratch_end(scratch); @@ -156,12 +164,12 @@ dw_cfi_next_row(Arena *arena, DW_CFI_Unwind *uw) // Row State Instructions case DW_CFA_RememberState: { - DW_CFA_Row *new_row = dw_copy_cfa_row(arena, uw->reg_count, uw->row); + DW_CFI_Row *new_row = dw_copy_cfi_row(arena, uw->row, uw->reg_count); SLLStackPush(uw->row, new_row); } break; case DW_CFA_RestoreState: { if (uw->row == 0) { goto exit; } // TODO: report error: unbalanced number of pushes and pops - DW_CFA_Row *free_row = uw->row; + DW_CFI_Row *free_row = uw->row; SLLStackPop(uw->row); SLLStackPush(uw->free_rows, free_row); } break; @@ -183,17 +191,54 @@ exit:; return is_row_valid; } +internal DW_CFI_Row * +dw_cfi_row_from_pc(Arena *arena, Arch arch, DW_CIE *cie, DW_FDE *fde, DW_DecodePtr *decode_ptr_func, void *decode_ptr_ctx, U64 pc) +{ + Temp scratch = scratch_begin(&arena, 1); + + B32 is_row_found = 0; + U64 reg_count = dw_reg_count_from_arch(arch); + DW_CFI_Unwind *uw = dw_cfi_unwind_init(scratch.arena, arch, cie, fde, decode_ptr_func, decode_ptr_ctx); + DW_CFI_Row *row = dw_copy_cfi_row(scratch.arena, uw->row, uw->reg_count); + U64 prev_pc = uw->pc; + while (dw_cfi_next_row(scratch.arena, uw)) { + if (prev_pc <= pc && pc < uw->pc) { + is_row_found = 1; + break; + } + dw_memcpy_cfi_row(row, uw->row, uw->reg_count); + prev_pc = uw->pc; + } + + // handle last row + if (!is_row_found) { + if (prev_pc <= pc && pc < uw->pc && contains_1u64(fde->pc_range, pc)) { + row = uw->row; + is_row_found = 1; + } + } + + // copy out final row + DW_CFI_Row *result = 0; + if (is_row_found) { + result = dw_copy_cfi_row(arena, row, reg_count); + } + + scratch_end(scratch); + return result; +} + internal DW_UnwindStatus -dw_cfi_apply_register_rules(DW_CFI_Unwind *uw, - 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) +dw_cfi_apply_register_rules(Arch arch, + DW_CIE *cie, + 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) { Temp scratch = scratch_begin(0,0); DW_UnwindStatus unwind_status = DW_UnwindStatus_Ok; - DW_CFA_Row *row = uw->row; - // establish CFA U64 cfa = 0; switch (row->cfa.rule) { @@ -201,7 +246,7 @@ dw_cfi_apply_register_rules(DW_CFI_Unwind *uw, case DW_CFA_Rule_RegOff: { // TODO: report error (invalid register read) U64 cfa_reg_value = 0; - U64 reg_size = dw_reg_size_from_code(uw->arch, row->cfa.reg); + 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) { goto exit; } @@ -212,11 +257,12 @@ dw_cfi_apply_register_rules(DW_CFI_Unwind *uw, } break; } - U64 max_reg_size = dw_reg_max_size_from_arch(uw->arch); + U64 max_reg_size = dw_reg_max_size_from_arch(arch); void *reg_buffer = push_array(scratch.arena, U8, max_reg_size); - for EachIndex(reg_idx, uw->reg_count) { - DW_CFI_Register *reg = &uw->row->regs[reg_idx]; + U64 reg_count = dw_reg_count_from_arch(arch); + for EachIndex(reg_idx, reg_count) { + DW_CFI_Register *reg = &row->regs[reg_idx]; switch (reg->rule) { case DW_CFI_RegisterRule_Undefined: { // TODO: ??? @@ -226,7 +272,7 @@ dw_cfi_apply_register_rules(DW_CFI_Unwind *uw, case DW_CFI_RegisterRule_Offset: { // read register value from memory U64 addr = cfa + reg->n; - U64 reg_size = dw_reg_size_from_code(uw->arch, reg_idx); + 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; } @@ -239,13 +285,13 @@ dw_cfi_apply_register_rules(DW_CFI_Unwind *uw, U64 reg_value = cfa + reg->n; // write register value to the thread context - U64 reg_size = dw_reg_size_from_code(uw->arch, reg_idx); + 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; } } break; case DW_CFI_RegisterRule_Register: { // read register value from another register - U64 reg_size = dw_reg_size_from_code(uw->arch, reg_idx); + 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; } @@ -264,8 +310,10 @@ dw_cfi_apply_register_rules(DW_CFI_Unwind *uw, case DW_CFI_RegisterRule_Architectural: { NotImplemented; } break; + default: { InvalidPath; } break; } } + exit:; scratch_end(scratch); diff --git a/src/dwarf/dwarf_unwind.h b/src/dwarf/dwarf_unwind.h index f5fde8c0..bc8b2c57 100644 --- a/src/dwarf/dwarf_unwind.h +++ b/src/dwarf/dwarf_unwind.h @@ -44,25 +44,25 @@ typedef struct DW_CFI_Register }; } DW_CFI_Register; -typedef struct DW_CFA_Row +typedef struct DW_CFI_Row { - DW_CFA cfa; + DW_CFA cfa; DW_CFI_Register *regs; - struct DW_CFA_Row *next; -} DW_CFA_Row; + struct DW_CFI_Row *next; +} DW_CFI_Row; typedef struct DW_CFI_Unwind { DW_CFA_InstList insts; DW_CIE *cie; DW_FDE *fde; - DW_CFA_Row *initial_row; - DW_CFA_Row *row; + DW_CFI_Row *initial_row; + DW_CFI_Row *row; DW_CFA_InstNode *curr_inst; - DW_CFA_Row *free_rows; - DW_Reg reg_count; + DW_CFI_Row *free_rows; U64 pc; Arch arch; + U64 reg_count; } DW_CFI_Unwind; typedef enum { @@ -82,12 +82,14 @@ typedef DW_MEM_READ(DW_MemRead); //////////////////////////////// -internal DW_CFA_Row * dw_make_cfa_row(Arena *arena, U64 reg_count); -internal DW_CFA_Row * dw_copy_cfa_row(Arena *arena, U64 reg_count, DW_CFA_Row *row); +internal DW_CFI_Row * dw_make_cfi_row(Arena *arena, U64 reg_count); +internal DW_CFI_Row * dw_copy_cfi_row(Arena *arena, DW_CFI_Row *row, U64 reg_count); internal DW_CFI_Unwind * dw_cfi_unwind_init(Arena *arena, Arch arch, DW_CIE *cie, DW_FDE *fde, DW_DecodePtr *decode_ptr_func, void *decode_ptr_ud); internal B32 dw_cfi_next_row(Arena *arena, DW_CFI_Unwind *uw); -internal DW_UnwindStatus dw_cfi_apply_register_rules(DW_CFI_Unwind *uw, 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 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, DW_CIE *cie, 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); #endif // DWARF_UNWIND_H diff --git a/src/eh/eh_frame.c b/src/eh/eh_frame.c index 0b3f56e3..c89c0ee5 100644 --- a/src/eh/eh_frame.c +++ b/src/eh/eh_frame.c @@ -2,10 +2,14 @@ // Licensed under the MIT license (https://opensource.org/license/mit/) internal U64 -eh_read_ptr(String8 frame_base, U64 off, EH_PtrCtx *ptr_ctx, EH_PtrEnc encoding, U64 *ptr_out) +eh_parse_ptr(String8 frame_base, U64 off, EH_PtrCtx *ptr_ctx, EH_PtrEnc encoding, U64 *ptr_out) { U64 ptr_off = off; + if (encoding == EH_PtrEnc_Omit) { + return 0; + } + // align read offset as needed if (encoding == EH_PtrEnc_Aligned) { ptr_off = AlignPow2(ptr_off, ptr_ctx->ptr_align); @@ -65,6 +69,60 @@ eh_read_ptr(String8 frame_base, U64 off, EH_PtrCtx *ptr_ctx, EH_PtrEnc encoding, return decode_size; } +internal EH_FrameHdr +eh_parse_frame_hdr(String8 data, U64 address_size, EH_PtrCtx *ptr_ctx) +{ + EH_FrameHdr header = {0}; + U64 cursor = 0; + + U64 version_size = str8_deserial_read_struct(data, cursor, &header.version); + if (version_size == 0) { goto exit; } + cursor += version_size; + + if (header.version == 1) { + U64 eh_frame_ptr_enc_size = str8_deserial_read_struct(data, cursor, &header.eh_frame_ptr_enc); + if (eh_frame_ptr_enc_size == 0) { goto exit; } + cursor += eh_frame_ptr_enc_size; + + EH_PtrEnc fde_count_enc = 0; + U64 fde_count_enc_size = str8_deserial_read_struct(data, cursor, &fde_count_enc); + if (fde_count_enc_size == 0) { goto exit; } + cursor += fde_count_enc_size; + + U64 table_enc_size = str8_deserial_read_struct(data, cursor, &header.table_enc); + if (table_enc_size == 0) { goto exit; } + cursor += table_enc_size; + + U64 eh_frame_ptr = 0; + cursor += eh_parse_ptr(data, cursor, ptr_ctx, header.eh_frame_ptr_enc, &eh_frame_ptr); + + cursor += eh_parse_ptr(data, cursor, ptr_ctx, header.fde_count_enc, &header.fde_count); + + switch (header.table_enc) { + case EH_PtrEnc_Ptr: { header.field_byte_size = address_size; } break; + case EH_PtrEnc_ULEB128: { InvalidPath; } break; // TODO: when loading module convert these to UData8 + case EH_PtrEnc_UData2: { header.field_byte_size = 2; } break; + case EH_PtrEnc_UData4: { header.field_byte_size = 4; } break; + case EH_PtrEnc_UData8: { header.field_byte_size = 8; } break; + case EH_PtrEnc_Signed: { header.field_byte_size = address_size; } break; + case EH_PtrEnc_SLEB128: { InvalidPath; } break; // TODO: when loading module convert these to UData8 + case EH_PtrEnc_SData2: { header.field_byte_size = 2; } break; + case EH_PtrEnc_SData4: { header.field_byte_size = 4; } break; + case EH_PtrEnc_SData8: { header.field_byte_size = 8; } break; + default: { InvalidPath; } break; + } + header.entry_byte_size = header.field_byte_size * 2; + + header.table = str8_skip(data, cursor); + AssertAlways(header.table.size == header.entry_byte_size * header.fde_count); + } else { + Assert(0 && "unknown version"); + } + +exit:; + return header; +} + internal U64 eh_parse_aug_data(String8 aug_string, String8 aug_data, EH_PtrCtx *ptr_ctx, EH_Augmentation *aug_out) { @@ -91,7 +149,7 @@ eh_parse_aug_data(String8 aug_string, String8 aug_data, EH_PtrCtx *ptr_ctx, EH_A } break; case 'P': { aug_cursor += str8_deserial_read_struct(aug_data, aug_cursor, &handler_encoding); - aug_cursor += eh_read_ptr(aug_data, aug_cursor, ptr_ctx, handler_encoding, &handler_ip); + aug_cursor += eh_parse_ptr(aug_data, aug_cursor, ptr_ctx, handler_encoding, &handler_ip); aug_flags |= EH_AugFlag_HasHandler; } break; case 'R': { @@ -116,128 +174,262 @@ exit:; return parse_size; } -internal U64 -eh_size_from_aug_data(String8 aug_string, String8 data, EH_PtrCtx *ptr_ctx) +internal B32 +eh_parse_cie(String8 data, DW_Format format, Arch arch, EH_PtrCtx *ptr_ctx, DW_CIE *cie_out) { - return eh_parse_aug_data(aug_string, data, ptr_ctx, 0); -} + B32 is_parsed = 0; + U64 cursor = format == DW_Format_32Bit ? 4 : 12; -internal U64 -eh_frame_hdr_search_linear_x64(String8 raw_eh_frame_hdr, EH_PtrCtx *ptr_ctx, U64 location) -{ - // Table contains only addresses for first instruction in a function and we cannot - // guarantee that result is FDE that corresponds to the input location. - // So input location must be cheked against range from FDE header again. - - U64 closest_location = max_U64; - U64 closest_address = max_U64; - - U64 cursor = 0; - - U8 version = 0; - cursor += str8_deserial_read_struct(raw_eh_frame_hdr, cursor, &version); - + U64 cie_id = 0; + U64 cie_id_size = str8_deserial_read_dwarf_uint(data, cursor, format, &cie_id); + if (cie_id_size == 0) { goto exit; } + cursor += cie_id_size; + + U8 version = 0; + U64 version_size = str8_deserial_read_struct(data, cursor, &version); + if (version_size == 0) { goto exit; } + cursor += version_size; + + String8 aug_string = {0}; + EH_Augmentation aug = {0}; + U64 code_align_factor = 0; + S64 data_align_factor = 0; + U64 ret_addr_reg = max_U64; if (version == 1) { -#if 0 - EH_PtrCtx ptr_ctx = {0}; - // Set this to base address of .eh_frame_hdr. Entries are relative - // to this section for some reason. - ptr_ctx.data_vaddr = range.min; - // If input location is VMA then set this to address of .text. - // Pointer parsing function will adjust "init_location" to correct VMA. - ptr_ctx.text_vaddr = 0; -#endif - - EH_PtrEnc eh_frame_ptr_enc = 0, fde_count_enc = 0, table_enc = 0; - cursor += str8_deserial_read_struct(raw_eh_frame_hdr, cursor, &eh_frame_ptr_enc); - cursor += str8_deserial_read_struct(raw_eh_frame_hdr, cursor, &fde_count_enc); - cursor += str8_deserial_read_struct(raw_eh_frame_hdr, cursor, &table_enc); - - U64 eh_frame_ptr = 0, fde_count = 0; - NotImplemented; - //cursor += dw_unwind_parse_pointer_x64(raw_eh_frame_hdr.str, rng_1u64(0, raw_eh_frame_hdr.size), ptr_ctx, eh_frame_ptr_enc, cursor, &eh_frame_ptr); - //cursor += dw_unwind_parse_pointer_x64(raw_eh_frame_hdr.str, rng_1u64(0, raw_eh_frame_hdr.size), ptr_ctx, fde_count_enc, cursor, &fde_count); - - for (U64 fde_idx = 0; fde_idx < fde_count; ++fde_idx) { - U64 init_location = 0, address = 0; - NotImplemented; - //cursor += dw_unwind_parse_pointer_x64(raw_eh_frame_hdr.str, rng_1u64(0, raw_eh_frame_hdr.size), ptr_ctx, table_enc, cursor, &init_location); - //cursor += dw_unwind_parse_pointer_x64(raw_eh_frame_hdr.str, rng_1u64(0, raw_eh_frame_hdr.size), ptr_ctx, table_enc, cursor, &address); - - S64 current_delta = (S64)(location - init_location); - S64 closest_delta = (S64)(location - closest_location); - if (0 <= current_delta && current_delta < closest_delta) { - closest_location = init_location; - closest_address = address; - } - } + U64 aug_string_size = str8_deserial_read_cstr(data, cursor, &aug_string); + if (aug_string_size == 0) { goto exit; } + cursor += aug_string_size; + + U64 aug_data_size = eh_parse_aug_data(aug_string, str8_skip(data, cursor), ptr_ctx, &aug); + cursor += aug_data_size; + + U64 code_align_factor_size = str8_deserial_read_uleb128(data, cursor, &code_align_factor); + if (code_align_factor_size == 0) { goto exit; } + cursor += code_align_factor_size; + + U64 data_align_factor_size = str8_deserial_read_sleb128(data, cursor, &data_align_factor); + if (data_align_factor_size == 0) { goto exit; } + cursor += data_align_factor_size; + + ret_addr_reg = 0; + U64 ret_addr_reg_size = str8_deserial_read(data, cursor, &ret_addr_reg, sizeof(U8), sizeof(U8)); + if (ret_addr_reg_size == 0) { goto exit; } + cursor += ret_addr_reg_size; } - - // address where to find corresponding FDE, this is an absolute offset - // into the image file. - return closest_address; + + cie_out->insts = str8_skip(data, cursor); + cie_out->aug_string = aug_string; + cie_out->code_align_factor = code_align_factor; + cie_out->data_align_factor = data_align_factor; + cie_out->ret_addr_reg = ret_addr_reg; + cie_out->format = format; + cie_out->version = version; + cie_out->address_size = byte_size_from_arch(arch); + cie_out->segment_selector_size = 0; + + cie_out->ext[EH_CIE_Ext_LSDAEnc] = EH_PtrEnc_Omit; + cie_out->ext[EH_CIE_Ext_AddrEnc] = EH_PtrEnc_Omit; + cie_out->ext[EH_CIE_Ext_HandlerEnc] = EH_PtrEnc_Omit; + + if (aug.flags & EH_AugFlag_HasLSDA) { + cie_out->ext[EH_CIE_Ext_LSDAEnc] = aug.lsda_encoding; + } + if (aug.flags & EH_AugFlag_HasAddrEnc) { + cie_out->ext[EH_CIE_Ext_AddrEnc] = aug.addr_encoding; + } + if (aug.flags & EH_AugFlag_HasHandler) { + cie_out->ext[EH_CIE_Ext_HandlerEnc] = aug.handler_encoding; + cie_out->ext[EH_CIE_Ext_HandlerIp ] = aug.handler_ip; + } + + is_parsed = 1; +exit:; + return is_parsed; } -#if 0 -internal DW_CFIRecords -dw_unwind_eh_frame_hdr_from_ip_fast_x64(String8 raw_eh_frame, String8 raw_eh_frame_hdr, EH_PtrCtx *ptr_ctx, U64 ip_voff) +internal B32 +eh_parse_fde(String8 data, + DW_Format format, + DW_CIEFromOffsetFunc *cie_from_offset_func, + void *cie_from_offset_ud, + EH_PtrCtx *ptr_ctx, + DW_FDE *fde_out) { - DW_CFIRecords result = {0}; - - // find FDE offset - void *eh_frame_hdr = raw_eh_frame.str; - U64 fde_offset = dw_search_eh_frame_hdr_linear_x64(raw_eh_frame_hdr, ptr_ctx, ip_voff); - - B32 is_fde_offset_valid = (fde_offset != max_U64); - if (is_fde_offset_valid) { - U64 fde_read_offset = (fde_offset - ptr_ctx->raw_base_vaddr); - - // read FDE size - U64 fde_size = 0; - fde_read_offset += str8_deserial_read_dwarf_packed_size(raw_eh_frame, fde_read_offset, &fde_size); - - // read FDE discriminator - U32 fde_discrim = 0; - fde_read_offset += str8_deserial_read_struct(raw_eh_frame, fde_read_offset, &fde_discrim); - - // compute parent CIE offset - U64 cie_read_offset = fde_read_offset - (fde_discrim + sizeof(fde_discrim)); - - // read CIE size - U64 cie_size = 0; - cie_read_offset += str8_deserial_read_dwarf_packed_size(raw_eh_frame, cie_read_offset, &cie_size); - - // read CIE discriminator - U32 cie_discrim = max_U32; - cie_read_offset += str8_deserial_read_struct(raw_eh_frame, cie_read_offset, &cie_discrim); - - B32 is_fde = (fde_discrim != 0); - B32 is_cie = (cie_discrim == 0); - if (is_fde && is_cie) { - Rng1U64 cie_range = rng_1u64(0, cie_read_offset + (cie_size - sizeof(cie_discrim))); - Rng1U64 fde_range = rng_1u64(0, fde_read_offset + (fde_size - sizeof(fde_discrim))); - - // parse CIE - DW_CIE cie = {0}; - dw_unwind_parse_cie_x64(raw_eh_frame.str, cie_range, ptr_ctx, cie_read_offset, &cie); - - // parse FDE - DW_FDE fde = {0}; - NotImplemented; - //dw_unwind_parse_fde_x64(raw_eh_frame.str, fde_range, ptr_ctx, &cie, fde_read_offset, &fde); - - // range check instruction pointer - if (contains_1u64(fde.pc_range, ip_voff)) { - result.valid = 1; - result.cie = cie; - result.fde = fde; + B32 is_parsed = 0; + U64 cursor = format == DW_Format_32Bit ? 4 : 12; + + U64 cie_pointer = 0; + U64 cie_pointer_size = str8_deserial_read_dwarf_uint(data, cursor, format, &cie_pointer); + if (cie_pointer_size == 0) { goto exit; } + + DW_CIE *cie = cie_from_offset_func(cie_from_offset_ud, cie_pointer); + if (cie == 0) { goto exit; } + + EH_PtrEnc addr_enc = cie->ext[EH_CIE_Ext_AddrEnc]; + + U64 pc_begin = 0; + U64 pc_range = 0; + if (addr_enc != EH_PtrEnc_Omit) { + U64 pc_begin_size = eh_parse_ptr(data, cursor, ptr_ctx, addr_enc, &pc_begin); + if (pc_begin_size == 0) { goto exit; } + cursor += pc_begin_size; + + U64 pc_range_size = eh_parse_ptr(data, cursor, ptr_ctx, addr_enc, &pc_range_size); + if (pc_range_size == 0) { goto exit; } + cursor += pc_range_size; + } + + if (cursor + cie->aug_data.size > data.size) { goto exit; } + cursor += cie->aug_data.size; + + fde_out->format = format; + fde_out->cie_pointer = cie_pointer; + fde_out->cie = cie; + fde_out->pc_range = rng_1u64(pc_begin, pc_begin + pc_range); + fde_out->insts = str8_skip(data, cursor); + + is_parsed = 1; +exit:; + return is_parsed; +} + +internal +DW_CIE_FROM_OFFSET_FUNC(eh_parse_cfi_cie_from_offset) +{ + return ud; +} + +internal B32 +eh_parse_cfi(String8 eh_frame, U64 fde_offset, Arch arch, EH_PtrCtx *ptr_ctx, DW_CIE *cie_out, DW_FDE *fde_out) +{ + B32 is_parsed = 0; + DW_DescriptorEntry fde_desc = {0}; + dw_parse_descriptor_entry_header(eh_frame, fde_offset, &fde_desc); + if (fde_desc.type == DW_DescriptorEntryType_FDE) { + U64 cie_pointer_off = fde_desc.format == DW_Format_32Bit ? 4 : 12; + U64 cie_pointer = 0; + U64 cie_pointer_size = str8_deserial_read_dwarf_uint(eh_frame, fde_offset + cie_pointer_off, fde_desc.format, &cie_pointer); + if (cie_pointer_size) { + DW_DescriptorEntry cie_desc = {0}; + dw_parse_descriptor_entry_header(eh_frame, cie_pointer, &cie_desc); + if (cie_desc.type == DW_DescriptorEntryType_CIE) { + if (eh_parse_cie(str8_substr(eh_frame, cie_desc.entry_range), cie_desc.format, arch, ptr_ctx, cie_out)) { + if (eh_parse_fde(str8_substr(eh_frame, fde_desc.entry_range), fde_desc.format, eh_parse_cfi_cie_from_offset, cie_out, ptr_ctx, fde_out)) { + return is_parsed; + } + } } } } - - return result; + return is_parsed; +} + +internal U64 +eh_find_nearest_fde(EH_FrameHdr header, EH_PtrCtx *ptr_ctx, U64 pc) +{ + U64 fde_offset = max_U64; + U64 fde_idx = max_U64; + + if (header.version == 1) { + if (header.fde_count > 0) { + U64 first = 0; + U64 first_size = eh_parse_ptr(header.table, 0, ptr_ctx, header.table_enc, &first); + AssertAlways(first_size); + if (first == pc) { + fde_idx = 0; + goto exit; + } + if (first > pc) { + goto exit; + } + + U64 last = 0; + U64 last_size = eh_parse_ptr(header.table, header.table.size - header.entry_byte_size, ptr_ctx, header.table_enc, &last); + AssertAlways(last_size); + if (last <= pc) { + fde_idx = header.fde_count - 1; + goto exit; + } + + if (first <= pc && pc < last) { + U64 l = 0; + U64 r = header.fde_count - 1; + while (l <= r) { + U64 m = l + (r - l) / 2; + U64 m_pc = 0; + U64 m_pc_size = eh_parse_ptr(header.table, m * header.entry_byte_size, ptr_ctx, header.table_enc, &m_pc); + Assert(m_pc_size); + + if (m_pc == pc) { + fde_idx = m; + goto exit; + } else if (m_pc < pc) { + l = m + 1; + } else { + r = m - 1; + } + } + fde_idx = l; + } + } + + } + +exit:; + if (fde_idx < header.fde_count) { + U64 fde_offset_size = eh_parse_ptr(header.table, (fde_idx * header.entry_byte_size) + header.field_byte_size, ptr_ctx, header.table_enc, &fde_offset); + Assert(fde_offset_size); + } + return fde_offset; +} + +internal int +eh_frame_hdr_entry_sort(void *raw_a, void *raw_b) +{ + return ((EH_FrameHdrEntry *)raw_a)->addr < ((EH_FrameHdrEntry *)raw_b)->addr; +} + +internal String8 +eh_frame_hdr_from_call_frame_info(Arena *arena, U64 fde_count, U64 *fde_offsets, DW_FDE *fde) +{ + Temp scratch = scratch_begin(&arena, 1); + + // make .eh_frame_hdr + String8List srl = {0}; + str8_serial_begin(scratch.arena, &srl); + str8_serial_push_u8(scratch.arena, &srl, 1); // version + str8_serial_push_u8(scratch.arena, &srl, EH_PtrEnc_Omit); // omit eh_frame_ptr field + str8_serial_push_u8(scratch.arena, &srl, EH_PtrEnc_UData8); // fde_count encoding + str8_serial_push_u8(scratch.arena, &srl, EH_PtrEnc_UData8); // table encoding + str8_serial_push_u64(scratch.arena, &srl, fde_count); // fde_count + String8 header = str8_serial_end(scratch.arena, &srl); + + // alloc buffer for output + U64 buffer_size = header.size + sizeof(EH_FrameHdrEntry) * fde_count; + U8 *buffer = push_array(arena, U8, buffer_size); + + // copy header + MemoryCopyStr8(buffer, header); + + // write the table + EH_FrameHdrEntry *table = (EH_FrameHdrEntry *)(buffer + header.size); + for EachIndex(fde_idx, fde_count) { + table[fde_idx].addr = fde[fde_idx].pc_range.min; + table[fde_idx].fde_offset = fde_offsets[fde_idx]; + } + radsort(table, fde_count, eh_frame_hdr_entry_sort); + + String8 eh_frame_hdr = str8(buffer, buffer_size); + scratch_end(scratch); + return eh_frame_hdr; +} + +internal +DW_DECODE_PTR(eh_decode_ptr) +{ + EH_DecodePtrCtx *ctx = ud; + return eh_parse_ptr(data, 0, ctx->ptr_ctx, ctx->addr_enc, ptr_out); } -#endif internal String8 eh_string_from_ptr_enc_type(EH_PtrEnc type) diff --git a/src/eh/eh_frame.h b/src/eh/eh_frame.h index ac5840e5..6139ca67 100644 --- a/src/eh/eh_frame.h +++ b/src/eh/eh_frame.h @@ -4,6 +4,9 @@ #ifndef EH_FRAME_H #define EH_FRAME_H +//////////////////////////////// +//~ Format + typedef U8 EH_PtrEnc; enum { @@ -65,10 +68,48 @@ typedef struct EH_Augmentation } EH_Augmentation; //////////////////////////////// +//~ Parser -internal U64 eh_read_ptr(String8 frame_base, U64 off, EH_PtrCtx *ptr_ctx, EH_PtrEnc encoding, U64 *ptr_out); -internal U64 eh_parse_aug_data(String8 aug_string, String8 aug_data, EH_PtrCtx *ptr_ctx, EH_Augmentation *aug_out); -internal U64 eh_size_from_aug_data(String8 aug_string, String8 data, EH_PtrCtx *ptr_ctx); +enum +{ + EH_CIE_Ext_AddrEnc, + EH_CIE_Ext_LSDAEnc, + EH_CIE_Ext_HandlerEnc, + EH_CIE_Ext_HandlerIp, +} EH_CIE_Ext; + +typedef struct EH_FrameHdrEntry +{ + U64 addr; + U64 fde_offset; +} EH_FrameHdrEntry; + +typedef struct EH_FrameHdr +{ + U8 version; + EH_PtrEnc eh_frame_ptr_enc; + EH_PtrEnc fde_count_enc; + EH_PtrEnc table_enc; + U64 field_byte_size; + U64 entry_byte_size; + U64 fde_count; + String8 table; +} EH_FrameHdr; + +typedef struct EH_DecodePtrCtx +{ + EH_PtrEnc addr_enc; + EH_PtrCtx *ptr_ctx; +} EH_DecodePtrCtx; + +//////////////////////////////// + +internal U64 eh_parse_ptr(String8 frame_base, U64 off, EH_PtrCtx *ptr_ctx, EH_PtrEnc encoding, U64 *ptr_out); +internal EH_FrameHdr eh_parse_frame_hdr(String8 data, U64 address_size, EH_PtrCtx *ptr_ctx); +internal U64 eh_parse_aug_data(String8 aug_string, String8 aug_data, EH_PtrCtx *ptr_ctx, EH_Augmentation *aug_out); +internal B32 eh_parse_cfi(String8 eh_frame, U64 fde_offset, Arch arch, EH_PtrCtx *ptr_ctx, struct DW_CIE *cie_out, struct DW_FDE *fde_out); +internal U64 eh_find_nearest_fde(EH_FrameHdr header, EH_PtrCtx *ptr_ctx, U64 pc); +internal String8 eh_frame_hdr_from_call_frame_info(Arena *arena, U64 fde_count, U64 *fde_offsets, struct DW_FDE *fde); //////////////////////////////// //~ Enum -> String diff --git a/src/raddbg/raddbg_main.c b/src/raddbg/raddbg_main.c index f633614c..723b0c64 100644 --- a/src/raddbg/raddbg_main.c +++ b/src/raddbg/raddbg_main.c @@ -217,8 +217,8 @@ #include "pdb/pdb.h" #include "pdb/pdb_parse.h" #include "pdb/pdb_stringize.h" -#include "eh/eh_frame.h" #include "dwarf/dwarf_inc.h" +#include "eh/eh_frame.h" #include "rdi_from_coff/rdi_from_coff.h" #include "rdi_from_elf/rdi_from_elf.h" #include "rdi_from_pdb/rdi_from_pdb.h" @@ -265,8 +265,8 @@ #include "pdb/pdb.c" #include "pdb/pdb_parse.c" #include "pdb/pdb_stringize.c" -#include "eh/eh_frame.c" #include "dwarf/dwarf_inc.c" +#include "eh/eh_frame.c" #include "rdi_from_coff/rdi_from_coff.c" #include "rdi_from_elf/rdi_from_elf.c" #include "rdi_from_pdb/rdi_from_pdb.c"