|
|
|
@ -32,6 +32,28 @@ enum dwarf_cfa_op {
@@ -32,6 +32,28 @@ enum dwarf_cfa_op {
|
|
|
|
|
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; |
|
|
|
@ -40,6 +62,12 @@ struct dwarf_cie_header {
@@ -40,6 +62,12 @@ struct dwarf_cie_header {
|
|
|
|
|
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 { |
|
|
|
@ -53,10 +81,8 @@ struct dwarf_fde_header {
@@ -53,10 +81,8 @@ struct dwarf_fde_header {
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
static u64 |
|
|
|
|
iterate_call_frame_instructions(struct dwarf_cie_header header, u8 *data, u8 *original_data) |
|
|
|
|
iterate_call_frame_instructions(u8 *data, u64 to_read) |
|
|
|
|
{ |
|
|
|
|
u64 already_read = data - original_data - 4; |
|
|
|
|
u64 leftover = header.length - already_read; |
|
|
|
|
u64 read = 0; |
|
|
|
|
|
|
|
|
|
for (;;) { |
|
|
|
@ -247,7 +273,7 @@ iterate_call_frame_instructions(struct dwarf_cie_header header, u8 *data, u8 *or
@@ -247,7 +273,7 @@ iterate_call_frame_instructions(struct dwarf_cie_header header, u8 *data, u8 *or
|
|
|
|
|
data += increment; |
|
|
|
|
read += increment + 1; |
|
|
|
|
|
|
|
|
|
if (read >= leftover) { |
|
|
|
|
if (read >= to_read) { |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
@ -256,12 +282,10 @@ iterate_call_frame_instructions(struct dwarf_cie_header header, u8 *data, u8 *or
@@ -256,12 +282,10 @@ iterate_call_frame_instructions(struct dwarf_cie_header header, u8 *data, u8 *or
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static u64 |
|
|
|
|
read_one_cie(u64 length, u8 *data, u8 *original_data) |
|
|
|
|
read_one_cie(struct dwarf_cie_header *header, u64 length, u8 *data, u8 *original_data) |
|
|
|
|
{ |
|
|
|
|
struct dwarf_cie_header header = { 0 }; |
|
|
|
|
|
|
|
|
|
header.length = length; |
|
|
|
|
header.version = *data++; |
|
|
|
|
header->length = length; |
|
|
|
|
header->version = *data++; |
|
|
|
|
|
|
|
|
|
char *augmenation_string = (char *) data; |
|
|
|
|
|
|
|
|
@ -282,50 +306,204 @@ read_one_cie(u64 length, u8 *data, u8 *original_data)
@@ -282,50 +306,204 @@ read_one_cie(u64 length, u8 *data, u8 *original_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); |
|
|
|
|
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 += 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); |
|
|
|
|
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_one_fde(u64 length, u32 cie_offset, u8 *data, u8 *original_data) |
|
|
|
|
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); |
|
|
|
|
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; |
|
|
|
|
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(hh, data, original_data); |
|
|
|
|
data += iterate_call_frame_instructions(data, header.length - (data - original_data - 4)); |
|
|
|
|
|
|
|
|
|
return(0); |
|
|
|
|
return(data - original_data); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static u64 |
|
|
|
|
read_one_call_frame_record(u8 *data) |
|
|
|
|
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; |
|
|
|
@ -336,6 +514,11 @@ read_one_call_frame_record(u8 *data)
@@ -336,6 +514,11 @@ read_one_call_frame_record(u8 *data)
|
|
|
|
|
length = length32; |
|
|
|
|
data += 4; |
|
|
|
|
|
|
|
|
|
if (length == 0) { |
|
|
|
|
// NOTE(aolo2): terminator
|
|
|
|
|
return(4); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (length == 0) { |
|
|
|
|
memcpy(&length, data, 8); |
|
|
|
|
data += 8; |
|
|
|
@ -347,23 +530,24 @@ read_one_call_frame_record(u8 *data)
@@ -347,23 +530,24 @@ read_one_call_frame_record(u8 *data)
|
|
|
|
|
u64 result; |
|
|
|
|
|
|
|
|
|
if (cie_id == 0) { |
|
|
|
|
result = read_one_cie(length, data, original_data); |
|
|
|
|
result = read_one_cie(last_cie, length, data, original_data); |
|
|
|
|
} else { |
|
|
|
|
result = read_one_fde(length, cie_id, data, original_data); |
|
|
|
|
result = read_one_fde(proc, last_cie, length, cie_id, data, original_data, section_base); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return(result); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static void |
|
|
|
|
parse_eh_frame(u8 *file) |
|
|
|
|
parse_eh_frame(struct mi_process proc) |
|
|
|
|
{ |
|
|
|
|
struct elf_section_table_entry_x64 eh_frame = get_section_entry(file, ".eh_frame"); |
|
|
|
|
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(file + eh_frame.offset_in_file + read); |
|
|
|
|
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; |
|
|
|
|