enum dwarf_cfa_op_base { DW_CFA_advance_loc = 0x1, DW_CFA_offset = 0x2, DW_CFA_restore = 0x3 }; enum dwarf_cfa_op { DW_CFA_nop = 0x00, DW_CFA_set_loc = 0x01, DW_CFA_advance_loc1 = 0x02, DW_CFA_advance_loc2 = 0x03, DW_CFA_advance_loc4 = 0x04, DW_CFA_offset_extended = 0x05, DW_CFA_restore_extended = 0x06, DW_CFA_undefined = 0x07, DW_CFA_same_value = 0x08, DW_CFA_register = 0x09, DW_CFA_remember_state = 0x0a, DW_CFA_restore_state = 0x0b, DW_CFA_def_cfa = 0x0c, DW_CFA_def_cfa_register = 0x0d, DW_CFA_def_cfa_offset = 0x0e, DW_CFA_def_cfa_expression = 0x0f, DW_CFA_expression = 0x10, DW_CFA_offset_extended_sf = 0x11, DW_CFA_def_cfa_sf = 0x12, DW_CFA_def_cfa_offset_sf = 0x13, DW_CFA_val_offset = 0x14, DW_CFA_val_offset_sf = 0x15, DW_CFA_val_expression = 0x16, DW_CFA_lo_user = 0x1c, DW_CFA_hi_user = 0x3f, }; struct dwarf_cie_header { u32 length; u8 version; u32 code_alignment; s32 data_alignment; u32 return_address_register; u32 augmentation_data_length; u8 *augmentation_data; }; struct dwarf_fde_header { u32 length; u64 optional_length; struct dwarf_cie_header *cie; u64 low_pc; u64 high_pc; u32 augmentation_data_length; u8 *augmentation_data; }; static u64 iterate_call_frame_instructions(struct dwarf_cie_header header, u8 *data, u8 *original_data) { u64 already_read = data - original_data - 4; u64 leftover = header.length - already_read; u64 read = 0; for (;;) { u8 op_byte = *data++; enum dwarf_cfa_op_base high_two = op_byte >> 6; enum dwarf_cfa_op low_six = op_byte & 0x3f; u32 increment = 0; if (high_two == DW_CFA_advance_loc) { u8 delta = low_six; } else if (high_two == DW_CFA_offset) { u8 reg = low_six; u32 factored_offset; increment += decode_leb128(data, &factored_offset); } else if (high_two == DW_CFA_restore) { u8 reg = low_six; } else if (high_two == 0) { switch (low_six) { case DW_CFA_nop: { break; } case DW_CFA_set_loc: { u64 address; memcpy(&address, data, 8); increment = 8; break; } case DW_CFA_advance_loc1: { u8 advance; memcpy(&advance, data, 1); increment = 1; break; } case DW_CFA_advance_loc2: { u16 advance; memcpy(&advance, data, 2); increment = 2; break; } case DW_CFA_advance_loc4: { u32 advance; memcpy(&advance, data, 4); increment = 4; break; } case DW_CFA_offset_extended: { u32 reg; u32 offset; increment += decode_leb128(data, ®); increment += decode_leb128(data, &offset); break; } case DW_CFA_restore_extended: { u32 reg; increment += decode_leb128(data, ®); break; } case DW_CFA_undefined: { u32 reg; increment += decode_leb128(data, ®); break; } case DW_CFA_same_value: { u32 reg; increment += decode_leb128(data, ®); break; } case DW_CFA_register: { u32 reg; s32 factored_offset; increment += decode_leb128(data, ®); increment += decode_leb128s(data, &factored_offset); break; } case DW_CFA_remember_state: { break; } case DW_CFA_restore_state: { break; } case DW_CFA_def_cfa: { u32 reg; u32 nonfactored_offset; increment += decode_leb128(data, ®); increment += decode_leb128(data, &nonfactored_offset); break; } case DW_CFA_def_cfa_register: { u32 reg; increment += decode_leb128(data, ®); break; } case DW_CFA_def_cfa_offset: { u32 offset; increment += decode_leb128(data, &offset); break; } case DW_CFA_def_cfa_expression: { u32 length; increment += decode_leb128(data, &length); increment += length; break; } case DW_CFA_expression: { u32 reg; u32 length; increment += decode_leb128(data, ®); increment += decode_leb128(data, &length); increment += length; break; } case DW_CFA_offset_extended_sf: { u32 reg; s32 factored_offset; increment += decode_leb128(data, ®); increment += decode_leb128s(data, &factored_offset); break; } case DW_CFA_def_cfa_sf: { u32 reg; s32 factored_offset; increment += decode_leb128(data, ®); increment += decode_leb128s(data, &factored_offset); break; } case DW_CFA_def_cfa_offset_sf: { s32 factored_offset; increment += decode_leb128s(data, &factored_offset); break; } case DW_CFA_val_offset: { u32 reg; u32 factored_offset; increment += decode_leb128(data, ®); increment += decode_leb128(data, &factored_offset); break; } case DW_CFA_val_offset_sf: { u32 reg; s32 factored_offset; increment += decode_leb128(data, ®); increment += decode_leb128s(data, &factored_offset); break; } case DW_CFA_val_expression: { u32 reg; u32 length; increment += decode_leb128(data, ®); increment += decode_leb128(data, &length); increment += length; break; } case DW_CFA_lo_user: { break; } case DW_CFA_hi_user: { break; } } } data += increment; read += increment + 1; if (read >= leftover) { break; } } return(read); } static u64 read_one_cie(u64 length, u8 *data, u8 *original_data) { struct dwarf_cie_header header = { 0 }; header.length = length; header.version = *data++; char *augmenation_string = (char *) data; // NOTE: null-terminated string int has_z = 0; int has_L = 0; int has_P = 0; int has_R = 0; while (*data) { if (*data == 'z') has_z = 1; if (*data == 'L') has_L = 1; if (*data == 'P') has_P = 1; if (*data == 'R') has_R = 1; ++data; } ++data; (void) has_L; (void) has_P; (void) has_R; data += decode_leb128(data, &header.code_alignment); data += decode_leb128s(data, &header.data_alignment); data += decode_leb128(data, &header.return_address_register); if (has_z) { data += decode_leb128(data, &header.augmentation_data_length); header.augmentation_data = data; data += header.augmentation_data_length; } data += iterate_call_frame_instructions(header, data, original_data); return(data - original_data); } static u64 read_one_fde(u64 length, u32 cie_offset, u8 *data, u8 *original_data) { struct dwarf_fde_header header = { 0 }; header.length = length; header.cie = (struct dwarf_cie_header *) (data - 4 - cie_offset); memcpy(&header.low_pc, data, 8); data += 8; memcpy(&header.high_pc, data, 8); data += 8; header.high_pc = header.low_pc + header.high_pc; // if (cie has 'z') data += decode_leb128(data, &header.augmentation_data_length); header.augmentation_data = data; data += header.augmentation_data_length; struct dwarf_cie_header hh = { 0 }; data += iterate_call_frame_instructions(hh, data, original_data); return(0); } static u64 read_one_call_frame_record(u8 *data) { u8 *original_data = data; u64 length; u32 length32; u32 cie_id; memcpy(&length32, data, 4); length = length32; data += 4; if (length == 0) { memcpy(&length, data, 8); data += 8; } memcpy(&cie_id, data, 4); data += 4; u64 result; if (cie_id == 0) { result = read_one_cie(length, data, original_data); } else { result = read_one_fde(length, cie_id, data, original_data); } return(result); } static void parse_eh_frame(u8 *file) { struct elf_section_table_entry_x64 eh_frame = get_section_entry(file, ".eh_frame"); u64 read = 0; for (;;) { u64 size = read_one_call_frame_record(file + eh_frame.offset_in_file + read); read += size; if (read >= eh_frame.size) { break; } } }