diff --git a/src/dwarf/dwarf_unwind.c b/src/dwarf/dwarf_unwind.c index e3540442..168fd52e 100644 --- a/src/dwarf/dwarf_unwind.c +++ b/src/dwarf/dwarf_unwind.c @@ -55,7 +55,7 @@ dw_unwind_x64(String8 raw_text, //- find cfi records DW_CFIRecords cfi_recs = {0}; if (has_needed_sections) { - DW_EhPtrCtx ptr_ctx = {0}; + EH_PtrCtx ptr_ctx = {0}; ptr_ctx.raw_base_vaddr = frame_base_voff; ptr_ctx.text_vaddr = text_base_vaddr; ptr_ctx.data_vaddr = data_base_vaddr; @@ -75,11 +75,11 @@ dw_unwind_x64(String8 raw_text, //- cfi machine setup DW_CFIMachine machine = {0}; if (cfi_recs.valid) { - DW_EhPtrCtx ptr_ctx = {0}; + EH_PtrCtx ptr_ctx = {0}; ptr_ctx.raw_base_vaddr = frame_base_voff; ptr_ctx.text_vaddr = text_base_vaddr; ptr_ctx.data_vaddr = data_base_vaddr; - ptr_ctx.func_vaddr = cfi_recs.fde.ip_voff_range.min + rebase_voff_to_vaddr; // TODO: it's not super clear how to set up this member, need more test cases + ptr_ctx.func_vaddr = cfi_recs.fde.pc_range.min + rebase_voff_to_vaddr; // TODO: it's not super clear how to set up this member, need more test cases machine = dw_unwind_make_machine_x64(DW_UNWIND_X64__REG_SLOT_COUNT, &cfi_recs.cie, &ptr_ctx); } @@ -101,7 +101,7 @@ dw_unwind_x64(String8 raw_text, if (init_row != 0) { // upgrade machine with new equipment dw_unwind_machine_equip_initial_row_x64(&machine, init_row); - dw_unwind_machine_equip_fde_ip_x64(&machine, cfi_recs.fde.ip_voff_range.min); + dw_unwind_machine_equip_fde_ip_x64(&machine, cfi_recs.fde.pc_range.min); // decode main row Rng1U64 main_cfi_range = cfi_recs.fde.cfi_range; @@ -320,100 +320,10 @@ dw_unwind_init_x64(void) } } -internal U64 -dw_unwind_parse_pointer_x64(void *frame_base, Rng1U64 frame_range, DW_EhPtrCtx *ptr_ctx, DW_EhPtrEnc encoding, U64 off, U64 *ptr_out) -{ - // aligned offset - U64 pointer_off = off; - if (encoding == DW_EhPtrEnc_Aligned) { - pointer_off = AlignPow2(off, 8); // TODO: align to 4 bytes when we parse x86 ELF binary - encoding = DW_EhPtrEnc_Ptr; - } - - // decode pointer value - U64 size_param = 0; - U64 after_pointer_off = 0; - U64 raw_pointer = 0; - switch (encoding & DW_EhPtrEnc_TypeMask) { - default:break; - - case DW_EhPtrEnc_Ptr : size_param = 8; goto ufixed; - case DW_EhPtrEnc_UData2: size_param = 2; goto ufixed; - case DW_EhPtrEnc_UData4: size_param = 4; goto ufixed; - case DW_EhPtrEnc_UData8: size_param = 8; goto ufixed; - ufixed: - { - dw_based_range_read(frame_base, frame_range, pointer_off, size_param, &raw_pointer); - after_pointer_off = pointer_off + size_param; - } break; - - // TODO: Signed is actually just a flag that indicates this int is negavite. - // There shouldn't be a read for Signed. - // For instance, (DW_EhPtrEnc_UData2 | DW_EhPtrEnc_Signed) == DW_EhPtrEnc_SData etc. - case DW_EhPtrEnc_Signed:size_param = 8; goto sfixed; - - case DW_EhPtrEnc_SData2:size_param = 2; goto sfixed; - case DW_EhPtrEnc_SData4:size_param = 4; goto sfixed; - case DW_EhPtrEnc_SData8:size_param = 8; goto sfixed; - sfixed: - { - dw_based_range_read(frame_base, frame_range, pointer_off, size_param, &raw_pointer); - after_pointer_off = pointer_off + size_param; - // sign extension - U64 sign_bit = size_param*8 - 1; - if ((raw_pointer >> sign_bit) != 0) { - raw_pointer |= (~(1 << sign_bit)) + 1; - } - } break; - - case DW_EhPtrEnc_ULEB128: - { - U64 size = dw_based_range_read_uleb128(frame_base, frame_range, pointer_off, &raw_pointer); - after_pointer_off = pointer_off + size; - } break; - - case DW_EhPtrEnc_SLEB128: - { - U64 size = dw_based_range_read_sleb128(frame_base, frame_range, pointer_off, - (S64*)&raw_pointer); - after_pointer_off = pointer_off + size; - } break; - } - - // apply relative bases - U64 pointer = raw_pointer; - if (pointer != 0) { - switch (encoding & DW_EhPtrEnc_ModifyMask) { - case DW_EhPtrEnc_PcRel: - { - pointer = ptr_ctx->raw_base_vaddr + frame_range.min + off + raw_pointer; - } break; - case DW_EhPtrEnc_TextRel: - { - pointer = ptr_ctx->text_vaddr + raw_pointer; - } break; - case DW_EhPtrEnc_DataRel: - { - pointer = ptr_ctx->data_vaddr + raw_pointer; - } break; - case DW_EhPtrEnc_FuncRel: - { - Assert(!"TODO: need a sample to verify implementation"); - pointer = ptr_ctx->func_vaddr + raw_pointer; - } break; - } - } - - // return - *ptr_out = pointer; - U64 result = after_pointer_off - off; - return(result); -} - //- eh_frame parsing internal void -dw_unwind_parse_cie_x64(void *base, Rng1U64 range, DW_EhPtrCtx *ptr_ctx, U64 off, DW_CIEUnpacked *cie_out) +dw_unwind_parse_cie_x64(void *base, Rng1U64 range, EH_PtrCtx *ptr_ctx, U64 off, DW_UnpackedCIE *cie_out) { NotImplemented; #if 0 @@ -474,9 +384,9 @@ dw_unwind_parse_cie_x64(void *base, Rng1U64 range, DW_EhPtrCtx *ptr_ctx, U64 off U64 aug_data_off = after_aug_size_off; U64 after_aug_data_off = after_aug_size_off; - DW_EhPtrEnc lsda_encoding = DW_EhPtrEnc_Omit; + EH_PtrEnc lsda_encoding = EH_PtrEnc_Omit; U64 handler_ip = 0; - DW_EhPtrEnc addr_encoding = DW_EhPtrEnc_UData8; + EH_PtrEnc addr_encoding = EH_PtrEnc_UData8; if (has_augmentation_size > 0) { U64 aug_data_cursor = aug_data_off; @@ -487,7 +397,7 @@ dw_unwind_parse_cie_x64(void *base, Rng1U64 range, DW_EhPtrCtx *ptr_ctx, U64 off aug_data_cursor += sizeof(lsda_encoding); } break; case 'P': { - DW_EhPtrEnc handler_encoding = DW_EhPtrEnc_Omit; + EH_PtrEnc handler_encoding = EH_PtrEnc_Omit; dw_based_range_read_struct(base, range, aug_data_cursor, &handler_encoding); U64 ptr_off = aug_data_cursor + sizeof(handler_encoding); @@ -516,6 +426,7 @@ dw_unwind_parse_cie_x64(void *base, Rng1U64 range, DW_EhPtrCtx *ptr_ctx, U64 off // commit values to out cie_out->version = version; + cie_out->lsda_encoding = lsda_encoding; cie_out->addr_encoding = addr_encoding; cie_out->has_augmentation_size = has_augmentation_size; @@ -531,68 +442,15 @@ dw_unwind_parse_cie_x64(void *base, Rng1U64 range, DW_EhPtrCtx *ptr_ctx, U64 off #endif } -internal void -dw_unwind_parse_fde_x64(void *base, Rng1U64 range, DW_EhPtrCtx *ptr_ctx, DW_CIEUnpacked *cie, U64 off, DW_FDEUnpacked *fde_out) -{ - // pull out pointer encoding field - DW_EhPtrEnc ptr_enc = cie->addr_encoding; - - // ip first - U64 ip_first_off = off; - U64 ip_first = 0; - U64 ip_first_size = dw_unwind_parse_pointer_x64(base, range, ptr_ctx, ptr_enc, ip_first_off, &ip_first); - - // ip range size - U64 ip_range_size_off = ip_first_off + ip_first_size; - U64 ip_range_size = 0; - U64 ip_range_size_size = dw_unwind_parse_pointer_x64(base, range, ptr_ctx, ptr_enc & DW_EhPtrEnc_TypeMask, ip_range_size_off, &ip_range_size); - - // augmentation data - U64 aug_data_off = ip_range_size_off + ip_range_size_size; - U64 after_aug_data_off = aug_data_off; - U64 lsda_ip = 0; - - if (cie->has_augmentation_size) { - // augmentation size - U64 augmentation_size = 0; - U64 aug_size_size = dw_based_range_read_uleb128(base, range, aug_data_off, &augmentation_size); - U64 after_aug_size_off = aug_data_off + aug_size_size; - - // extract lsda (only thing that can actually be in FDE's augmentation data as far as we know) - DW_EhPtrEnc lsda_encoding = cie->lsda_encoding; - if (lsda_encoding != DW_EhPtrEnc_Omit) { - U64 lsda_off = after_aug_size_off; - dw_unwind_parse_pointer_x64(base, range, ptr_ctx, lsda_encoding, lsda_off, &lsda_ip); - } - - // set offset at end of augmentation data - after_aug_data_off = after_aug_size_off + augmentation_size; - } - - // cfi range - U64 cfi_off = range.min + after_aug_data_off; - U64 cfi_size = 0; - if (range.max > cfi_off) { - cfi_size = range.max - cfi_off; - } - - // commit values to out - fde_out->ip_voff_range.min = ip_first; - fde_out->ip_voff_range.max = ip_first + ip_range_size; - fde_out->lsda_ip = lsda_ip; - fde_out->cfi_range.min = cfi_off; - fde_out->cfi_range.max = cfi_off + cfi_size; -} - internal DW_CFIRecords -dw_unwind_eh_frame_cfi_from_ip_slow_x64(String8 raw_eh_frame, DW_EhPtrCtx *ptr_ctx, U64 ip_voff) +dw_unwind_eh_frame_cfi_from_ip_slow_x64(String8 raw_eh_frame, EH_PtrCtx *ptr_ctx, U64 ip_voff) { Temp scratch = scratch_begin(0, 0); DW_CFIRecords result = {0}; - DW_CIEUnpackedNode *cie_first = 0; - DW_CIEUnpackedNode *cie_last = 0; + DW_UnpackedCIENode *cie_first = 0; + DW_UnpackedCIENode *cie_last = 0; U64 cursor = 0; for (;;) { @@ -632,10 +490,10 @@ dw_unwind_eh_frame_cfi_from_ip_slow_x64(String8 raw_eh_frame, DW_EhPtrCtx *ptr_c // CIE if (discrim == 0) { - DW_CIEUnpacked cie = {0}; + DW_UnpackedCIE cie = {0}; dw_unwind_parse_cie_x64(raw_rec.str, rng_1u64(0, raw_rec.size), ptr_ctx, after_discrim_off, &cie); if (cie.version != 0) { - DW_CIEUnpackedNode *node = push_array(scratch.arena, DW_CIEUnpackedNode, 1); + DW_UnpackedCIENode *node = push_array(scratch.arena, DW_UnpackedCIENode, 1); node->cie = cie; node->offset = rec_off; SLLQueuePush(cie_first, cie_last, node); @@ -647,8 +505,8 @@ dw_unwind_eh_frame_cfi_from_ip_slow_x64(String8 raw_eh_frame, DW_EhPtrCtx *ptr_c U64 cie_offset = rec_range.min + discrim_off - discrim; // get cie node - DW_CIEUnpackedNode *cie_node = 0; - for (DW_CIEUnpackedNode *node = cie_first; node != 0; node = node->next) { + DW_UnpackedCIENode *cie_node = 0; + for (DW_UnpackedCIENode *node = cie_first; node != 0; node = node->next) { if (node->offset == cie_offset) { cie_node = node; break; @@ -656,12 +514,13 @@ dw_unwind_eh_frame_cfi_from_ip_slow_x64(String8 raw_eh_frame, DW_EhPtrCtx *ptr_c } // parse fde - DW_FDEUnpacked fde = {0}; + DW_UnpackedFDE fde = {0}; if (cie_node != 0) { - dw_unwind_parse_fde_x64(raw_rec.str, rng_1u64(0,raw_rec.size), ptr_ctx, &cie_node->cie, after_discrim_off, &fde); + NotImplemented; + //dw_unwind_parse_fde_x64(raw_rec.str, rng_1u64(0,raw_rec.size), ptr_ctx, &cie_node->cie, after_discrim_off, &fde); } - if (contains_1u64(fde.ip_voff_range, ip_voff)) { + if (contains_1u64(fde.pc_range, ip_voff)) { result.valid = 1; result.cie = cie_node->cie; result.fde = fde; @@ -679,7 +538,7 @@ dw_unwind_eh_frame_cfi_from_ip_slow_x64(String8 raw_eh_frame, DW_EhPtrCtx *ptr_c } internal U64 -dw_search_eh_frame_hdr_linear_x64(String8 raw_eh_frame_hdr, DW_EhPtrCtx *ptr_ctx, U64 location) +dw_search_eh_frame_hdr_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. @@ -695,7 +554,7 @@ dw_search_eh_frame_hdr_linear_x64(String8 raw_eh_frame_hdr, DW_EhPtrCtx *ptr_ctx if (version == 1) { #if 0 - DW_EhPtrCtx ptr_ctx = {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; @@ -704,7 +563,7 @@ dw_search_eh_frame_hdr_linear_x64(String8 raw_eh_frame_hdr, DW_EhPtrCtx *ptr_ctx ptr_ctx.text_vaddr = 0; #endif - DW_EhPtrEnc eh_frame_ptr_enc = 0, fde_count_enc = 0, table_enc = 0; + 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); @@ -733,7 +592,7 @@ dw_search_eh_frame_hdr_linear_x64(String8 raw_eh_frame_hdr, DW_EhPtrCtx *ptr_ctx } internal DW_CFIRecords -dw_unwind_eh_frame_hdr_from_ip_fast_x64(String8 raw_eh_frame, String8 raw_eh_frame_hdr, DW_EhPtrCtx *ptr_ctx, U64 ip_voff) +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) { DW_CFIRecords result = {0}; @@ -771,15 +630,16 @@ dw_unwind_eh_frame_hdr_from_ip_fast_x64(String8 raw_eh_frame, String8 raw_eh_fra Rng1U64 fde_range = rng_1u64(0, fde_read_offset + (fde_size - sizeof(fde_discrim))); // parse CIE - DW_CIEUnpacked cie = {0}; + DW_UnpackedCIE cie = {0}; dw_unwind_parse_cie_x64(raw_eh_frame.str, cie_range, ptr_ctx, cie_read_offset, &cie); // parse FDE - DW_FDEUnpacked fde = {0}; - dw_unwind_parse_fde_x64(raw_eh_frame.str, fde_range, ptr_ctx, &cie, fde_read_offset, &fde); + DW_UnpackedFDE 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.ip_voff_range, ip_voff)) { + if (contains_1u64(fde.pc_range, ip_voff)) { result.valid = 1; result.cie = cie; result.fde = fde; @@ -793,7 +653,7 @@ dw_unwind_eh_frame_hdr_from_ip_fast_x64(String8 raw_eh_frame, String8 raw_eh_fra //- cfi machine internal DW_CFIMachine -dw_unwind_make_machine_x64(U64 cells_per_row, DW_CIEUnpacked *cie, DW_EhPtrCtx *ptr_ctx) +dw_unwind_make_machine_x64(U64 cells_per_row, DW_UnpackedCIE *cie, EH_PtrCtx *ptr_ctx) { DW_CFIMachine result = {0}; result.cells_per_row = cells_per_row; @@ -843,8 +703,8 @@ dw_unwind_machine_run_to_ip_x64(void *base, Rng1U64 range, DW_CFIMachine *machin B32 result = 0; // pull out machine's equipment - DW_CIEUnpacked *cie = machine->cie; - DW_EhPtrCtx *ptr_ctx = machine->ptr_ctx; + DW_UnpackedCIE *cie = machine->cie; + EH_PtrCtx *ptr_ctx = machine->ptr_ctx; U64 cells_per_row = machine->cells_per_row; DW_CFIRow *initial_row = machine->initial_row; @@ -910,7 +770,9 @@ dw_unwind_machine_run_to_ip_x64(void *base, Rng1U64 range, DW_CFIMachine *machin } } break; case DW_CFADecode_Address: { - o_size = dw_unwind_parse_pointer_x64(base, range, ptr_ctx, cie->addr_encoding, decode_cursor, out); + // TODO: + NotImplemented; + //o_size = dw_unwind_parse_pointer_x64(base, range, ptr_ctx, cie->addr_encoding, decode_cursor, out); } break; case DW_CFADecode_ULEB128: { o_size = dw_based_range_read_uleb128(base, range, decode_cursor, out); @@ -1163,3 +1025,46 @@ dw_unwind_machine_run_to_ip_x64(void *base, Rng1U64 range, DW_CFIMachine *machin return result; } +//////////////////////////////// + +internal String8 +dw_string_from_eh_ptr_enc_type(EH_PtrEnc type) +{ + switch (type) { + case EH_PtrEnc_Ptr: return str8_lit("Ptr"); + case EH_PtrEnc_ULEB128: return str8_lit("ULEB128"); + case EH_PtrEnc_UData2: return str8_lit("UData2"); + case EH_PtrEnc_UData4: return str8_lit("UData4"); + case EH_PtrEnc_UData8: return str8_lit("UData8"); + case EH_PtrEnc_Signed: return str8_lit("Signed"); + case EH_PtrEnc_SLEB128: return str8_lit("SLEB128"); + case EH_PtrEnc_SData2: return str8_lit("SData2"); + case EH_PtrEnc_SData4: return str8_lit("SData4"); + case EH_PtrEnc_SData8: return str8_lit("SData8"); + } + return str8_zero(); +} + +internal String8 +dw_string_from_eh_ptr_enc_modifier(EH_PtrEnc modifier) +{ + switch (modifier) { + case EH_PtrEnc_PcRel: return str8_lit("PcRel"); + case EH_PtrEnc_TextRel: return str8_lit("TextRel"); + case EH_PtrEnc_DataRel: return str8_lit("DataRel"); + case EH_PtrEnc_FuncRel: return str8_lit("FuncRel"); + case EH_PtrEnc_Aligned: return str8_lit("Aligned"); + } + return str8_zero(); +} + +internal String8 +dw_string_from_eh_ptr_enc(Arena *arena, EH_PtrEnc enc) +{ + String8 type_str = dw_string_from_eh_ptr_enc_type(enc & EH_PtrEnc_TypeMask); + String8 modifer_str = dw_string_from_eh_ptr_enc_modifier(enc & EH_PtrEnc_ModifierMask); + String8 indir_str = enc & EH_PtrEnc_Indirect ? str8_lit("Indirect") : str8_zero(); + String8 result = str8f(arena, "Type: %S, Modifier %S (%S)", type_str, modifer_str, indir_str); + return result; +} + diff --git a/src/dwarf/dwarf_unwind.h b/src/dwarf/dwarf_unwind.h index f2e6d2c1..54f7f187 100644 --- a/src/dwarf/dwarf_unwind.h +++ b/src/dwarf/dwarf_unwind.h @@ -14,87 +14,19 @@ typedef struct DW_UnwindResult // EH: Exception Frames -typedef U8 DW_EhPtrEnc; -enum +typedef struct DW_UnpackedCIENode { - DW_EhPtrEnc_TypeMask = 0x0F, - DW_EhPtrEnc_Ptr = 0x00, // Pointer sized unsigned value - DW_EhPtrEnc_ULEB128 = 0x01, // Unsigned LE base-128 value - DW_EhPtrEnc_UData2 = 0x02, // Unsigned 16-bit value - DW_EhPtrEnc_UData4 = 0x03, // Unsigned 32-bit value - DW_EhPtrEnc_UData8 = 0x04, // Unsigned 64-bit value - DW_EhPtrEnc_Signed = 0x08, // Signed pointer - DW_EhPtrEnc_SLEB128 = 0x09, // Signed LE base-128 value - DW_EhPtrEnc_SData2 = 0x0A, // Signed 16-bit value - DW_EhPtrEnc_SData4 = 0x0B, // Signed 32-bit value - DW_EhPtrEnc_SData8 = 0x0C, // Signed 64-bit value -}; - -enum -{ - DW_EhPtrEnc_ModifyMask = 0x70, - DW_EhPtrEnc_PcRel = 0x10, // Value is relative to the current program counter. - DW_EhPtrEnc_TextRel = 0x20, // Value is relative to the .text section. - DW_EhPtrEnc_DataRel = 0x30, // Value is relative to the .got or .eh_frame_hdr section. - DW_EhPtrEnc_FuncRel = 0x40, // Value is relative to the function. - DW_EhPtrEnc_Aligned = 0x50, // Value is aligned to an address unit sized boundary. -}; - -enum -{ - DW_EhPtrEnc_Indirect = 0x80, // This flag indicates that value is stored in virtual memory. - DW_EhPtrEnc_Omit = 0xFF, -}; - -typedef struct DW_EhPtrCtx -{ - U64 raw_base_vaddr; // address where pointer is being read - U64 text_vaddr; // base address of section with instructions (used for encoding pointer on SH and IA64) - U64 data_vaddr; // base address of data section (used for encoding pointer on x86-64) - U64 func_vaddr; // base address of function where IP is located -} DW_EhPtrCtx; - -// CIE: Common Information Entry -typedef struct DW_CIEUnpacked -{ - U8 version; - DW_EhPtrEnc lsda_encoding; - DW_EhPtrEnc addr_encoding; - - B32 has_augmentation_size; - U64 augmentation_size; - String8 augmentation; - - U64 code_align_factor; - S64 data_align_factor; - U64 ret_addr_reg; - - U64 handler_ip; - - Rng1U64 cfi_range; -} DW_CIEUnpacked; - -typedef struct DW_CIEUnpackedNode -{ - struct DW_CIEUnpackedNode *next; - DW_CIEUnpacked cie; + struct DW_UnpackedCIENode *next; + DW_UnpackedCIE cie; U64 offset; -} DW_CIEUnpackedNode; - -// FDE: Frame Description Entry -typedef struct DW_FDEUnpacked -{ - Rng1U64 ip_voff_range; - U64 lsda_ip; - Rng1U64 cfi_range; -} DW_FDEUnpacked; +} DW_UnpackedCIENode; // CFI: Call Frame Information typedef struct DW_CFIRecords { B32 valid; - DW_CIEUnpacked cie; - DW_FDEUnpacked fde; + DW_UnpackedCIE cie; + DW_UnpackedFDE fde; } DW_CFIRecords; typedef enum DW_CFICFARule{ @@ -144,10 +76,10 @@ typedef struct DW_CFIRow typedef struct DW_CFIMachine { U64 cells_per_row; - DW_CIEUnpacked *cie; - DW_EhPtrCtx *ptr_ctx; + DW_UnpackedCIE *cie; DW_CFIRow *initial_row; U64 fde_ip; + EH_PtrCtx *ptr_ctx; } DW_CFIMachine; typedef U8 DW_CFADecode; @@ -200,17 +132,14 @@ internal DW_UnwindResult dw_unwind_x64__apply_frame_rules(String8 raw_eh_frame, // x64 Unwind Helper Functions internal void dw_unwind_init_x64(void); -internal U64 dw_unwind_parse_pointer_x64(void *base, Rng1U64 range, DW_EhPtrCtx *ptr_ctx, DW_EhPtrEnc ptr_enc, U64 off, U64 *ptr_out); //- eh_frame parsing -internal void dw_unwind_parse_cie_x64(void *base,Rng1U64 range,DW_EhPtrCtx *ptr_ctx, U64 off, DW_CIEUnpacked *cie_out); -internal void dw_unwind_parse_fde_x64(void *base,Rng1U64 range,DW_EhPtrCtx *ptr_ctx, DW_CIEUnpacked *parent_cie, U64 off, DW_FDEUnpacked *fde_out); -internal DW_CFIRecords dw_unwind_eh_frame_cfi_from_ip_slow_x64(String8 raw_eh_frame, DW_EhPtrCtx *ptr_ctx, U64 ip_voff); -internal DW_CFIRecords dw_unwind_eh_frame_hdr_from_ip_fast_x64(String8 raw_eh_frame, String8 raw_eh_frame_hdr, DW_EhPtrCtx *ptr_ctx, U64 ip_voff); +internal DW_CFIRecords dw_unwind_eh_frame_cfi_from_ip_slow_x64(String8 raw_eh_frame, EH_PtrCtx *ptr_ctx, U64 ip_voff); +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); //- cfi machine -internal DW_CFIMachine dw_unwind_make_machine_x64(U64 cells_per_row, DW_CIEUnpacked *cie, DW_EhPtrCtx *ptr_ctx); +internal DW_CFIMachine dw_unwind_make_machine_x64(U64 cells_per_row, DW_UnpackedCIE *cie, EH_PtrCtx *ptr_ctx); internal void dw_unwind_machine_equip_initial_row_x64(DW_CFIMachine *machine, DW_CFIRow *initial_row); internal void dw_unwind_machine_equip_fde_ip_x64(DW_CFIMachine *machine, U64 fde_ip); diff --git a/src/eh/eh_frame.c b/src/eh/eh_frame.c new file mode 100644 index 00000000..bebc714c --- /dev/null +++ b/src/eh/eh_frame.c @@ -0,0 +1,130 @@ +// Copyright (c) Epic Games Tools +// 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) +{ + U64 ptr_off = off; + + // align read offset as needed + if (encoding == EH_PtrEnc_Aligned) { + ptr_off = AlignPow2(ptr_off, ptr_ctx->ptr_align); + encoding = EH_PtrEnc_Ptr; + } + + // decode pointer value + U64 decode_size = 0; + U64 raw_ptr_size = 0; + U64 raw_ptr = 0; + switch (encoding & EH_PtrEnc_TypeMask) { + default: { InvalidPath; } break; + + case EH_PtrEnc_Ptr : { raw_ptr_size = 8; } goto ufixed; + case EH_PtrEnc_UData2: { raw_ptr_size = 2; } goto ufixed; + case EH_PtrEnc_UData4: { raw_ptr_size = 4; } goto ufixed; + case EH_PtrEnc_UData8: { raw_ptr_size = 8; } goto ufixed; + ufixed: { + decode_size += str8_deserial_read(frame_base, ptr_off, &raw_ptr, raw_ptr_size, raw_ptr_size); + } break; + + // TODO: Signed is actually just a flag that indicates this int is negavite. + // There shouldn't be a read for Signed. + // For instance, (EH_PtrEnc_UData2 | EH_PtrEnc_Signed) == EH_PtrEnc_SData etc. + case EH_PtrEnc_Signed: { raw_ptr_size = 8; } goto sfixed; + + case EH_PtrEnc_SData2: { raw_ptr_size = 2; } goto sfixed; + case EH_PtrEnc_SData4: { raw_ptr_size = 4; } goto sfixed; + case EH_PtrEnc_SData8: { raw_ptr_size = 8; } goto sfixed; + sfixed: { + decode_size += str8_deserial_read(frame_base, ptr_off, &raw_ptr, raw_ptr_size, raw_ptr_size); + raw_ptr = extend_sign64(raw_ptr, raw_ptr_size); + } break; + + case EH_PtrEnc_ULEB128: { decode_size += str8_deserial_read_uleb128(frame_base, ptr_off, &raw_ptr); } break; + case EH_PtrEnc_SLEB128: { decode_size += str8_deserial_read_sleb128(frame_base, ptr_off, (S64*)&raw_ptr); } break; + } + + // apply relative bases + if (decode_size > 0) { + U64 ptr = raw_ptr; + switch (encoding & EH_PtrEnc_ModifierMask) { + case EH_PtrEnc_PcRel: { ptr = ptr_ctx->raw_base_vaddr + off + raw_ptr; } break; + case EH_PtrEnc_TextRel: { ptr = ptr_ctx->text_vaddr + raw_ptr; } break; + case EH_PtrEnc_DataRel: { ptr = ptr_ctx->data_vaddr + raw_ptr; } break; + case EH_PtrEnc_FuncRel: { + Assert(!"TODO: need a sample to verify implementation"); + ptr = ptr_ctx->func_vaddr + raw_ptr; + } break; + } + + if (ptr_out) { + *ptr_out = raw_ptr; + } + } + + return decode_size; +} + +internal U64 +eh_parse_aug_data(String8 aug_string, String8 aug_data, EH_PtrCtx *ptr_ctx, EH_Augmentation *aug_out) +{ + // TODO: + // Handle "eh" param, it indicates presence of EH Data field. + // On 32bit arch it is a 4-byte and on 64-bit 8-byte value. + // Reference: https://refspecs.linuxfoundation.org/LSB_3.0.0/LSB-PDA/LSB-PDA/ehframechpt.html + // Reference doc doesn't clarify structure for EH Data though + + U64 cursor = 0; + + EH_AugFlags aug_flags = 0; + EH_PtrEnc lsda_encoding = EH_PtrEnc_Omit; + EH_PtrEnc addr_encoding = EH_PtrEnc_UData8; + EH_PtrEnc handler_encoding = EH_PtrEnc_Omit; + U64 handler_ip = 0; + if (str8_match(str8_prefix(aug_string, 1), str8_lit("z"), 0)) { + U64 aug_data_off = cursor; + U64 aug_data_size = 0; + cursor += str8_deserial_read_uleb128(aug_string, cursor, &aug_data_size); + cursor += aug_data_size; + + String8 aug_data = str8_substr(aug_data, rng_1u64(aug_data_off, aug_data_off + aug_data_size)); + U64 aug_cursor = 0; + for (U8 *ptr = aug_string.str; ptr < (aug_string.str+aug_string.size); ptr += 1) { + switch (*ptr) { + case 'L': { + aug_cursor += str8_deserial_read_struct(aug_data, aug_cursor, &lsda_encoding); + aug_flags |= EH_AugFlag_HasLSDA; + } break; + case 'P': { + aug_cursor += str8_deserial_read_struct(aug_data, aug_cursor, &handler_encoding); + aug_cursor += str8_deserial_read_dwarf_ptr(aug_data, aug_cursor, ptr_ctx, handler_encoding, &handler_ip); + aug_flags |= EH_AugFlag_HasHandler; + } break; + case 'R': { + aug_cursor += str8_deserial_read_struct(aug_data, aug_cursor, &addr_encoding); + aug_flags |= EH_AugFlag_HasAddrEnc; + } break; + default: { Assert(!"failed to parse augmentation string"); goto exit; } break; + } + } + } + + if (aug_out) { + aug_out->handler_ip = handler_ip; + aug_out->handler_encoding = handler_encoding; + aug_out->lsda_encoding = handler_encoding; + aug_out->addr_encoding = addr_encoding; + aug_out->flags = aug_flags; + } + +exit:; + U64 parse_size = cursor; + return parse_size; +} + +internal U64 +eh_size_from_aug_data(String8 aug_string, String8 data, EH_PtrCtx *ptr_ctx) +{ + return eh_parse_aug_data(aug_string, data, ptr_ctx, 0); +} + diff --git a/src/eh/eh_frame.h b/src/eh/eh_frame.h new file mode 100644 index 00000000..f4ee576c --- /dev/null +++ b/src/eh/eh_frame.h @@ -0,0 +1,74 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef EH_FRAME_H +#define EH_FRAME_H + +typedef U8 EH_PtrEnc; +enum +{ + EH_PtrEnc_Ptr = 0x00, // Pointer sized unsigned value + EH_PtrEnc_ULEB128 = 0x01, // Unsigned LE base-128 value + EH_PtrEnc_UData2 = 0x02, // Unsigned 16-bit value + EH_PtrEnc_UData4 = 0x03, // Unsigned 32-bit value + EH_PtrEnc_UData8 = 0x04, // Unsigned 64-bit value + EH_PtrEnc_Signed = 0x08, // Signed pointer + EH_PtrEnc_SLEB128 = 0x09, // Signed LE base-128 value + EH_PtrEnc_SData2 = 0x0A, // Signed 16-bit value + EH_PtrEnc_SData4 = 0x0B, // Signed 32-bit value + EH_PtrEnc_SData8 = 0x0C, // Signed 64-bit value + + EH_PtrEnc_TypeMask = 0x0F, +}; + +enum +{ + EH_PtrEnc_PcRel = 0x10, // Value is relative to the current program counter. + EH_PtrEnc_TextRel = 0x20, // Value is relative to the .text section. + EH_PtrEnc_DataRel = 0x30, // Value is relative to the .got or .eh_frame_hdr section. + EH_PtrEnc_FuncRel = 0x40, // Value is relative to the function. + EH_PtrEnc_Aligned = 0x50, // Value is aligned to an address unit sized boundary. + + EH_PtrEnc_ModifierMask = 0x70, +}; + +enum +{ + EH_PtrEnc_Indirect = 0x80, // Value is stored in virtual memory. + EH_PtrEnc_Omit = 0xFF, +}; + +typedef struct EH_PtrCtx +{ + U64 raw_base_vaddr; // address where pointer is being read + U64 text_vaddr; // base address of section with instructions (used for encoding pointer on SH and IA64) + U64 data_vaddr; // base address of data section (used for encoding pointer on x86-64) + U64 func_vaddr; // base address of function where IP is located + U64 ptr_align; +} EH_PtrCtx; + +typedef U8 EH_AugFlags; +enum +{ + EH_AugFlag_HasLSDA = (1 << 0), + EH_AugFlag_HasHandler = (1 << 1), + EH_AugFlag_HasAddrEnc = (1 << 2), +}; + +typedef struct EH_Augmentation +{ + EH_AugFlags flags; + U64 handler_ip; + EH_PtrEnc handler_encoding; + EH_PtrEnc lsda_encoding; + EH_PtrEnc addr_encoding; +} EH_Augmentation; + +//////////////////////////////// + +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); + +#endif // EH_FRAME_H + diff --git a/src/radbin/radbin_main.c b/src/radbin/radbin_main.c index e1abc2fc..51a65730 100644 --- a/src/radbin/radbin_main.c +++ b/src/radbin/radbin_main.c @@ -22,6 +22,7 @@ #include "elf/elf_parse.h" #include "codeview/codeview.h" #include "codeview/codeview_parse.h" +#include "eh/eh_frame.h" #include "dwarf/dwarf_inc.h" #include "msf/msf.h" #include "msf/msf_parse.h" @@ -46,6 +47,7 @@ #include "elf/elf_parse.c" #include "codeview/codeview.c" #include "codeview/codeview_parse.c" +#include "eh/eh_frame.c" #include "dwarf/dwarf_inc.c" #include "msf/msf.c" #include "msf/msf_parse.c" diff --git a/src/raddbg/raddbg_main.c b/src/raddbg/raddbg_main.c index a59ab36f..3861c3f4 100644 --- a/src/raddbg/raddbg_main.c +++ b/src/raddbg/raddbg_main.c @@ -239,6 +239,7 @@ #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 "rdi_from_coff/rdi_from_coff.h" #include "rdi_from_elf/rdi_from_elf.h" @@ -285,6 +286,7 @@ #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 "rdi_from_coff/rdi_from_coff.c" #include "rdi_from_elf/rdi_from_elf.c"