|
|
|
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);
|
|
|
|
}
|