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, }; enum dwarf_cie_pointer_format { DW_EH_PE_absptr = 0x00, DW_EH_PE_uleb128 = 0x01, DW_EH_PE_udata2 = 0x02, DW_EH_PE_udata4 = 0x03, DW_EH_PE_udata8 = 0x04, DW_EH_PE_sleb128 = 0x09, DW_EH_PE_sdata2 = 0x0A, DW_EH_PE_sdata4 = 0x0B, DW_EH_PE_sdata8 = 0x0C, }; enum dwarf_cie_pointer_application { DW_EH_PE_pcrel = 0x10, DW_EH_PE_textrel = 0x20, DW_EH_PE_datarel = 0x30, DW_EH_PE_funcrel = 0x40, DW_EH_PE_aligned = 0x50, DW_EH_PE_indirect = 0x80, DW_EH_PE_omit = 0xFF, }; 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; int has_z; enum dwarf_cie_pointer_format pointer_format; enum dwarf_cie_pointer_application pointer_application; u8 pointer_indirect; }; 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(u8 *data, u64 to_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 >= to_read) { break; } } return(read); } static u64 read_one_cie(struct dwarf_cie_header *header, u64 length, u8 *data, u8 *original_data) { 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; 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; } header->has_z = has_z; data += iterate_call_frame_instructions(data, header->length - (data - original_data - 4)); if (has_R) { // NOTE(aolo2): this shit is undocumented. Best sources I could find: // - gdb source code (dwarf2cfi.c) // - this random blog post: https://dandylife.net/blog/archives/686 // - this pdf by Igor Skochinsky: // https://www.hexblog.com/wp-content/uploads/2012/06/Recon-2012-Skochinsky-Compiler-Internals.pdf // - perf source code: http://ansymbol.com/linux/v3.13/source/tools/perf/util/unwind.c // NOTE(aolo2): WAIT! There ARE docs! There are just no links to them from the .eh_frame page! // https://refspecs.linuxfoundation.org/LSB_3.0.0/LSB-PDA/LSB-PDA.junk/dwarfext.html u8 byte = *header->augmentation_data; header->pointer_format = byte & 0x0f; header->pointer_application = byte & 0x70; header->pointer_indirect = byte & 0x80; } return(data - original_data); } static u64 read_encoded_pointer(struct mi_process proc, struct dwarf_cie_header *cie, u8 *data, u64 *dest, u8 *section_base) { u64 offset = 0; s64 final_value; switch (cie->pointer_format) { case DW_EH_PE_absptr: { u64 value; memcpy(&value, data, 8); offset = 8; final_value = value; break; } case DW_EH_PE_uleb128: { u32 value; offset = decode_leb128(data, &value); final_value= value; break; } case DW_EH_PE_sleb128: { s32 value; offset = decode_leb128s(data, &value); final_value = value; break; } case DW_EH_PE_udata2: { u16 value; memcpy(&value, data, 2); offset = 2; final_value = value; break; } case DW_EH_PE_udata4: { u32 value; memcpy(&value, data, 4); offset = 4; final_value = value; break; } case DW_EH_PE_udata8: { u64 value; memcpy(&value, data, 8); offset = 8; final_value = value; break; } case DW_EH_PE_sdata2: { s16 value; memcpy(&value, data, 2); offset = 2; final_value = value; break; } case DW_EH_PE_sdata4: { s32 value; memcpy(&value, data, 4); offset = 4; final_value = value; break; } case DW_EH_PE_sdata8: { s64 value; memcpy(&value, data, 8); offset = 8; final_value = value; break; } } struct mi_registers regs = get_process_registers(proc); struct mi_function *func = get_function_around_pc(proc, regs.rip - proc.base_address); switch (cie->pointer_application) { case DW_EH_PE_pcrel: { *dest = data - proc.elf + final_value; break; } case DW_EH_PE_textrel: { struct elf_section_table_entry_x64 text = get_section_entry(proc.elf, ".text"); *dest = text.virtual_address + final_value - proc.base_address; break; } case DW_EH_PE_datarel: { struct elf_section_table_entry_x64 got = get_section_entry(proc.elf, ".got"); struct elf_section_table_entry_x64 eh_frame_hdr = get_section_entry(proc.elf, ".eh_frame_hdr"); if (got.virtual_address) { *dest = got.virtual_address + final_value - proc.base_address; } else { *dest = eh_frame_hdr.virtual_address + final_value - proc.base_address; } break; } case DW_EH_PE_funcrel: { if (func) { *dest = func->low_pc + final_value; } else { DIE("could not find function around pc while decoding address!\n"); } break; } default: { DIE("unsupported pointer application: DW_EH_PE_aligned, DW_EH_PE_indirect, or DW_EH_PE_omit\n"); } } return(offset); } static u64 read_one_fde(struct mi_process proc, struct dwarf_cie_header *cie, u64 length, u32 cie_offset, u8 *data, u8 *original_data, u8 *section_base) { struct dwarf_fde_header header = { 0 }; header.length = length; header.cie = (struct dwarf_cie_header *) (data - 4 - cie_offset); u32 pointer_size = read_encoded_pointer(proc, cie, data, &header.low_pc, section_base); data += pointer_size; u64 fde_length = 0; if (pointer_size == 2) { memcpy(&fde_length, data, 2); data += 2; } else if (pointer_size == 4) { memcpy(&fde_length, data, 4); data += 4; } else if (pointer_size == 8) { memcpy(&fde_length, data, 8); data += 8; } header.high_pc = header.low_pc + fde_length; 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(data, header.length - (data - original_data - 4)); return(data - original_data); } static u64 read_one_call_frame_record(struct mi_process proc, struct dwarf_cie_header *last_cie, u8 *section_base, u8 *data) { u8 *original_data = data; u64 length; u32 length32; u32 cie_id; memcpy(&length32, data, 4); length = length32; data += 4; if (length == 0) { // NOTE(aolo2): terminator return(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(last_cie, length, data, original_data); } else { result = read_one_fde(proc, last_cie, length, cie_id, data, original_data, section_base); } return(result); } static void parse_eh_frame(struct mi_process proc) { struct elf_section_table_entry_x64 eh_frame = get_section_entry(proc.elf, ".eh_frame"); struct dwarf_cie_header last_cie = { 0 }; u64 read = 0; for (;;) { u64 size = read_one_call_frame_record(proc, &last_cie, proc.elf + eh_frame.offset_in_file, proc.elf + eh_frame.offset_in_file + read); read += size; if (read >= eh_frame.size) { break; } } }