static u64 iterate_call_frame_instructions(struct dwarf_cie *cie, u8 *data, u64 to_read, struct dwarf_regset *regset, u64 location) { 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; if (regset) { regset->loc += delta * cie->code_alignment; } } 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; if (regset) { regset->loc = address; // TODO: encoding?? } break; } case DW_CFA_advance_loc1: { u8 advance; memcpy(&advance, data, 1); increment = 1; if (regset) { regset->loc += advance * cie->code_alignment; } break; } case DW_CFA_advance_loc2: { u16 advance; memcpy(&advance, data, 2); increment = 2; if (regset) { regset->loc += advance * cie->code_alignment; } break; } case DW_CFA_advance_loc4: { u32 advance; memcpy(&advance, data, 4); increment = 4; if (regset) { regset->loc += advance * cie->code_alignment; } 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 + increment, &nonfactored_offset); if (regset) { regset->cfa_offset = nonfactored_offset; regset->cfa_register = reg; } break; } case DW_CFA_def_cfa_register: { u32 reg; increment += decode_leb128(data, ®); if (regset) { regset->cfa_register = reg; } break; } case DW_CFA_def_cfa_offset: { u32 offset; increment += decode_leb128(data, &offset); if (regset) { regset->cfa_offset = 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; } } } if (location && regset->loc > location) { regset->cfa = regset->system[regset->cfa_register] + regset->cfa_offset; break; } data += increment; read += increment + 1; if (read >= to_read) { break; } } return(read); } static u64 read_one_cie(struct dwarf_cie *header, u64 length, u8 *data, u8 *original_data) { header->length = length; header->version = *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; header->instructions = data; header->instructions_length = header->length - (data - original_data - 4); data += header->instructions_length; 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 *cie, u8 *data, u64 *dest) { 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; } } 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; } default: { DIE("unsupported pointer application: DW_EH_PE_funcrel, 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 *cie, u64 length, u8 *data, u8 *original_data, struct dwarf_fde *header) { header->length = length; header->cie = *cie; u32 pointer_size = read_encoded_pointer(proc, cie, data, &header->low_pc); 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; } header->instructions = data; header->instructions_length = header->length - (data - original_data - 4); data += header->instructions_length; return(data - original_data); } static u64 read_one_call_frame_record(struct mi_process proc, struct dwarf_cie *last_cie, struct dwarf_fde *last_fde, int *is_cie, 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); *is_cie = 1; } else { result = read_one_fde(proc, last_cie, length, data, original_data, last_fde); *is_cie = 0; } return(result); } static struct dwarf_fde eh_frame_find_fde(struct mi_process proc, u64 pc) { struct elf_section_table_entry_x64 eh_frame = get_section_entry(proc.elf, ".eh_frame"); struct dwarf_cie last_cie = { 0 }; struct dwarf_fde last_fde = { 0 }; int is_cie = 0; u64 read = 0; for (;;) { u64 size = read_one_call_frame_record(proc, &last_cie, &last_fde, &is_cie, proc.elf + eh_frame.offset_in_file + read); if (!is_cie) { if (last_fde.low_pc <= pc && pc < last_fde.high_pc) { return(last_fde); } } read += size; if (read >= eh_frame.size) { break; } } last_fde.length = 0; return(last_fde); } static struct dwarf_regset eh_frame_init_registers(struct mi_registers regs, struct dwarf_cie cie) { struct dwarf_regset regset = { 0 }; regset.system[0] = regs._sys.rax; regset.system[1] = regs._sys.rdx; regset.system[2] = regs._sys.rcx; regset.system[3] = regs._sys.rbx; regset.system[4] = regs._sys.rsi; regset.system[5] = regs._sys.rdi; regset.system[6] = regs._sys.rbp; regset.system[7] = regs._sys.rsp; regset.system[8] = regs._sys.r8; regset.system[9] = regs._sys.r9; regset.system[10] = regs._sys.r10; regset.system[11] = regs._sys.r11; regset.system[12] = regs._sys.r12; regset.system[13] = regs._sys.r13; regset.system[14] = regs._sys.r14; regset.system[15] = regs._sys.r15; iterate_call_frame_instructions(&cie, cie.instructions, cie.instructions_length, ®set, 0); return(regset); } static struct dwarf_regset eh_frame_find_pc(struct dwarf_fde fde, struct dwarf_regset regset, u64 pc) { regset.loc = fde.low_pc; iterate_call_frame_instructions(&fde.cie, fde.instructions, fde.instructions_length, ®set, pc); return(regset); }