forked from OSchip/llvm-project
1516 lines
51 KiB
C
1516 lines
51 KiB
C
#include <fcntl.h>
|
|
#include <inttypes.h>
|
|
#include <mach-o/compact_unwind_encoding.h>
|
|
#include <mach-o/loader.h>
|
|
#include <mach-o/nlist.h>
|
|
#include <mach/machine.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/errno.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
|
|
enum {
|
|
UNWIND_ARM64_MODE_MASK = 0x0F000000,
|
|
UNWIND_ARM64_MODE_FRAMELESS = 0x02000000,
|
|
UNWIND_ARM64_MODE_DWARF = 0x03000000,
|
|
UNWIND_ARM64_MODE_FRAME = 0x04000000,
|
|
|
|
UNWIND_ARM64_FRAME_X19_X20_PAIR = 0x00000001,
|
|
UNWIND_ARM64_FRAME_X21_X22_PAIR = 0x00000002,
|
|
UNWIND_ARM64_FRAME_X23_X24_PAIR = 0x00000004,
|
|
UNWIND_ARM64_FRAME_X25_X26_PAIR = 0x00000008,
|
|
UNWIND_ARM64_FRAME_X27_X28_PAIR = 0x00000010,
|
|
UNWIND_ARM64_FRAME_D8_D9_PAIR = 0x00000100,
|
|
UNWIND_ARM64_FRAME_D10_D11_PAIR = 0x00000200,
|
|
UNWIND_ARM64_FRAME_D12_D13_PAIR = 0x00000400,
|
|
UNWIND_ARM64_FRAME_D14_D15_PAIR = 0x00000800,
|
|
|
|
UNWIND_ARM64_FRAMELESS_STACK_SIZE_MASK = 0x00FFF000,
|
|
UNWIND_ARM64_DWARF_SECTION_OFFSET = 0x00FFFFFF,
|
|
};
|
|
|
|
enum {
|
|
UNWIND_ARM_MODE_MASK = 0x0F000000,
|
|
UNWIND_ARM_MODE_FRAME = 0x01000000,
|
|
UNWIND_ARM_MODE_FRAME_D = 0x02000000,
|
|
UNWIND_ARM_MODE_DWARF = 0x04000000,
|
|
|
|
UNWIND_ARM_FRAME_STACK_ADJUST_MASK = 0x00C00000,
|
|
|
|
UNWIND_ARM_FRAME_FIRST_PUSH_R4 = 0x00000001,
|
|
UNWIND_ARM_FRAME_FIRST_PUSH_R5 = 0x00000002,
|
|
UNWIND_ARM_FRAME_FIRST_PUSH_R6 = 0x00000004,
|
|
|
|
UNWIND_ARM_FRAME_SECOND_PUSH_R8 = 0x00000008,
|
|
UNWIND_ARM_FRAME_SECOND_PUSH_R9 = 0x00000010,
|
|
UNWIND_ARM_FRAME_SECOND_PUSH_R10 = 0x00000020,
|
|
UNWIND_ARM_FRAME_SECOND_PUSH_R11 = 0x00000040,
|
|
UNWIND_ARM_FRAME_SECOND_PUSH_R12 = 0x00000080,
|
|
|
|
UNWIND_ARM_FRAME_D_REG_COUNT_MASK = 0x00000700,
|
|
|
|
UNWIND_ARM_DWARF_SECTION_OFFSET = 0x00FFFFFF,
|
|
};
|
|
|
|
#define EXTRACT_BITS(value, mask) \
|
|
((value >> __builtin_ctz(mask)) & (((1 << __builtin_popcount(mask))) - 1))
|
|
|
|
// A quick sketch of a program which can parse the compact unwind info
|
|
// used on Darwin systems for exception handling. The output of
|
|
// unwinddump will be more authoritative/reliable but this program
|
|
// can dump at least the UNWIND_X86_64_MODE_RBP_FRAME format entries
|
|
// correctly.
|
|
|
|
struct symbol {
|
|
uint64_t file_address;
|
|
const char *name;
|
|
};
|
|
|
|
int symbol_compare(const void *a, const void *b) {
|
|
return (int)((struct symbol *)a)->file_address -
|
|
((struct symbol *)b)->file_address;
|
|
}
|
|
|
|
struct baton {
|
|
cpu_type_t cputype;
|
|
|
|
uint8_t *mach_header_start; // pointer into this program's address space
|
|
uint8_t *compact_unwind_start; // pointer into this program's address space
|
|
|
|
int addr_size; // 4 or 8 bytes, the size of addresses in this file
|
|
|
|
uint64_t text_segment_vmaddr; // __TEXT segment vmaddr
|
|
uint64_t text_segment_file_offset;
|
|
|
|
uint64_t text_section_vmaddr; // __TEXT,__text section vmaddr
|
|
uint64_t text_section_file_offset;
|
|
|
|
uint64_t eh_section_file_address; // the file address of the __TEXT,__eh_frame
|
|
// section
|
|
|
|
uint8_t
|
|
*lsda_array_start; // for the currently-being-processed first-level index
|
|
uint8_t
|
|
*lsda_array_end; // the lsda_array_start for the NEXT first-level index
|
|
|
|
struct symbol *symbols;
|
|
int symbols_count;
|
|
|
|
uint64_t *function_start_addresses;
|
|
int function_start_addresses_count;
|
|
|
|
int current_index_table_number;
|
|
|
|
struct unwind_info_section_header unwind_header;
|
|
struct unwind_info_section_header_index_entry first_level_index_entry;
|
|
struct unwind_info_compressed_second_level_page_header
|
|
compressed_second_level_page_header;
|
|
struct unwind_info_regular_second_level_page_header
|
|
regular_second_level_page_header;
|
|
};
|
|
|
|
uint64_t read_leb128(uint8_t **offset) {
|
|
uint64_t result = 0;
|
|
int shift = 0;
|
|
while (1) {
|
|
uint8_t byte = **offset;
|
|
*offset = *offset + 1;
|
|
result |= (byte & 0x7f) << shift;
|
|
if ((byte & 0x80) == 0)
|
|
break;
|
|
shift += 7;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// step through the load commands in a thin mach-o binary,
|
|
// find the cputype and the start of the __TEXT,__unwind_info
|
|
// section, return a pointer to that section or NULL if not found.
|
|
|
|
static void scan_macho_load_commands(struct baton *baton) {
|
|
struct symtab_command symtab_cmd;
|
|
uint64_t linkedit_segment_vmaddr;
|
|
uint64_t linkedit_segment_file_offset;
|
|
|
|
baton->compact_unwind_start = 0;
|
|
|
|
uint32_t *magic = (uint32_t *)baton->mach_header_start;
|
|
|
|
if (*magic != MH_MAGIC && *magic != MH_MAGIC_64) {
|
|
printf("Unexpected magic number 0x%x in header, exiting.", *magic);
|
|
exit(1);
|
|
}
|
|
|
|
bool is_64bit = false;
|
|
if (*magic == MH_MAGIC_64)
|
|
is_64bit = true;
|
|
|
|
uint8_t *offset = baton->mach_header_start;
|
|
|
|
struct mach_header mh;
|
|
memcpy(&mh, offset, sizeof(struct mach_header));
|
|
if (is_64bit)
|
|
offset += sizeof(struct mach_header_64);
|
|
else
|
|
offset += sizeof(struct mach_header);
|
|
|
|
if (is_64bit)
|
|
baton->addr_size = 8;
|
|
else
|
|
baton->addr_size = 4;
|
|
|
|
baton->cputype = mh.cputype;
|
|
|
|
uint8_t *start_of_load_commands = offset;
|
|
|
|
uint32_t cur_cmd = 0;
|
|
while (cur_cmd < mh.ncmds &&
|
|
(offset - start_of_load_commands) < mh.sizeofcmds) {
|
|
struct load_command lc;
|
|
uint32_t *lc_cmd = (uint32_t *)offset;
|
|
uint32_t *lc_cmdsize = (uint32_t *)offset + 1;
|
|
uint8_t *start_of_this_load_cmd = offset;
|
|
|
|
if (*lc_cmd == LC_SEGMENT || *lc_cmd == LC_SEGMENT_64) {
|
|
char segment_name[17];
|
|
segment_name[0] = '\0';
|
|
uint32_t nsects = 0;
|
|
uint64_t segment_offset = 0;
|
|
uint64_t segment_vmaddr = 0;
|
|
|
|
if (*lc_cmd == LC_SEGMENT_64) {
|
|
struct segment_command_64 seg;
|
|
memcpy(&seg, offset, sizeof(struct segment_command_64));
|
|
memcpy(&segment_name, &seg.segname, 16);
|
|
segment_name[16] = '\0';
|
|
nsects = seg.nsects;
|
|
segment_offset = seg.fileoff;
|
|
segment_vmaddr = seg.vmaddr;
|
|
offset += sizeof(struct segment_command_64);
|
|
if ((seg.flags & SG_PROTECTED_VERSION_1) == SG_PROTECTED_VERSION_1) {
|
|
printf("Segment '%s' is encrypted.\n", segment_name);
|
|
}
|
|
}
|
|
|
|
if (*lc_cmd == LC_SEGMENT) {
|
|
struct segment_command seg;
|
|
memcpy(&seg, offset, sizeof(struct segment_command));
|
|
memcpy(&segment_name, &seg.segname, 16);
|
|
segment_name[16] = '\0';
|
|
nsects = seg.nsects;
|
|
segment_offset = seg.fileoff;
|
|
segment_vmaddr = seg.vmaddr;
|
|
offset += sizeof(struct segment_command);
|
|
if ((seg.flags & SG_PROTECTED_VERSION_1) == SG_PROTECTED_VERSION_1) {
|
|
printf("Segment '%s' is encrypted.\n", segment_name);
|
|
}
|
|
}
|
|
|
|
if (nsects != 0 && strcmp(segment_name, "__TEXT") == 0) {
|
|
baton->text_segment_vmaddr = segment_vmaddr;
|
|
baton->text_segment_file_offset = segment_offset;
|
|
|
|
uint32_t current_sect = 0;
|
|
while (current_sect < nsects &&
|
|
(offset - start_of_this_load_cmd) < *lc_cmdsize) {
|
|
char sect_name[17];
|
|
memcpy(§_name, offset, 16);
|
|
sect_name[16] = '\0';
|
|
if (strcmp(sect_name, "__unwind_info") == 0) {
|
|
if (is_64bit) {
|
|
struct section_64 sect;
|
|
memset(§, 0, sizeof(struct section_64));
|
|
memcpy(§, offset, sizeof(struct section_64));
|
|
baton->compact_unwind_start =
|
|
baton->mach_header_start + sect.offset;
|
|
} else {
|
|
struct section sect;
|
|
memset(§, 0, sizeof(struct section));
|
|
memcpy(§, offset, sizeof(struct section));
|
|
baton->compact_unwind_start =
|
|
baton->mach_header_start + sect.offset;
|
|
}
|
|
}
|
|
if (strcmp(sect_name, "__eh_frame") == 0) {
|
|
if (is_64bit) {
|
|
struct section_64 sect;
|
|
memset(§, 0, sizeof(struct section_64));
|
|
memcpy(§, offset, sizeof(struct section_64));
|
|
baton->eh_section_file_address = sect.addr;
|
|
} else {
|
|
struct section sect;
|
|
memset(§, 0, sizeof(struct section));
|
|
memcpy(§, offset, sizeof(struct section));
|
|
baton->eh_section_file_address = sect.addr;
|
|
}
|
|
}
|
|
if (strcmp(sect_name, "__text") == 0) {
|
|
if (is_64bit) {
|
|
struct section_64 sect;
|
|
memset(§, 0, sizeof(struct section_64));
|
|
memcpy(§, offset, sizeof(struct section_64));
|
|
baton->text_section_vmaddr = sect.addr;
|
|
baton->text_section_file_offset = sect.offset;
|
|
} else {
|
|
struct section sect;
|
|
memset(§, 0, sizeof(struct section));
|
|
memcpy(§, offset, sizeof(struct section));
|
|
baton->text_section_vmaddr = sect.addr;
|
|
}
|
|
}
|
|
if (is_64bit) {
|
|
offset += sizeof(struct section_64);
|
|
} else {
|
|
offset += sizeof(struct section);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (strcmp(segment_name, "__LINKEDIT") == 0) {
|
|
linkedit_segment_vmaddr = segment_vmaddr;
|
|
linkedit_segment_file_offset = segment_offset;
|
|
}
|
|
}
|
|
|
|
if (*lc_cmd == LC_SYMTAB) {
|
|
memcpy(&symtab_cmd, offset, sizeof(struct symtab_command));
|
|
}
|
|
|
|
if (*lc_cmd == LC_DYSYMTAB) {
|
|
struct dysymtab_command dysymtab_cmd;
|
|
memcpy(&dysymtab_cmd, offset, sizeof(struct dysymtab_command));
|
|
|
|
int nlist_size = 12;
|
|
if (is_64bit)
|
|
nlist_size = 16;
|
|
|
|
char *string_table =
|
|
(char *)(baton->mach_header_start + symtab_cmd.stroff);
|
|
uint8_t *local_syms = baton->mach_header_start + symtab_cmd.symoff +
|
|
(dysymtab_cmd.ilocalsym * nlist_size);
|
|
int local_syms_count = dysymtab_cmd.nlocalsym;
|
|
uint8_t *exported_syms = baton->mach_header_start + symtab_cmd.symoff +
|
|
(dysymtab_cmd.iextdefsym * nlist_size);
|
|
int exported_syms_count = dysymtab_cmd.nextdefsym;
|
|
|
|
// We're only going to create records for a small number of these symbols
|
|
// but to
|
|
// simplify the memory management I'll allocate enough space to store all
|
|
// of them.
|
|
baton->symbols = (struct symbol *)malloc(
|
|
sizeof(struct symbol) * (local_syms_count + exported_syms_count));
|
|
baton->symbols_count = 0;
|
|
|
|
for (int i = 0; i < local_syms_count; i++) {
|
|
struct nlist_64 nlist;
|
|
memset(&nlist, 0, sizeof(struct nlist_64));
|
|
if (is_64bit) {
|
|
memcpy(&nlist, local_syms + (i * nlist_size),
|
|
sizeof(struct nlist_64));
|
|
} else {
|
|
struct nlist nlist_32;
|
|
memset(&nlist_32, 0, sizeof(struct nlist));
|
|
memcpy(&nlist_32, local_syms + (i * nlist_size),
|
|
sizeof(struct nlist));
|
|
nlist.n_un.n_strx = nlist_32.n_un.n_strx;
|
|
nlist.n_type = nlist_32.n_type;
|
|
nlist.n_sect = nlist_32.n_sect;
|
|
nlist.n_desc = nlist_32.n_desc;
|
|
nlist.n_value = nlist_32.n_value;
|
|
}
|
|
if ((nlist.n_type & N_STAB) == 0 &&
|
|
((nlist.n_type & N_EXT) == 1 ||
|
|
((nlist.n_type & N_TYPE) == N_TYPE && nlist.n_sect != NO_SECT)) &&
|
|
nlist.n_value != 0 && nlist.n_value != baton->text_segment_vmaddr) {
|
|
baton->symbols[baton->symbols_count].file_address = nlist.n_value;
|
|
if (baton->cputype == CPU_TYPE_ARM)
|
|
baton->symbols[baton->symbols_count].file_address =
|
|
baton->symbols[baton->symbols_count].file_address & ~1;
|
|
baton->symbols[baton->symbols_count].name =
|
|
string_table + nlist.n_un.n_strx;
|
|
baton->symbols_count++;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < exported_syms_count; i++) {
|
|
struct nlist_64 nlist;
|
|
memset(&nlist, 0, sizeof(struct nlist_64));
|
|
if (is_64bit) {
|
|
memcpy(&nlist, exported_syms + (i * nlist_size),
|
|
sizeof(struct nlist_64));
|
|
} else {
|
|
struct nlist nlist_32;
|
|
memcpy(&nlist_32, exported_syms + (i * nlist_size),
|
|
sizeof(struct nlist));
|
|
nlist.n_un.n_strx = nlist_32.n_un.n_strx;
|
|
nlist.n_type = nlist_32.n_type;
|
|
nlist.n_sect = nlist_32.n_sect;
|
|
nlist.n_desc = nlist_32.n_desc;
|
|
nlist.n_value = nlist_32.n_value;
|
|
}
|
|
if ((nlist.n_type & N_STAB) == 0 &&
|
|
((nlist.n_type & N_EXT) == 1 ||
|
|
((nlist.n_type & N_TYPE) == N_TYPE && nlist.n_sect != NO_SECT)) &&
|
|
nlist.n_value != 0 && nlist.n_value != baton->text_segment_vmaddr) {
|
|
baton->symbols[baton->symbols_count].file_address = nlist.n_value;
|
|
if (baton->cputype == CPU_TYPE_ARM)
|
|
baton->symbols[baton->symbols_count].file_address =
|
|
baton->symbols[baton->symbols_count].file_address & ~1;
|
|
baton->symbols[baton->symbols_count].name =
|
|
string_table + nlist.n_un.n_strx;
|
|
baton->symbols_count++;
|
|
}
|
|
}
|
|
|
|
qsort(baton->symbols, baton->symbols_count, sizeof(struct symbol),
|
|
symbol_compare);
|
|
}
|
|
|
|
if (*lc_cmd == LC_FUNCTION_STARTS) {
|
|
struct linkedit_data_command function_starts_cmd;
|
|
memcpy(&function_starts_cmd, offset,
|
|
sizeof(struct linkedit_data_command));
|
|
|
|
uint8_t *funcstarts_offset =
|
|
baton->mach_header_start + function_starts_cmd.dataoff;
|
|
uint8_t *function_end = funcstarts_offset + function_starts_cmd.datasize;
|
|
int count = 0;
|
|
|
|
while (funcstarts_offset < function_end) {
|
|
if (read_leb128(&funcstarts_offset) != 0) {
|
|
count++;
|
|
}
|
|
}
|
|
|
|
baton->function_start_addresses =
|
|
(uint64_t *)malloc(sizeof(uint64_t) * count);
|
|
baton->function_start_addresses_count = count;
|
|
|
|
funcstarts_offset =
|
|
baton->mach_header_start + function_starts_cmd.dataoff;
|
|
uint64_t current_pc = baton->text_segment_vmaddr;
|
|
int i = 0;
|
|
while (funcstarts_offset < function_end) {
|
|
uint64_t func_start = read_leb128(&funcstarts_offset);
|
|
if (func_start != 0) {
|
|
current_pc += func_start;
|
|
baton->function_start_addresses[i++] = current_pc;
|
|
}
|
|
}
|
|
}
|
|
|
|
offset = start_of_this_load_cmd + *lc_cmdsize;
|
|
cur_cmd++;
|
|
}
|
|
|
|
// Augment the symbol table with the function starts table -- adding symbol
|
|
// entries
|
|
// for functions that were stripped.
|
|
|
|
int unnamed_functions_to_add = 0;
|
|
for (int i = 0; i < baton->function_start_addresses_count; i++) {
|
|
struct symbol search_key;
|
|
search_key.file_address = baton->function_start_addresses[i];
|
|
if (baton->cputype == CPU_TYPE_ARM)
|
|
search_key.file_address = search_key.file_address & ~1;
|
|
struct symbol *sym =
|
|
bsearch(&search_key, baton->symbols, baton->symbols_count,
|
|
sizeof(struct symbol), symbol_compare);
|
|
if (sym == NULL)
|
|
unnamed_functions_to_add++;
|
|
}
|
|
|
|
baton->symbols = (struct symbol *)realloc(
|
|
baton->symbols, sizeof(struct symbol) *
|
|
(baton->symbols_count + unnamed_functions_to_add));
|
|
|
|
int current_unnamed_symbol = 1;
|
|
int number_symbols_added = 0;
|
|
for (int i = 0; i < baton->function_start_addresses_count; i++) {
|
|
struct symbol search_key;
|
|
search_key.file_address = baton->function_start_addresses[i];
|
|
if (baton->cputype == CPU_TYPE_ARM)
|
|
search_key.file_address = search_key.file_address & ~1;
|
|
struct symbol *sym =
|
|
bsearch(&search_key, baton->symbols, baton->symbols_count,
|
|
sizeof(struct symbol), symbol_compare);
|
|
if (sym == NULL) {
|
|
char *name;
|
|
asprintf(&name, "unnamed function #%d", current_unnamed_symbol++);
|
|
baton->symbols[baton->symbols_count + number_symbols_added].file_address =
|
|
baton->function_start_addresses[i];
|
|
baton->symbols[baton->symbols_count + number_symbols_added].name = name;
|
|
number_symbols_added++;
|
|
}
|
|
}
|
|
baton->symbols_count += number_symbols_added;
|
|
qsort(baton->symbols, baton->symbols_count, sizeof(struct symbol),
|
|
symbol_compare);
|
|
|
|
// printf ("function start addresses\n");
|
|
// for (int i = 0; i < baton->function_start_addresses_count; i++)
|
|
// {
|
|
// printf ("0x%012llx\n", baton->function_start_addresses[i]);
|
|
// }
|
|
|
|
// printf ("symbol table names & addresses\n");
|
|
// for (int i = 0; i < baton->symbols_count; i++)
|
|
// {
|
|
// printf ("0x%012llx %s\n", baton->symbols[i].file_address,
|
|
// baton->symbols[i].name);
|
|
// }
|
|
}
|
|
|
|
void print_encoding_x86_64(struct baton baton, uint8_t *function_start,
|
|
uint32_t encoding) {
|
|
int mode = encoding & UNWIND_X86_64_MODE_MASK;
|
|
switch (mode) {
|
|
case UNWIND_X86_64_MODE_RBP_FRAME: {
|
|
printf("frame func: CFA is rbp+%d ", 16);
|
|
printf(" rip=[CFA-8] rbp=[CFA-16]");
|
|
uint32_t saved_registers_offset =
|
|
EXTRACT_BITS(encoding, UNWIND_X86_64_RBP_FRAME_OFFSET);
|
|
|
|
uint32_t saved_registers_locations =
|
|
EXTRACT_BITS(encoding, UNWIND_X86_64_RBP_FRAME_REGISTERS);
|
|
|
|
saved_registers_offset += 2;
|
|
|
|
for (int i = 0; i < 5; i++) {
|
|
switch (saved_registers_locations & 0x7) {
|
|
case UNWIND_X86_64_REG_NONE:
|
|
break;
|
|
case UNWIND_X86_64_REG_RBX:
|
|
printf(" rbx=[CFA-%d]", saved_registers_offset * 8);
|
|
break;
|
|
case UNWIND_X86_64_REG_R12:
|
|
printf(" r12=[CFA-%d]", saved_registers_offset * 8);
|
|
break;
|
|
case UNWIND_X86_64_REG_R13:
|
|
printf(" r13=[CFA-%d]", saved_registers_offset * 8);
|
|
break;
|
|
case UNWIND_X86_64_REG_R14:
|
|
printf(" r14=[CFA-%d]", saved_registers_offset * 8);
|
|
break;
|
|
case UNWIND_X86_64_REG_R15:
|
|
printf(" r15=[CFA-%d]", saved_registers_offset * 8);
|
|
break;
|
|
}
|
|
saved_registers_offset--;
|
|
saved_registers_locations >>= 3;
|
|
}
|
|
} break;
|
|
|
|
case UNWIND_X86_64_MODE_STACK_IND:
|
|
case UNWIND_X86_64_MODE_STACK_IMMD: {
|
|
uint32_t stack_size =
|
|
EXTRACT_BITS(encoding, UNWIND_X86_64_FRAMELESS_STACK_SIZE);
|
|
uint32_t register_count =
|
|
EXTRACT_BITS(encoding, UNWIND_X86_64_FRAMELESS_STACK_REG_COUNT);
|
|
uint32_t permutation =
|
|
EXTRACT_BITS(encoding, UNWIND_X86_64_FRAMELESS_STACK_REG_PERMUTATION);
|
|
|
|
if (mode == UNWIND_X86_64_MODE_STACK_IND && function_start) {
|
|
uint32_t stack_adjust =
|
|
EXTRACT_BITS(encoding, UNWIND_X86_64_FRAMELESS_STACK_ADJUST);
|
|
|
|
// offset into the function instructions; 0 == beginning of first
|
|
// instruction
|
|
uint32_t offset_to_subl_insn =
|
|
EXTRACT_BITS(encoding, UNWIND_X86_64_FRAMELESS_STACK_SIZE);
|
|
|
|
stack_size = *((uint32_t *)(function_start + offset_to_subl_insn));
|
|
|
|
stack_size += stack_adjust * 8;
|
|
|
|
printf("large stack ");
|
|
}
|
|
|
|
if (mode == UNWIND_X86_64_MODE_STACK_IND) {
|
|
printf("frameless function: stack size %d, register count %d ",
|
|
stack_size * 8, register_count);
|
|
} else {
|
|
printf("frameless function: stack size %d, register count %d ",
|
|
stack_size, register_count);
|
|
}
|
|
|
|
if (register_count == 0) {
|
|
printf(" no registers saved");
|
|
} else {
|
|
|
|
// We need to include (up to) 6 registers in 10 bits.
|
|
// That would be 18 bits if we just used 3 bits per reg to indicate
|
|
// the order they're saved on the stack.
|
|
//
|
|
// This is done with Lehmer code permutation, e.g. see
|
|
// http://stackoverflow.com/questions/1506078/fast-permutation-number-permutation-mapping-algorithms
|
|
int permunreg[6];
|
|
|
|
// This decodes the variable-base number in the 10 bits
|
|
// and gives us the Lehmer code sequence which can then
|
|
// be decoded.
|
|
|
|
switch (register_count) {
|
|
case 6:
|
|
permunreg[0] = permutation / 120; // 120 == 5!
|
|
permutation -= (permunreg[0] * 120);
|
|
permunreg[1] = permutation / 24; // 24 == 4!
|
|
permutation -= (permunreg[1] * 24);
|
|
permunreg[2] = permutation / 6; // 6 == 3!
|
|
permutation -= (permunreg[2] * 6);
|
|
permunreg[3] = permutation / 2; // 2 == 2!
|
|
permutation -= (permunreg[3] * 2);
|
|
permunreg[4] = permutation; // 1 == 1!
|
|
permunreg[5] = 0;
|
|
break;
|
|
case 5:
|
|
permunreg[0] = permutation / 120;
|
|
permutation -= (permunreg[0] * 120);
|
|
permunreg[1] = permutation / 24;
|
|
permutation -= (permunreg[1] * 24);
|
|
permunreg[2] = permutation / 6;
|
|
permutation -= (permunreg[2] * 6);
|
|
permunreg[3] = permutation / 2;
|
|
permutation -= (permunreg[3] * 2);
|
|
permunreg[4] = permutation;
|
|
break;
|
|
case 4:
|
|
permunreg[0] = permutation / 60;
|
|
permutation -= (permunreg[0] * 60);
|
|
permunreg[1] = permutation / 12;
|
|
permutation -= (permunreg[1] * 12);
|
|
permunreg[2] = permutation / 3;
|
|
permutation -= (permunreg[2] * 3);
|
|
permunreg[3] = permutation;
|
|
break;
|
|
case 3:
|
|
permunreg[0] = permutation / 20;
|
|
permutation -= (permunreg[0] * 20);
|
|
permunreg[1] = permutation / 4;
|
|
permutation -= (permunreg[1] * 4);
|
|
permunreg[2] = permutation;
|
|
break;
|
|
case 2:
|
|
permunreg[0] = permutation / 5;
|
|
permutation -= (permunreg[0] * 5);
|
|
permunreg[1] = permutation;
|
|
break;
|
|
case 1:
|
|
permunreg[0] = permutation;
|
|
break;
|
|
}
|
|
|
|
// Decode the Lehmer code for this permutation of
|
|
// the registers v. http://en.wikipedia.org/wiki/Lehmer_code
|
|
|
|
int registers[6];
|
|
bool used[7] = {false, false, false, false, false, false, false};
|
|
for (int i = 0; i < register_count; i++) {
|
|
int renum = 0;
|
|
for (int j = 1; j < 7; j++) {
|
|
if (used[j] == false) {
|
|
if (renum == permunreg[i]) {
|
|
registers[i] = j;
|
|
used[j] = true;
|
|
break;
|
|
}
|
|
renum++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mode == UNWIND_X86_64_MODE_STACK_IND) {
|
|
printf(" CFA is rsp+%d ", stack_size);
|
|
} else {
|
|
printf(" CFA is rsp+%d ", stack_size * 8);
|
|
}
|
|
|
|
uint32_t saved_registers_offset = 1;
|
|
printf(" rip=[CFA-%d]", saved_registers_offset * 8);
|
|
saved_registers_offset++;
|
|
|
|
for (int i = (sizeof(registers) / sizeof(int)) - 1; i >= 0; i--) {
|
|
switch (registers[i]) {
|
|
case UNWIND_X86_64_REG_NONE:
|
|
break;
|
|
case UNWIND_X86_64_REG_RBX:
|
|
printf(" rbx=[CFA-%d]", saved_registers_offset * 8);
|
|
saved_registers_offset++;
|
|
break;
|
|
case UNWIND_X86_64_REG_R12:
|
|
printf(" r12=[CFA-%d]", saved_registers_offset * 8);
|
|
saved_registers_offset++;
|
|
break;
|
|
case UNWIND_X86_64_REG_R13:
|
|
printf(" r13=[CFA-%d]", saved_registers_offset * 8);
|
|
saved_registers_offset++;
|
|
break;
|
|
case UNWIND_X86_64_REG_R14:
|
|
printf(" r14=[CFA-%d]", saved_registers_offset * 8);
|
|
saved_registers_offset++;
|
|
break;
|
|
case UNWIND_X86_64_REG_R15:
|
|
printf(" r15=[CFA-%d]", saved_registers_offset * 8);
|
|
saved_registers_offset++;
|
|
break;
|
|
case UNWIND_X86_64_REG_RBP:
|
|
printf(" rbp=[CFA-%d]", saved_registers_offset * 8);
|
|
saved_registers_offset++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
} break;
|
|
|
|
case UNWIND_X86_64_MODE_DWARF: {
|
|
uint32_t dwarf_offset = encoding & UNWIND_X86_DWARF_SECTION_OFFSET;
|
|
printf(
|
|
"DWARF unwind instructions: FDE at offset %d (file address 0x%" PRIx64
|
|
")",
|
|
dwarf_offset, dwarf_offset + baton.eh_section_file_address);
|
|
} break;
|
|
|
|
case 0: {
|
|
printf(" no unwind information");
|
|
} break;
|
|
}
|
|
}
|
|
|
|
void print_encoding_i386(struct baton baton, uint8_t *function_start,
|
|
uint32_t encoding) {
|
|
int mode = encoding & UNWIND_X86_MODE_MASK;
|
|
switch (mode) {
|
|
case UNWIND_X86_MODE_EBP_FRAME: {
|
|
printf("frame func: CFA is ebp+%d ", 8);
|
|
printf(" eip=[CFA-4] ebp=[CFA-8]");
|
|
uint32_t saved_registers_offset =
|
|
EXTRACT_BITS(encoding, UNWIND_X86_EBP_FRAME_OFFSET);
|
|
|
|
uint32_t saved_registers_locations =
|
|
EXTRACT_BITS(encoding, UNWIND_X86_EBP_FRAME_REGISTERS);
|
|
|
|
saved_registers_offset += 2;
|
|
|
|
for (int i = 0; i < 5; i++) {
|
|
switch (saved_registers_locations & 0x7) {
|
|
case UNWIND_X86_REG_NONE:
|
|
break;
|
|
case UNWIND_X86_REG_EBX:
|
|
printf(" ebx=[CFA-%d]", saved_registers_offset * 4);
|
|
break;
|
|
case UNWIND_X86_REG_ECX:
|
|
printf(" ecx=[CFA-%d]", saved_registers_offset * 4);
|
|
break;
|
|
case UNWIND_X86_REG_EDX:
|
|
printf(" edx=[CFA-%d]", saved_registers_offset * 4);
|
|
break;
|
|
case UNWIND_X86_REG_EDI:
|
|
printf(" edi=[CFA-%d]", saved_registers_offset * 4);
|
|
break;
|
|
case UNWIND_X86_REG_ESI:
|
|
printf(" esi=[CFA-%d]", saved_registers_offset * 4);
|
|
break;
|
|
}
|
|
saved_registers_offset--;
|
|
saved_registers_locations >>= 3;
|
|
}
|
|
} break;
|
|
|
|
case UNWIND_X86_MODE_STACK_IND:
|
|
case UNWIND_X86_MODE_STACK_IMMD: {
|
|
uint32_t stack_size =
|
|
EXTRACT_BITS(encoding, UNWIND_X86_FRAMELESS_STACK_SIZE);
|
|
uint32_t register_count =
|
|
EXTRACT_BITS(encoding, UNWIND_X86_FRAMELESS_STACK_REG_COUNT);
|
|
uint32_t permutation =
|
|
EXTRACT_BITS(encoding, UNWIND_X86_FRAMELESS_STACK_REG_PERMUTATION);
|
|
|
|
if (mode == UNWIND_X86_MODE_STACK_IND && function_start) {
|
|
uint32_t stack_adjust =
|
|
EXTRACT_BITS(encoding, UNWIND_X86_FRAMELESS_STACK_ADJUST);
|
|
|
|
// offset into the function instructions; 0 == beginning of first
|
|
// instruction
|
|
uint32_t offset_to_subl_insn =
|
|
EXTRACT_BITS(encoding, UNWIND_X86_FRAMELESS_STACK_SIZE);
|
|
|
|
stack_size = *((uint32_t *)(function_start + offset_to_subl_insn));
|
|
|
|
stack_size += stack_adjust * 4;
|
|
|
|
printf("large stack ");
|
|
}
|
|
|
|
if (mode == UNWIND_X86_MODE_STACK_IND) {
|
|
printf("frameless function: stack size %d, register count %d ",
|
|
stack_size, register_count);
|
|
} else {
|
|
printf("frameless function: stack size %d, register count %d ",
|
|
stack_size * 4, register_count);
|
|
}
|
|
|
|
if (register_count == 0) {
|
|
printf(" no registers saved");
|
|
} else {
|
|
|
|
// We need to include (up to) 6 registers in 10 bits.
|
|
// That would be 18 bits if we just used 3 bits per reg to indicate
|
|
// the order they're saved on the stack.
|
|
//
|
|
// This is done with Lehmer code permutation, e.g. see
|
|
// http://stackoverflow.com/questions/1506078/fast-permutation-number-permutation-mapping-algorithms
|
|
int permunreg[6];
|
|
|
|
// This decodes the variable-base number in the 10 bits
|
|
// and gives us the Lehmer code sequence which can then
|
|
// be decoded.
|
|
|
|
switch (register_count) {
|
|
case 6:
|
|
permunreg[0] = permutation / 120; // 120 == 5!
|
|
permutation -= (permunreg[0] * 120);
|
|
permunreg[1] = permutation / 24; // 24 == 4!
|
|
permutation -= (permunreg[1] * 24);
|
|
permunreg[2] = permutation / 6; // 6 == 3!
|
|
permutation -= (permunreg[2] * 6);
|
|
permunreg[3] = permutation / 2; // 2 == 2!
|
|
permutation -= (permunreg[3] * 2);
|
|
permunreg[4] = permutation; // 1 == 1!
|
|
permunreg[5] = 0;
|
|
break;
|
|
case 5:
|
|
permunreg[0] = permutation / 120;
|
|
permutation -= (permunreg[0] * 120);
|
|
permunreg[1] = permutation / 24;
|
|
permutation -= (permunreg[1] * 24);
|
|
permunreg[2] = permutation / 6;
|
|
permutation -= (permunreg[2] * 6);
|
|
permunreg[3] = permutation / 2;
|
|
permutation -= (permunreg[3] * 2);
|
|
permunreg[4] = permutation;
|
|
break;
|
|
case 4:
|
|
permunreg[0] = permutation / 60;
|
|
permutation -= (permunreg[0] * 60);
|
|
permunreg[1] = permutation / 12;
|
|
permutation -= (permunreg[1] * 12);
|
|
permunreg[2] = permutation / 3;
|
|
permutation -= (permunreg[2] * 3);
|
|
permunreg[3] = permutation;
|
|
break;
|
|
case 3:
|
|
permunreg[0] = permutation / 20;
|
|
permutation -= (permunreg[0] * 20);
|
|
permunreg[1] = permutation / 4;
|
|
permutation -= (permunreg[1] * 4);
|
|
permunreg[2] = permutation;
|
|
break;
|
|
case 2:
|
|
permunreg[0] = permutation / 5;
|
|
permutation -= (permunreg[0] * 5);
|
|
permunreg[1] = permutation;
|
|
break;
|
|
case 1:
|
|
permunreg[0] = permutation;
|
|
break;
|
|
}
|
|
|
|
// Decode the Lehmer code for this permutation of
|
|
// the registers v. http://en.wikipedia.org/wiki/Lehmer_code
|
|
|
|
int registers[6];
|
|
bool used[7] = {false, false, false, false, false, false, false};
|
|
for (int i = 0; i < register_count; i++) {
|
|
int renum = 0;
|
|
for (int j = 1; j < 7; j++) {
|
|
if (used[j] == false) {
|
|
if (renum == permunreg[i]) {
|
|
registers[i] = j;
|
|
used[j] = true;
|
|
break;
|
|
}
|
|
renum++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mode == UNWIND_X86_MODE_STACK_IND) {
|
|
printf(" CFA is esp+%d ", stack_size);
|
|
} else {
|
|
printf(" CFA is esp+%d ", stack_size * 4);
|
|
}
|
|
|
|
uint32_t saved_registers_offset = 1;
|
|
printf(" eip=[CFA-%d]", saved_registers_offset * 4);
|
|
saved_registers_offset++;
|
|
|
|
for (int i = (sizeof(registers) / sizeof(int)) - 1; i >= 0; i--) {
|
|
switch (registers[i]) {
|
|
case UNWIND_X86_REG_NONE:
|
|
break;
|
|
case UNWIND_X86_REG_EBX:
|
|
printf(" ebx=[CFA-%d]", saved_registers_offset * 4);
|
|
saved_registers_offset++;
|
|
break;
|
|
case UNWIND_X86_REG_ECX:
|
|
printf(" ecx=[CFA-%d]", saved_registers_offset * 4);
|
|
saved_registers_offset++;
|
|
break;
|
|
case UNWIND_X86_REG_EDX:
|
|
printf(" edx=[CFA-%d]", saved_registers_offset * 4);
|
|
saved_registers_offset++;
|
|
break;
|
|
case UNWIND_X86_REG_EDI:
|
|
printf(" edi=[CFA-%d]", saved_registers_offset * 4);
|
|
saved_registers_offset++;
|
|
break;
|
|
case UNWIND_X86_REG_ESI:
|
|
printf(" esi=[CFA-%d]", saved_registers_offset * 4);
|
|
saved_registers_offset++;
|
|
break;
|
|
case UNWIND_X86_REG_EBP:
|
|
printf(" ebp=[CFA-%d]", saved_registers_offset * 4);
|
|
saved_registers_offset++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
} break;
|
|
|
|
case UNWIND_X86_MODE_DWARF: {
|
|
uint32_t dwarf_offset = encoding & UNWIND_X86_DWARF_SECTION_OFFSET;
|
|
printf(
|
|
"DWARF unwind instructions: FDE at offset %d (file address 0x%" PRIx64
|
|
")",
|
|
dwarf_offset, dwarf_offset + baton.eh_section_file_address);
|
|
} break;
|
|
|
|
case 0: {
|
|
printf(" no unwind information");
|
|
} break;
|
|
}
|
|
}
|
|
|
|
void print_encoding_arm64(struct baton baton, uint8_t *function_start,
|
|
uint32_t encoding) {
|
|
const int wordsize = 8;
|
|
int mode = encoding & UNWIND_ARM64_MODE_MASK;
|
|
switch (mode) {
|
|
case UNWIND_ARM64_MODE_FRAME: {
|
|
printf("frame func: CFA is fp+%d ", 16);
|
|
printf(" pc=[CFA-8] fp=[CFA-16]");
|
|
int reg_pairs_saved_count = 1;
|
|
uint32_t saved_register_bits = encoding & 0xfff;
|
|
if (saved_register_bits & UNWIND_ARM64_FRAME_X19_X20_PAIR) {
|
|
int cfa_offset = reg_pairs_saved_count * -2 * wordsize;
|
|
cfa_offset -= wordsize;
|
|
printf(" x19=[CFA%d]", cfa_offset);
|
|
cfa_offset -= wordsize;
|
|
printf(" x20=[CFA%d]", cfa_offset);
|
|
reg_pairs_saved_count++;
|
|
}
|
|
if (saved_register_bits & UNWIND_ARM64_FRAME_X21_X22_PAIR) {
|
|
int cfa_offset = reg_pairs_saved_count * -2 * wordsize;
|
|
cfa_offset -= wordsize;
|
|
printf(" x21=[CFA%d]", cfa_offset);
|
|
cfa_offset -= wordsize;
|
|
printf(" x22=[CFA%d]", cfa_offset);
|
|
reg_pairs_saved_count++;
|
|
}
|
|
if (saved_register_bits & UNWIND_ARM64_FRAME_X23_X24_PAIR) {
|
|
int cfa_offset = reg_pairs_saved_count * -2 * wordsize;
|
|
cfa_offset -= wordsize;
|
|
printf(" x23=[CFA%d]", cfa_offset);
|
|
cfa_offset -= wordsize;
|
|
printf(" x24=[CFA%d]", cfa_offset);
|
|
reg_pairs_saved_count++;
|
|
}
|
|
if (saved_register_bits & UNWIND_ARM64_FRAME_X25_X26_PAIR) {
|
|
int cfa_offset = reg_pairs_saved_count * -2 * wordsize;
|
|
cfa_offset -= wordsize;
|
|
printf(" x25=[CFA%d]", cfa_offset);
|
|
cfa_offset -= wordsize;
|
|
printf(" x26=[CFA%d]", cfa_offset);
|
|
reg_pairs_saved_count++;
|
|
}
|
|
if (saved_register_bits & UNWIND_ARM64_FRAME_X27_X28_PAIR) {
|
|
int cfa_offset = reg_pairs_saved_count * -2 * wordsize;
|
|
cfa_offset -= wordsize;
|
|
printf(" x27=[CFA%d]", cfa_offset);
|
|
cfa_offset -= wordsize;
|
|
printf(" x28=[CFA%d]", cfa_offset);
|
|
reg_pairs_saved_count++;
|
|
}
|
|
if (saved_register_bits & UNWIND_ARM64_FRAME_D8_D9_PAIR) {
|
|
int cfa_offset = reg_pairs_saved_count * -2 * wordsize;
|
|
cfa_offset -= wordsize;
|
|
printf(" d8=[CFA%d]", cfa_offset);
|
|
cfa_offset -= wordsize;
|
|
printf(" d9=[CFA%d]", cfa_offset);
|
|
reg_pairs_saved_count++;
|
|
}
|
|
if (saved_register_bits & UNWIND_ARM64_FRAME_D10_D11_PAIR) {
|
|
int cfa_offset = reg_pairs_saved_count * -2 * wordsize;
|
|
cfa_offset -= wordsize;
|
|
printf(" d10=[CFA%d]", cfa_offset);
|
|
cfa_offset -= wordsize;
|
|
printf(" d11=[CFA%d]", cfa_offset);
|
|
reg_pairs_saved_count++;
|
|
}
|
|
if (saved_register_bits & UNWIND_ARM64_FRAME_D12_D13_PAIR) {
|
|
int cfa_offset = reg_pairs_saved_count * -2 * wordsize;
|
|
cfa_offset -= wordsize;
|
|
printf(" d12=[CFA%d]", cfa_offset);
|
|
cfa_offset -= wordsize;
|
|
printf(" d13=[CFA%d]", cfa_offset);
|
|
reg_pairs_saved_count++;
|
|
}
|
|
if (saved_register_bits & UNWIND_ARM64_FRAME_D14_D15_PAIR) {
|
|
int cfa_offset = reg_pairs_saved_count * -2 * wordsize;
|
|
cfa_offset -= wordsize;
|
|
printf(" d14=[CFA%d]", cfa_offset);
|
|
cfa_offset -= wordsize;
|
|
printf(" d15=[CFA%d]", cfa_offset);
|
|
reg_pairs_saved_count++;
|
|
}
|
|
|
|
} break;
|
|
|
|
case UNWIND_ARM64_MODE_FRAMELESS: {
|
|
uint32_t stack_size = encoding & UNWIND_ARM64_FRAMELESS_STACK_SIZE_MASK;
|
|
printf("frameless function: stack size %d ", stack_size * 16);
|
|
|
|
} break;
|
|
|
|
case UNWIND_ARM64_MODE_DWARF: {
|
|
uint32_t dwarf_offset = encoding & UNWIND_ARM64_DWARF_SECTION_OFFSET;
|
|
printf(
|
|
"DWARF unwind instructions: FDE at offset %d (file address 0x%" PRIx64
|
|
")",
|
|
dwarf_offset, dwarf_offset + baton.eh_section_file_address);
|
|
} break;
|
|
|
|
case 0: {
|
|
printf(" no unwind information");
|
|
} break;
|
|
}
|
|
}
|
|
|
|
void print_encoding_armv7(struct baton baton, uint8_t *function_start,
|
|
uint32_t encoding) {
|
|
const int wordsize = 4;
|
|
int mode = encoding & UNWIND_ARM_MODE_MASK;
|
|
switch (mode) {
|
|
case UNWIND_ARM_MODE_FRAME_D:
|
|
case UNWIND_ARM_MODE_FRAME: {
|
|
int stack_adjust =
|
|
EXTRACT_BITS(encoding, UNWIND_ARM_FRAME_STACK_ADJUST_MASK) * wordsize;
|
|
|
|
printf("frame func: CFA is fp+%d ", (2 * wordsize) + stack_adjust);
|
|
int cfa_offset = -stack_adjust;
|
|
|
|
cfa_offset -= wordsize;
|
|
printf(" pc=[CFA%d]", cfa_offset);
|
|
cfa_offset -= wordsize;
|
|
printf(" fp=[CFA%d]", cfa_offset);
|
|
|
|
uint32_t saved_register_bits = encoding & 0xff;
|
|
if (saved_register_bits & UNWIND_ARM_FRAME_FIRST_PUSH_R6) {
|
|
cfa_offset -= wordsize;
|
|
printf(" r6=[CFA%d]", cfa_offset);
|
|
}
|
|
if (saved_register_bits & UNWIND_ARM_FRAME_FIRST_PUSH_R5) {
|
|
cfa_offset -= wordsize;
|
|
printf(" r5=[CFA%d]", cfa_offset);
|
|
}
|
|
if (saved_register_bits & UNWIND_ARM_FRAME_FIRST_PUSH_R4) {
|
|
cfa_offset -= wordsize;
|
|
printf(" r4=[CFA%d]", cfa_offset);
|
|
}
|
|
if (saved_register_bits & UNWIND_ARM_FRAME_SECOND_PUSH_R12) {
|
|
cfa_offset -= wordsize;
|
|
printf(" r12=[CFA%d]", cfa_offset);
|
|
}
|
|
if (saved_register_bits & UNWIND_ARM_FRAME_SECOND_PUSH_R11) {
|
|
cfa_offset -= wordsize;
|
|
printf(" r11=[CFA%d]", cfa_offset);
|
|
}
|
|
if (saved_register_bits & UNWIND_ARM_FRAME_SECOND_PUSH_R10) {
|
|
cfa_offset -= wordsize;
|
|
printf(" r10=[CFA%d]", cfa_offset);
|
|
}
|
|
if (saved_register_bits & UNWIND_ARM_FRAME_SECOND_PUSH_R9) {
|
|
cfa_offset -= wordsize;
|
|
printf(" r9=[CFA%d]", cfa_offset);
|
|
}
|
|
if (saved_register_bits & UNWIND_ARM_FRAME_SECOND_PUSH_R8) {
|
|
cfa_offset -= wordsize;
|
|
printf(" r8=[CFA%d]", cfa_offset);
|
|
}
|
|
|
|
if (mode == UNWIND_ARM_MODE_FRAME_D) {
|
|
uint32_t d_reg_bits =
|
|
EXTRACT_BITS(encoding, UNWIND_ARM_FRAME_D_REG_COUNT_MASK);
|
|
switch (d_reg_bits) {
|
|
case 0:
|
|
// vpush {d8}
|
|
cfa_offset -= 8;
|
|
printf(" d8=[CFA%d]", cfa_offset);
|
|
break;
|
|
case 1:
|
|
// vpush {d10}
|
|
// vpush {d8}
|
|
cfa_offset -= 8;
|
|
printf(" d10=[CFA%d]", cfa_offset);
|
|
cfa_offset -= 8;
|
|
printf(" d8=[CFA%d]", cfa_offset);
|
|
break;
|
|
case 2:
|
|
// vpush {d12}
|
|
// vpush {d10}
|
|
// vpush {d8}
|
|
cfa_offset -= 8;
|
|
printf(" d12=[CFA%d]", cfa_offset);
|
|
cfa_offset -= 8;
|
|
printf(" d10=[CFA%d]", cfa_offset);
|
|
cfa_offset -= 8;
|
|
printf(" d8=[CFA%d]", cfa_offset);
|
|
break;
|
|
case 3:
|
|
// vpush {d14}
|
|
// vpush {d12}
|
|
// vpush {d10}
|
|
// vpush {d8}
|
|
cfa_offset -= 8;
|
|
printf(" d14=[CFA%d]", cfa_offset);
|
|
cfa_offset -= 8;
|
|
printf(" d12=[CFA%d]", cfa_offset);
|
|
cfa_offset -= 8;
|
|
printf(" d10=[CFA%d]", cfa_offset);
|
|
cfa_offset -= 8;
|
|
printf(" d8=[CFA%d]", cfa_offset);
|
|
break;
|
|
case 4:
|
|
// vpush {d14}
|
|
// vpush {d12}
|
|
// sp = (sp - 24) & (-16);
|
|
// vst {d8, d9, d10}
|
|
printf(" d14, d12, d10, d9, d8");
|
|
break;
|
|
case 5:
|
|
// vpush {d14}
|
|
// sp = (sp - 40) & (-16);
|
|
// vst {d8, d9, d10, d11}
|
|
// vst {d12}
|
|
printf(" d14, d11, d10, d9, d8, d12");
|
|
break;
|
|
case 6:
|
|
// sp = (sp - 56) & (-16);
|
|
// vst {d8, d9, d10, d11}
|
|
// vst {d12, d13, d14}
|
|
printf(" d11, d10, d9, d8, d14, d13, d12");
|
|
break;
|
|
case 7:
|
|
// sp = (sp - 64) & (-16);
|
|
// vst {d8, d9, d10, d11}
|
|
// vst {d12, d13, d14, d15}
|
|
printf(" d11, d10, d9, d8, d15, d14, d13, d12");
|
|
break;
|
|
}
|
|
}
|
|
} break;
|
|
|
|
case UNWIND_ARM_MODE_DWARF: {
|
|
uint32_t dwarf_offset = encoding & UNWIND_ARM_DWARF_SECTION_OFFSET;
|
|
printf(
|
|
"DWARF unwind instructions: FDE at offset %d (file address 0x%" PRIx64
|
|
")",
|
|
dwarf_offset, dwarf_offset + baton.eh_section_file_address);
|
|
} break;
|
|
|
|
case 0: {
|
|
printf(" no unwind information");
|
|
} break;
|
|
}
|
|
}
|
|
|
|
void print_encoding(struct baton baton, uint8_t *function_start,
|
|
uint32_t encoding) {
|
|
|
|
if (baton.cputype == CPU_TYPE_X86_64) {
|
|
print_encoding_x86_64(baton, function_start, encoding);
|
|
} else if (baton.cputype == CPU_TYPE_I386) {
|
|
print_encoding_i386(baton, function_start, encoding);
|
|
} else if (baton.cputype == CPU_TYPE_ARM64) {
|
|
print_encoding_arm64(baton, function_start, encoding);
|
|
} else if (baton.cputype == CPU_TYPE_ARM) {
|
|
print_encoding_armv7(baton, function_start, encoding);
|
|
} else {
|
|
printf(" -- unsupported encoding arch -- ");
|
|
}
|
|
}
|
|
|
|
void print_function_encoding(struct baton baton, uint32_t idx,
|
|
uint32_t encoding, uint32_t entry_encoding_index,
|
|
uint32_t entry_func_offset) {
|
|
|
|
char *entry_encoding_index_str = "";
|
|
if (entry_encoding_index != (uint32_t)-1) {
|
|
asprintf(&entry_encoding_index_str, ", encoding #%d", entry_encoding_index);
|
|
} else {
|
|
asprintf(&entry_encoding_index_str, "");
|
|
}
|
|
|
|
uint64_t file_address = baton.first_level_index_entry.functionOffset +
|
|
entry_func_offset + baton.text_segment_vmaddr;
|
|
|
|
if (baton.cputype == CPU_TYPE_ARM)
|
|
file_address = file_address & ~1;
|
|
|
|
printf(
|
|
" func [%d] offset %d (file addr 0x%" PRIx64 ")%s, encoding is 0x%x",
|
|
idx, entry_func_offset, file_address, entry_encoding_index_str, encoding);
|
|
|
|
struct symbol *symbol = NULL;
|
|
for (int i = 0; i < baton.symbols_count; i++) {
|
|
if (i == baton.symbols_count - 1 &&
|
|
baton.symbols[i].file_address <= file_address) {
|
|
symbol = &(baton.symbols[i]);
|
|
break;
|
|
} else {
|
|
if (baton.symbols[i].file_address <= file_address &&
|
|
baton.symbols[i + 1].file_address > file_address) {
|
|
symbol = &(baton.symbols[i]);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
printf("\n ");
|
|
if (symbol) {
|
|
int offset = file_address - symbol->file_address;
|
|
|
|
// FIXME this is a poor heuristic - if we're greater than 16 bytes past the
|
|
// start of the function, this is the unwind info for a stripped function.
|
|
// In reality the compact unwind entry may not line up exactly with the
|
|
// function bounds.
|
|
if (offset >= 0) {
|
|
printf("name: %s", symbol->name);
|
|
if (offset > 0) {
|
|
printf(" + %d", offset);
|
|
}
|
|
}
|
|
printf("\n ");
|
|
}
|
|
|
|
print_encoding(baton, baton.mach_header_start +
|
|
baton.first_level_index_entry.functionOffset +
|
|
baton.text_section_file_offset + entry_func_offset,
|
|
encoding);
|
|
|
|
bool has_lsda = encoding & UNWIND_HAS_LSDA;
|
|
|
|
if (has_lsda) {
|
|
uint32_t func_offset =
|
|
entry_func_offset + baton.first_level_index_entry.functionOffset;
|
|
|
|
int lsda_entry_number = -1;
|
|
|
|
uint32_t low = 0;
|
|
uint32_t high = (baton.lsda_array_end - baton.lsda_array_start) /
|
|
sizeof(struct unwind_info_section_header_lsda_index_entry);
|
|
|
|
while (low < high) {
|
|
uint32_t mid = (low + high) / 2;
|
|
|
|
uint8_t *mid_lsda_entry_addr =
|
|
(baton.lsda_array_start +
|
|
(mid * sizeof(struct unwind_info_section_header_lsda_index_entry)));
|
|
struct unwind_info_section_header_lsda_index_entry mid_lsda_entry;
|
|
memcpy(&mid_lsda_entry, mid_lsda_entry_addr,
|
|
sizeof(struct unwind_info_section_header_lsda_index_entry));
|
|
if (mid_lsda_entry.functionOffset == func_offset) {
|
|
lsda_entry_number =
|
|
(mid_lsda_entry_addr - baton.lsda_array_start) /
|
|
sizeof(struct unwind_info_section_header_lsda_index_entry);
|
|
break;
|
|
} else if (mid_lsda_entry.functionOffset < func_offset) {
|
|
low = mid + 1;
|
|
} else {
|
|
high = mid;
|
|
}
|
|
}
|
|
|
|
if (lsda_entry_number != -1) {
|
|
printf(", LSDA entry #%d", lsda_entry_number);
|
|
} else {
|
|
printf(", LSDA entry not found");
|
|
}
|
|
}
|
|
|
|
uint32_t pers_idx = EXTRACT_BITS(encoding, UNWIND_PERSONALITY_MASK);
|
|
if (pers_idx != 0) {
|
|
pers_idx--; // Change 1-based to 0-based index
|
|
printf(", personality entry #%d", pers_idx);
|
|
}
|
|
|
|
printf("\n");
|
|
}
|
|
|
|
void print_second_level_index_regular(struct baton baton) {
|
|
uint8_t *page_entries =
|
|
baton.compact_unwind_start +
|
|
baton.first_level_index_entry.secondLevelPagesSectionOffset +
|
|
baton.regular_second_level_page_header.entryPageOffset;
|
|
uint32_t entries_count = baton.regular_second_level_page_header.entryCount;
|
|
|
|
uint8_t *offset = page_entries;
|
|
|
|
uint32_t idx = 0;
|
|
while (idx < entries_count) {
|
|
uint32_t func_offset = *((uint32_t *)(offset));
|
|
uint32_t encoding = *((uint32_t *)(offset + 4));
|
|
|
|
// UNWIND_SECOND_LEVEL_REGULAR entries have a funcOffset which includes the
|
|
// functionOffset from the containing index table already.
|
|
// UNWIND_SECOND_LEVEL_COMPRESSED
|
|
// entries only have the offset from the containing index table
|
|
// functionOffset.
|
|
// So strip off the containing index table functionOffset value here so they
|
|
// can
|
|
// be treated the same at the lower layers.
|
|
|
|
print_function_encoding(baton, idx, encoding, (uint32_t)-1,
|
|
func_offset -
|
|
baton.first_level_index_entry.functionOffset);
|
|
idx++;
|
|
offset += 8;
|
|
}
|
|
}
|
|
|
|
void print_second_level_index_compressed(struct baton baton) {
|
|
uint8_t *this_index =
|
|
baton.compact_unwind_start +
|
|
baton.first_level_index_entry.secondLevelPagesSectionOffset;
|
|
uint8_t *start_of_entries =
|
|
this_index + baton.compressed_second_level_page_header.entryPageOffset;
|
|
uint8_t *offset = start_of_entries;
|
|
for (uint16_t idx = 0;
|
|
idx < baton.compressed_second_level_page_header.entryCount; idx++) {
|
|
uint32_t entry = *((uint32_t *)offset);
|
|
offset += 4;
|
|
uint32_t encoding;
|
|
|
|
uint32_t entry_encoding_index =
|
|
UNWIND_INFO_COMPRESSED_ENTRY_ENCODING_INDEX(entry);
|
|
uint32_t entry_func_offset =
|
|
UNWIND_INFO_COMPRESSED_ENTRY_FUNC_OFFSET(entry);
|
|
|
|
if (entry_encoding_index < baton.unwind_header.commonEncodingsArrayCount) {
|
|
// encoding is in common table in section header
|
|
encoding =
|
|
*((uint32_t *)(baton.compact_unwind_start +
|
|
baton.unwind_header.commonEncodingsArraySectionOffset +
|
|
(entry_encoding_index * sizeof(uint32_t))));
|
|
} else {
|
|
// encoding is in page specific table
|
|
uint32_t page_encoding_index =
|
|
entry_encoding_index - baton.unwind_header.commonEncodingsArrayCount;
|
|
encoding = *((uint32_t *)(this_index +
|
|
baton.compressed_second_level_page_header
|
|
.encodingsPageOffset +
|
|
(page_encoding_index * sizeof(uint32_t))));
|
|
}
|
|
|
|
print_function_encoding(baton, idx, encoding, entry_encoding_index,
|
|
entry_func_offset);
|
|
}
|
|
}
|
|
|
|
void print_second_level_index(struct baton baton) {
|
|
uint8_t *index_start =
|
|
baton.compact_unwind_start +
|
|
baton.first_level_index_entry.secondLevelPagesSectionOffset;
|
|
|
|
if ((*(uint32_t *)index_start) == UNWIND_SECOND_LEVEL_REGULAR) {
|
|
struct unwind_info_regular_second_level_page_header header;
|
|
memcpy(&header, index_start,
|
|
sizeof(struct unwind_info_regular_second_level_page_header));
|
|
printf(
|
|
" UNWIND_SECOND_LEVEL_REGULAR #%d entryPageOffset %d, entryCount %d\n",
|
|
baton.current_index_table_number, header.entryPageOffset,
|
|
header.entryCount);
|
|
baton.regular_second_level_page_header = header;
|
|
print_second_level_index_regular(baton);
|
|
}
|
|
|
|
if ((*(uint32_t *)index_start) == UNWIND_SECOND_LEVEL_COMPRESSED) {
|
|
struct unwind_info_compressed_second_level_page_header header;
|
|
memcpy(&header, index_start,
|
|
sizeof(struct unwind_info_compressed_second_level_page_header));
|
|
printf(" UNWIND_SECOND_LEVEL_COMPRESSED #%d entryPageOffset %d, "
|
|
"entryCount %d, encodingsPageOffset %d, encodingsCount %d\n",
|
|
baton.current_index_table_number, header.entryPageOffset,
|
|
header.entryCount, header.encodingsPageOffset,
|
|
header.encodingsCount);
|
|
baton.compressed_second_level_page_header = header;
|
|
print_second_level_index_compressed(baton);
|
|
}
|
|
}
|
|
|
|
void print_index_sections(struct baton baton) {
|
|
uint8_t *index_section_offset =
|
|
baton.compact_unwind_start + baton.unwind_header.indexSectionOffset;
|
|
uint32_t index_count = baton.unwind_header.indexCount;
|
|
|
|
uint32_t cur_idx = 0;
|
|
|
|
uint8_t *offset = index_section_offset;
|
|
while (cur_idx < index_count) {
|
|
baton.current_index_table_number = cur_idx;
|
|
struct unwind_info_section_header_index_entry index_entry;
|
|
memcpy(&index_entry, offset,
|
|
sizeof(struct unwind_info_section_header_index_entry));
|
|
printf("index section #%d: functionOffset %d, "
|
|
"secondLevelPagesSectionOffset %d, lsdaIndexArraySectionOffset %d\n",
|
|
cur_idx, index_entry.functionOffset,
|
|
index_entry.secondLevelPagesSectionOffset,
|
|
index_entry.lsdaIndexArraySectionOffset);
|
|
|
|
// secondLevelPagesSectionOffset == 0 means this is a sentinel entry
|
|
if (index_entry.secondLevelPagesSectionOffset != 0) {
|
|
struct unwind_info_section_header_index_entry next_index_entry;
|
|
memcpy(&next_index_entry,
|
|
offset + sizeof(struct unwind_info_section_header_index_entry),
|
|
sizeof(struct unwind_info_section_header_index_entry));
|
|
|
|
baton.lsda_array_start =
|
|
baton.compact_unwind_start + index_entry.lsdaIndexArraySectionOffset;
|
|
baton.lsda_array_end = baton.compact_unwind_start +
|
|
next_index_entry.lsdaIndexArraySectionOffset;
|
|
|
|
uint8_t *lsda_entry_offset = baton.lsda_array_start;
|
|
uint32_t lsda_count = 0;
|
|
while (lsda_entry_offset < baton.lsda_array_end) {
|
|
struct unwind_info_section_header_lsda_index_entry lsda_entry;
|
|
memcpy(&lsda_entry, lsda_entry_offset,
|
|
sizeof(struct unwind_info_section_header_lsda_index_entry));
|
|
uint64_t function_file_address =
|
|
baton.first_level_index_entry.functionOffset +
|
|
lsda_entry.functionOffset + baton.text_segment_vmaddr;
|
|
uint64_t lsda_file_address =
|
|
lsda_entry.lsdaOffset + baton.text_segment_vmaddr;
|
|
printf(" LSDA [%d] functionOffset %d (%d) (file address 0x%" PRIx64
|
|
"), lsdaOffset %d (file address 0x%" PRIx64 ")\n",
|
|
lsda_count, lsda_entry.functionOffset,
|
|
lsda_entry.functionOffset - index_entry.functionOffset,
|
|
function_file_address, lsda_entry.lsdaOffset, lsda_file_address);
|
|
lsda_count++;
|
|
lsda_entry_offset +=
|
|
sizeof(struct unwind_info_section_header_lsda_index_entry);
|
|
}
|
|
|
|
printf("\n");
|
|
|
|
baton.first_level_index_entry = index_entry;
|
|
print_second_level_index(baton);
|
|
}
|
|
|
|
printf("\n");
|
|
|
|
cur_idx++;
|
|
offset += sizeof(struct unwind_info_section_header_index_entry);
|
|
}
|
|
}
|
|
|
|
int main(int argc, char **argv) {
|
|
struct stat st;
|
|
char *file = argv[0];
|
|
if (argc > 1)
|
|
file = argv[1];
|
|
int fd = open(file, O_RDONLY);
|
|
if (fd == -1) {
|
|
printf("Failed to open '%s'\n", file);
|
|
exit(1);
|
|
}
|
|
fstat(fd, &st);
|
|
uint8_t *file_mem =
|
|
(uint8_t *)mmap(0, st.st_size, PROT_READ, MAP_PRIVATE | MAP_FILE, fd, 0);
|
|
if (file_mem == MAP_FAILED) {
|
|
printf("Failed to mmap() '%s'\n", file);
|
|
}
|
|
|
|
FILE *f = fopen("a.out", "r");
|
|
|
|
struct baton baton;
|
|
baton.mach_header_start = file_mem;
|
|
baton.symbols = NULL;
|
|
baton.symbols_count = 0;
|
|
baton.function_start_addresses = NULL;
|
|
baton.function_start_addresses_count = 0;
|
|
|
|
scan_macho_load_commands(&baton);
|
|
|
|
if (baton.compact_unwind_start == NULL) {
|
|
printf("could not find __TEXT,__unwind_info section\n");
|
|
exit(1);
|
|
}
|
|
|
|
struct unwind_info_section_header header;
|
|
memcpy(&header, baton.compact_unwind_start,
|
|
sizeof(struct unwind_info_section_header));
|
|
printf("Header:\n");
|
|
printf(" version %u\n", header.version);
|
|
printf(" commonEncodingsArraySectionOffset is %d\n",
|
|
header.commonEncodingsArraySectionOffset);
|
|
printf(" commonEncodingsArrayCount is %d\n",
|
|
header.commonEncodingsArrayCount);
|
|
printf(" personalityArraySectionOffset is %d\n",
|
|
header.personalityArraySectionOffset);
|
|
printf(" personalityArrayCount is %d\n", header.personalityArrayCount);
|
|
printf(" indexSectionOffset is %d\n", header.indexSectionOffset);
|
|
printf(" indexCount is %d\n", header.indexCount);
|
|
|
|
uint8_t *common_encodings =
|
|
baton.compact_unwind_start + header.commonEncodingsArraySectionOffset;
|
|
uint32_t encoding_idx = 0;
|
|
while (encoding_idx < header.commonEncodingsArrayCount) {
|
|
uint32_t encoding = *((uint32_t *)common_encodings);
|
|
printf(" Common Encoding [%d]: 0x%x ", encoding_idx, encoding);
|
|
print_encoding(baton, NULL, encoding);
|
|
printf("\n");
|
|
common_encodings += sizeof(uint32_t);
|
|
encoding_idx++;
|
|
}
|
|
|
|
uint8_t *pers_arr =
|
|
baton.compact_unwind_start + header.personalityArraySectionOffset;
|
|
uint32_t pers_idx = 0;
|
|
while (pers_idx < header.personalityArrayCount) {
|
|
int32_t pers_delta = *((int32_t *)(baton.compact_unwind_start +
|
|
header.personalityArraySectionOffset +
|
|
(pers_idx * sizeof(uint32_t))));
|
|
printf(" Personality [%d]: personality function ptr @ offset %d (file "
|
|
"address 0x%" PRIx64 ")\n",
|
|
pers_idx, pers_delta, baton.text_segment_vmaddr + pers_delta);
|
|
pers_idx++;
|
|
pers_arr += sizeof(uint32_t);
|
|
}
|
|
|
|
printf("\n");
|
|
|
|
baton.unwind_header = header;
|
|
|
|
print_index_sections(baton);
|
|
|
|
return 0;
|
|
}
|