From 77925f264fbb60d67fb83e9f2d7d27f5898f7982 Mon Sep 17 00:00:00 2001 From: rajdakin Date: Mon, 14 Feb 2022 13:13:12 +0100 Subject: [PATCH] Fixed the backtrace wrapper (uses eh_frame information only) --- CMakeLists.txt | 82 ++-- src/dynarec/dynarec.c | 2 + src/elfs/elfdwarf_private.c | 717 +++++++++++++++++++++++++++++++++++ src/elfs/elfdwarf_private.h | 19 + src/elfs/elfloader_private.h | 5 + src/elfs/elfparser.c | 11 + src/emu/x64emu.c | 2 + src/libtools/threads.c | 5 +- src/wrapped/wrappedlibc.c | 68 ++-- tests/ref19.txt | 5 + tests/test19 | Bin 0 -> 24408 bytes tests/test19.c | 73 ++++ 12 files changed, 917 insertions(+), 72 deletions(-) create mode 100644 src/elfs/elfdwarf_private.c create mode 100644 src/elfs/elfdwarf_private.h create mode 100644 tests/ref19.txt create mode 100755 tests/test19 create mode 100644 tests/test19.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 30622eb5c..cc83f5301 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,12 +21,12 @@ option(NOGIT "Set to ON if not building from a git clone repo (like when buildin if(LARCH64) set(LD80BITS OFF CACHE BOOL "") set(NOALIGN OFF CACHE BOOL "") - set(ARM_DYNAREC OFF CACHE BOOL "") + set(ARM_DYNAREC OFF CACHE BOOL "") endif() if(PPC64LE) set(LD80BITS OFF CACHE BOOL "") set(NOALIGN OFF CACHE BOOL "") - set(ARM_DYNAREC OFF CACHE BOOL "") + set(ARM_DYNAREC OFF CACHE BOOL "") endif() if(RK3399 OR RPI4ARM64 OR RK3326 OR TEGRAX1 OR PHYTIUM OR SD845 OR LX2160A) set(LD80BITS OFF CACHE BOOL "") @@ -166,6 +166,7 @@ set(ELFLOADER_SRC "${BOX64_ROOT}/src/build_info.c" "${BOX64_ROOT}/src/custommem.c" "${BOX64_ROOT}/src/dynarec/dynarec.c" + "${BOX64_ROOT}/src/elfs/elfdwarf_private.c" "${BOX64_ROOT}/src/elfs/elfloader.c" "${BOX64_ROOT}/src/elfs/elfparser.c" "${BOX64_ROOT}/src/elfs/elfload_dump.c" @@ -572,96 +573,101 @@ endif() set(CPACK_DEBIAN_FILE_NAME "${BOX64}-${BOX64_MAJOR}.${BOX64_MINOR}.${BOX64_REVISION}_Linux-${CPACK_DEBIAN_PACKAGE_ARCHITECTURE}.deb") INCLUDE(CPack) -add_test(test01 ${CMAKE_COMMAND} -D TEST_PROGRAM=${CMAKE_BINARY_DIR}/${BOX64} - -D TEST_ARGS=${CMAKE_SOURCE_DIR}/tests/test01 -D TEST_OUTPUT=tmpfile01.txt +add_test(test01 ${CMAKE_COMMAND} -D TEST_PROGRAM=${CMAKE_BINARY_DIR}/${BOX64} + -D TEST_ARGS=${CMAKE_SOURCE_DIR}/tests/test01 -D TEST_OUTPUT=tmpfile01.txt -D TEST_REFERENCE=${CMAKE_SOURCE_DIR}/tests/ref01.txt -P ${CMAKE_SOURCE_DIR}/runTest.cmake ) -add_test(test02 ${CMAKE_COMMAND} -D TEST_PROGRAM=${CMAKE_BINARY_DIR}/${BOX64} - -D TEST_ARGS=${CMAKE_SOURCE_DIR}/tests/test02 -D TEST_OUTPUT=tmpfile02.txt +add_test(test02 ${CMAKE_COMMAND} -D TEST_PROGRAM=${CMAKE_BINARY_DIR}/${BOX64} + -D TEST_ARGS=${CMAKE_SOURCE_DIR}/tests/test02 -D TEST_OUTPUT=tmpfile02.txt -D TEST_REFERENCE=${CMAKE_SOURCE_DIR}/tests/ref02.txt -P ${CMAKE_SOURCE_DIR}/runTest.cmake ) -add_test(test03 ${CMAKE_COMMAND} -D TEST_PROGRAM=${CMAKE_BINARY_DIR}/${BOX64} - -D TEST_ARGS=${CMAKE_SOURCE_DIR}/tests/test03 -D TEST_OUTPUT=tmpfile03.txt +add_test(test03 ${CMAKE_COMMAND} -D TEST_PROGRAM=${CMAKE_BINARY_DIR}/${BOX64} + -D TEST_ARGS=${CMAKE_SOURCE_DIR}/tests/test03 -D TEST_OUTPUT=tmpfile03.txt -D TEST_REFERENCE=${CMAKE_SOURCE_DIR}/tests/ref03.txt -P ${CMAKE_SOURCE_DIR}/runTest.cmake ) -add_test(test04 ${CMAKE_COMMAND} -D TEST_PROGRAM=${CMAKE_BINARY_DIR}/${BOX64} - -D TEST_ARGS=${CMAKE_SOURCE_DIR}/tests/test04 -D TEST_ARGS2=yeah -D TEST_OUTPUT=tmpfile04.txt +add_test(test04 ${CMAKE_COMMAND} -D TEST_PROGRAM=${CMAKE_BINARY_DIR}/${BOX64} + -D TEST_ARGS=${CMAKE_SOURCE_DIR}/tests/test04 -D TEST_ARGS2=yeah -D TEST_OUTPUT=tmpfile04.txt -D TEST_REFERENCE=${CMAKE_SOURCE_DIR}/tests/ref04.txt -P ${CMAKE_SOURCE_DIR}/runTest.cmake ) -add_test(test05 ${CMAKE_COMMAND} -D TEST_PROGRAM=${CMAKE_BINARY_DIR}/${BOX64} - -D TEST_ARGS=${CMAKE_SOURCE_DIR}/tests/test05 -D TEST_ARGS2=7 -D TEST_OUTPUT=tmpfile05.txt +add_test(test05 ${CMAKE_COMMAND} -D TEST_PROGRAM=${CMAKE_BINARY_DIR}/${BOX64} + -D TEST_ARGS=${CMAKE_SOURCE_DIR}/tests/test05 -D TEST_ARGS2=7 -D TEST_OUTPUT=tmpfile05.txt -D TEST_REFERENCE=${CMAKE_SOURCE_DIR}/tests/ref05.txt -P ${CMAKE_SOURCE_DIR}/runTest.cmake ) -add_test(test06 ${CMAKE_COMMAND} -D TEST_PROGRAM=${CMAKE_BINARY_DIR}/${BOX64} - -D TEST_ARGS=${CMAKE_SOURCE_DIR}/tests/test06 -D TEST_OUTPUT=tmpfile06.txt +add_test(test06 ${CMAKE_COMMAND} -D TEST_PROGRAM=${CMAKE_BINARY_DIR}/${BOX64} + -D TEST_ARGS=${CMAKE_SOURCE_DIR}/tests/test06 -D TEST_OUTPUT=tmpfile06.txt -D TEST_REFERENCE=${CMAKE_SOURCE_DIR}/tests/ref06.txt -P ${CMAKE_SOURCE_DIR}/runTest.cmake ) -add_test(test07 ${CMAKE_COMMAND} -D TEST_PROGRAM=${CMAKE_BINARY_DIR}/${BOX64} - -D TEST_ARGS=${CMAKE_SOURCE_DIR}/tests/test07 -D TEST_OUTPUT=tmpfile07.txt +add_test(test07 ${CMAKE_COMMAND} -D TEST_PROGRAM=${CMAKE_BINARY_DIR}/${BOX64} + -D TEST_ARGS=${CMAKE_SOURCE_DIR}/tests/test07 -D TEST_OUTPUT=tmpfile07.txt -D TEST_REFERENCE=${CMAKE_SOURCE_DIR}/tests/ref07.txt -P ${CMAKE_SOURCE_DIR}/runTest.cmake ) -add_test(test08 ${CMAKE_COMMAND} -D TEST_PROGRAM=${CMAKE_BINARY_DIR}/${BOX64} - -D TEST_ARGS=${CMAKE_SOURCE_DIR}/tests/test08 -D TEST_OUTPUT=tmpfile08.txt +add_test(test08 ${CMAKE_COMMAND} -D TEST_PROGRAM=${CMAKE_BINARY_DIR}/${BOX64} + -D TEST_ARGS=${CMAKE_SOURCE_DIR}/tests/test08 -D TEST_OUTPUT=tmpfile08.txt -D TEST_REFERENCE=${CMAKE_SOURCE_DIR}/tests/ref08.txt -P ${CMAKE_SOURCE_DIR}/runTest.cmake ) -add_test(test09 ${CMAKE_COMMAND} -D TEST_PROGRAM=${CMAKE_BINARY_DIR}/${BOX64} - -D TEST_ARGS=${CMAKE_SOURCE_DIR}/tests/test09 -D TEST_OUTPUT=tmpfile09.txt +add_test(test09 ${CMAKE_COMMAND} -D TEST_PROGRAM=${CMAKE_BINARY_DIR}/${BOX64} + -D TEST_ARGS=${CMAKE_SOURCE_DIR}/tests/test09 -D TEST_OUTPUT=tmpfile09.txt -D TEST_REFERENCE=${CMAKE_SOURCE_DIR}/tests/ref09.txt -P ${CMAKE_SOURCE_DIR}/runTest.cmake ) -add_test(test10 ${CMAKE_COMMAND} -D TEST_PROGRAM=${CMAKE_BINARY_DIR}/${BOX64} - -D TEST_ARGS=${CMAKE_SOURCE_DIR}/tests/test10 -D TEST_OUTPUT=tmpfile10.txt +add_test(test10 ${CMAKE_COMMAND} -D TEST_PROGRAM=${CMAKE_BINARY_DIR}/${BOX64} + -D TEST_ARGS=${CMAKE_SOURCE_DIR}/tests/test10 -D TEST_OUTPUT=tmpfile10.txt -D TEST_REFERENCE=${CMAKE_SOURCE_DIR}/tests/ref10.txt -P ${CMAKE_SOURCE_DIR}/runTest.cmake ) -add_test(test11 ${CMAKE_COMMAND} -D TEST_PROGRAM=${CMAKE_BINARY_DIR}/${BOX64} - -D TEST_ARGS=${CMAKE_SOURCE_DIR}/tests/test11 -D TEST_OUTPUT=tmpfile11.txt +add_test(test11 ${CMAKE_COMMAND} -D TEST_PROGRAM=${CMAKE_BINARY_DIR}/${BOX64} + -D TEST_ARGS=${CMAKE_SOURCE_DIR}/tests/test11 -D TEST_OUTPUT=tmpfile11.txt -D TEST_REFERENCE=${CMAKE_SOURCE_DIR}/tests/ref11.txt -P ${CMAKE_SOURCE_DIR}/runTest.cmake ) -add_test(test12 ${CMAKE_COMMAND} -D TEST_PROGRAM=${CMAKE_BINARY_DIR}/${BOX64} - -D TEST_ARGS=${CMAKE_SOURCE_DIR}/tests/test12 -D TEST_OUTPUT=tmpfile12.txt +add_test(test12 ${CMAKE_COMMAND} -D TEST_PROGRAM=${CMAKE_BINARY_DIR}/${BOX64} + -D TEST_ARGS=${CMAKE_SOURCE_DIR}/tests/test12 -D TEST_OUTPUT=tmpfile12.txt -D TEST_REFERENCE=${CMAKE_SOURCE_DIR}/tests/ref12.txt -P ${CMAKE_SOURCE_DIR}/runTest.cmake ) -add_test(test13 ${CMAKE_COMMAND} -D TEST_PROGRAM=${CMAKE_BINARY_DIR}/${BOX64} - -D TEST_ARGS=${CMAKE_SOURCE_DIR}/tests/test13 -D TEST_OUTPUT=tmpfile13.txt +add_test(test13 ${CMAKE_COMMAND} -D TEST_PROGRAM=${CMAKE_BINARY_DIR}/${BOX64} + -D TEST_ARGS=${CMAKE_SOURCE_DIR}/tests/test13 -D TEST_OUTPUT=tmpfile13.txt -D TEST_REFERENCE=${CMAKE_SOURCE_DIR}/tests/ref13.txt -P ${CMAKE_SOURCE_DIR}/runTest.cmake ) -#add_test(test14 ${CMAKE_COMMAND} -D TEST_PROGRAM=${CMAKE_BINARY_DIR}/${BOX64} -# -D TEST_ARGS=${CMAKE_SOURCE_DIR}/tests/test14 -D TEST_OUTPUT=tmpfile14.txt +#add_test(test14 ${CMAKE_COMMAND} -D TEST_PROGRAM=${CMAKE_BINARY_DIR}/${BOX64} +# -D TEST_ARGS=${CMAKE_SOURCE_DIR}/tests/test14 -D TEST_OUTPUT=tmpfile14.txt # -D TEST_REFERENCE=${CMAKE_SOURCE_DIR}/tests/ref14.txt # -P ${CMAKE_SOURCE_DIR}/runTest.cmake ) -add_test(test15 ${CMAKE_COMMAND} -D TEST_PROGRAM=${CMAKE_BINARY_DIR}/${BOX64} - -D TEST_ARGS=${CMAKE_SOURCE_DIR}/tests/test15 -D TEST_OUTPUT=tmpfile15.txt +add_test(test15 ${CMAKE_COMMAND} -D TEST_PROGRAM=${CMAKE_BINARY_DIR}/${BOX64} + -D TEST_ARGS=${CMAKE_SOURCE_DIR}/tests/test15 -D TEST_OUTPUT=tmpfile15.txt -D TEST_REFERENCE=${CMAKE_SOURCE_DIR}/tests/ref15.txt -P ${CMAKE_SOURCE_DIR}/runTest.cmake ) -add_test(test16 ${CMAKE_COMMAND} -D TEST_PROGRAM=${CMAKE_BINARY_DIR}/${BOX64} - -D TEST_ARGS=${CMAKE_SOURCE_DIR}/tests/test16 -D TEST_OUTPUT=tmpfile16.txt +add_test(test16 ${CMAKE_COMMAND} -D TEST_PROGRAM=${CMAKE_BINARY_DIR}/${BOX64} + -D TEST_ARGS=${CMAKE_SOURCE_DIR}/tests/test16 -D TEST_OUTPUT=tmpfile16.txt -D TEST_REFERENCE=${CMAKE_SOURCE_DIR}/tests/ref16.txt -P ${CMAKE_SOURCE_DIR}/runTest.cmake ) -add_test(test17 ${CMAKE_COMMAND} -D TEST_PROGRAM=${CMAKE_BINARY_DIR}/${BOX64} - -D TEST_ARGS=${CMAKE_SOURCE_DIR}/tests/test17 -D TEST_OUTPUT=tmpfile17.txt +add_test(test17 ${CMAKE_COMMAND} -D TEST_PROGRAM=${CMAKE_BINARY_DIR}/${BOX64} + -D TEST_ARGS=${CMAKE_SOURCE_DIR}/tests/test17 -D TEST_OUTPUT=tmpfile17.txt -D TEST_REFERENCE=${CMAKE_SOURCE_DIR}/tests/ref17.txt -P ${CMAKE_SOURCE_DIR}/runTest.cmake ) -add_test(aes ${CMAKE_COMMAND} -D TEST_PROGRAM=${CMAKE_BINARY_DIR}/${BOX64} - -D TEST_ARGS=${CMAKE_SOURCE_DIR}/tests/test18 -D TEST_OUTPUT=tmpfile18.txt +add_test(aes ${CMAKE_COMMAND} -D TEST_PROGRAM=${CMAKE_BINARY_DIR}/${BOX64} + -D TEST_ARGS=${CMAKE_SOURCE_DIR}/tests/test18 -D TEST_OUTPUT=tmpfile18.txt -D TEST_REFERENCE=${CMAKE_SOURCE_DIR}/tests/ref18.txt -P ${CMAKE_SOURCE_DIR}/runTest.cmake ) +add_test(backtrace ${CMAKE_COMMAND} -D TEST_PROGRAM=${CMAKE_BINARY_DIR}/${BOX64} + -D TEST_ARGS=${CMAKE_SOURCE_DIR}/tests/test19 -D TEST_OUTPUT=tmpfile19.txt + -D TEST_REFERENCE=${CMAKE_SOURCE_DIR}/tests/ref19.txt + -P ${CMAKE_SOURCE_DIR}/runTest.cmake ) + file(GLOB extension_tests "${CMAKE_SOURCE_DIR}/tests/extensions/*.c") foreach(file ${extension_tests}) get_filename_component(testname "${file}" NAME_WE) diff --git a/src/dynarec/dynarec.c b/src/dynarec/dynarec.c index 8edefa580..36fa85ad0 100755 --- a/src/dynarec/dynarec.c +++ b/src/dynarec/dynarec.c @@ -111,6 +111,8 @@ void DynaCall(x64emu_t* emu, uintptr_t addr) uint64_t old_rsi = R_RSI; uint64_t old_rbp = R_RBP; uint64_t old_rip = R_RIP; + Push64(emu, GetRBP(emu)); // set frame pointer + SetRBP(emu, GetRSP(emu)); // save RSP PushExit(emu); R_RIP = addr; emu->df = d_none; diff --git a/src/elfs/elfdwarf_private.c b/src/elfs/elfdwarf_private.c new file mode 100644 index 000000000..7c5b3c92e --- /dev/null +++ b/src/elfs/elfdwarf_private.c @@ -0,0 +1,717 @@ +#include +#include +#include +#include + +#include "debug.h" +#include "elfloader_private.h" +#include "elfdwarf_private.h" + +typedef struct dwarf_unwind_constr_s { + uint8_t reg_count; + uint64_t *table; + uint8_t *statuses; +#define GET_STATUS(n,i) (((n).statuses[(i) >> 1] >> (((i) & 1) << 2)) & 0xF) +#define SET_STATUS(n,i,v) (n).statuses[(i) >> 1] = ((n).statuses[(i) >> 1] & (0xF0 >> (((i) & 1) << 2))) | (v << (((i) & 1) << 2)) +#define REGSTATUS_undefined 0b0000 +#define REGSTATUS_same_val 0b0101 +#define REGSTATUS_offset 0b0010 +#define REGSTATUS_val_offset 0b0011 +#define REGSTATUS_register 0b0100 +} dwarf_unwind_constr_t; + +#define DW_EH_PE_uptr 0x00 +#define DW_EH_PE_uleb128 0x01 +#define DW_EH_PE_udata2 0x02 +#define DW_EH_PE_udata4 0x03 +#define DW_EH_PE_udata8 0x04 +#define DW_EH_PE_SIZE_MASK 0x07 +#define DW_EH_PE_signed 0x08 +#define DW_EH_PE_absptr 0x00 +#define DW_EH_PE_pcrel 0x10 +#define DW_EH_PE_textrel 0x20 // Unsupported +#define DW_EH_PE_datarel 0x30 // Unsupported +#define DW_EH_PE_indirect 0x80 // May crash +#define DW_EH_PE_omit 0xff + +#define READ_U1(var, ptr) do { (var) = *( uint8_t*)(ptr); (ptr) += 1; } while (0) +#define READ_S1(var, ptr) do { (var) = *( int8_t*)(ptr); (ptr) += 1; } while (0) +#define READ_U2(var, ptr) do { (var) = *(uint16_t*)(ptr); (ptr) += 2; } while (0) +#define READ_S2(var, ptr) do { (var) = *( int16_t*)(ptr); (ptr) += 2; } while (0) +#define READ_U4(var, ptr) do { (var) = *(uint32_t*)(ptr); (ptr) += 4; } while (0) +#define READ_S4(var, ptr) do { (var) = *( int32_t*)(ptr); (ptr) += 4; } while (0) +#define READ_U8(var, ptr) do { (var) = *(uint64_t*)(ptr); (ptr) += 8; } while (0) +#define READ_S8(var, ptr) do { (var) = *( int64_t*)(ptr); (ptr) += 8; } while (0) +#define READ_ULEB128(var, ptr) readULEB(&(var), &(ptr)) +#define READ_SLEB128(var, ptr) readSLEB((int64_t*)&(var), &(ptr)) +// Seems like DW_EH_PE_signed is actually an invalid encoding +#define READ_ENCODED(var, ptr, enc, isptr) do { uintptr_t optr = (uintptr_t)(ptr); \ + if ((((enc) & DW_EH_PE_SIZE_MASK) == DW_EH_PE_uleb128) && ((enc) & DW_EH_PE_signed)) READ_SLEB128(var, ptr); \ + else if ((((enc) & DW_EH_PE_SIZE_MASK) == DW_EH_PE_uleb128) && !((enc) & DW_EH_PE_signed)) READ_ULEB128(var, ptr); \ + else if ((((enc) & DW_EH_PE_SIZE_MASK) == DW_EH_PE_udata2 ) && ((enc) & DW_EH_PE_signed)) READ_S2 (var, ptr); \ + else if ((((enc) & DW_EH_PE_SIZE_MASK) == DW_EH_PE_udata2 ) && !((enc) & DW_EH_PE_signed)) READ_U2 (var, ptr); \ + else if ((((enc) & DW_EH_PE_SIZE_MASK) == DW_EH_PE_udata4 ) && ((enc) & DW_EH_PE_signed)) READ_S4 (var, ptr); \ + else if ((((enc) & DW_EH_PE_SIZE_MASK) == DW_EH_PE_udata4 ) && !((enc) & DW_EH_PE_signed)) READ_U4 (var, ptr); \ + else if ((((enc) & DW_EH_PE_SIZE_MASK) == DW_EH_PE_udata8 ) && ((enc) & DW_EH_PE_signed)) READ_S8 (var, ptr); \ + else if ((((enc) & DW_EH_PE_SIZE_MASK) == DW_EH_PE_udata8 ) && !((enc) & DW_EH_PE_signed)) READ_U8 (var, ptr); \ + else if ((((enc) & DW_EH_PE_SIZE_MASK) == DW_EH_PE_uptr ) && ((enc) & DW_EH_PE_signed)) READ_S8 (var, ptr); \ + else if ((((enc) & DW_EH_PE_SIZE_MASK) == DW_EH_PE_uptr ) && !((enc) & DW_EH_PE_signed)) READ_U8 (var, ptr); \ + else { printf_log(LOG_DEBUG, "Invalid encoding 0x%02X\n", (enc)); (var) = 0; } \ + if (((enc) & DW_EH_PE_pcrel) && isptr) (var) += optr; \ + if ((enc) & DW_EH_PE_indirect) (var) = *(uint64_t*)(var); \ +} while (0) +#define SKIP_1(ptr) (ptr) += 1 +#define SKIP_2(ptr) (ptr) += 2 +#define SKIP_4(ptr) (ptr) += 4 +#define SKIP_8(ptr) (ptr) += 8 +#define SKIP_LEB128(ptr) do { ++(ptr); } while (((*(((unsigned char*)(ptr))-1))) & (1 << 7)) +#define SKIP_ENCODED(ptr, enc) \ + do { if (((enc) & DW_EH_PE_SIZE_MASK) == DW_EH_PE_uleb128) SKIP_LEB128(ptr); \ + else if (((enc) & DW_EH_PE_SIZE_MASK) == DW_EH_PE_udata2 ) SKIP_2 (ptr); \ + else if (((enc) & DW_EH_PE_SIZE_MASK) == DW_EH_PE_udata4 ) SKIP_4 (ptr); \ + else if (((enc) & DW_EH_PE_SIZE_MASK) == DW_EH_PE_udata8 ) SKIP_8 (ptr); \ + else if (((enc) & DW_EH_PE_SIZE_MASK) == DW_EH_PE_uptr ) SKIP_8 (ptr); \ + else printf_log(LOG_DEBUG, "Invalid encoding 0x%02X\n", (enc)); \ +} while (0) + +// LEB128 wil only work on 64 bits numbers (unsigned long, like the Linux kernel) +void readULEB(uint64_t *var, unsigned char **ptr) { + *var = 0; + int shift = 0; + while (1) { + unsigned char byte = **ptr; ++*ptr; + *var |= ((byte & 0x7F) << shift); + if (!(byte & (1 << 7))) break; + shift += 7; + } +} +void readSLEB(int64_t *var, unsigned char **ptr) { + uint64_t tmp = 0; + int shift = 0; + unsigned char byte; + while (1) { + byte = **ptr; ++*ptr; + tmp |= ((byte & 0x7F) << shift); + if (!(byte & (1 << 7))) break; + shift += 7; + } + // Sign extend + if (byte & (1 << 6)) { + *(uint64_t*)var = tmp | (0xFFFFFFFFFFFFFFFFull << (shift + 7)); + } else { + *var = tmp; + } +} + +uintptr_t get_parent_registers(dwarf_unwind_t *unwind, const elfheader_t *ehdr, uintptr_t addr, char *success) { + if (!ehdr) { + *success = 0; + return 0; + } + unsigned char ehfh_version = *(unsigned char*)ehdr->ehframehdr; + if (ehfh_version != 1) { + *success = 0; + return 0; + } + + // printf_log(LOG_NONE, "Address: 0x%016lX\nEH frame address: 0x%016lX\n", addr, ehdr->ehframe); + + // Not using the binary search table (for now) + + unsigned char *cur_addr = (unsigned char*)ehdr->ehframe; + unsigned char *end_addr = (unsigned char*)ehdr->ehframe_end; + +#define AUG_EHDATA (1 << 0) +#define AUG_AUGDATA (1 << 1) +#define AUG_LSDA (1 << 2) +#define AUG_PARG (1 << 3) +#define AUG_FDEENC (1 << 4) +#define AUG_SIGHDLER (1 << 5) + unsigned short aug_fields = 0; + unsigned char lsda_encoding = DW_EH_PE_omit; + unsigned char fde_encoding = DW_EH_PE_omit; + uint64_t eh_data = 0; // Note: these are never reset, so use aug_fields & AUG_EHDATA + uint64_t code_alignment_factor = 0; + int64_t data_alignment_factor = 0; + uint64_t cie_aug_data_len = 0; // Note: these are never reset, so use aug_fields & AUG_AUGDATA + unsigned char *cie_aug_data = NULL; // Note: these are never reset, so use aug_fields & AUG_AUGDATA + unsigned char *initCIEinstr = NULL; // Never NULL if initialized + unsigned char *endCIEinstr = NULL; // Never NULL if initialized + uint64_t return_addr_reg = 0; // Description absent in the LSBCS 5.0, but still present + while (cur_addr < end_addr) { + // Length + uint64_t len; int extended = 0; + READ_U4(len, cur_addr); + if (!len) { + // Terminator, end parsing + *success = 0; + return 0; + } else if (len == 0xFFFFFFFF) { + // Extended Length (optional) + extended = 1; + READ_U8(len, cur_addr); + } + (void)extended; + // printf_log(LOG_NONE, "[???] Length %02X\n", len); + // printf_log(LOG_NONE, "[???] Extended %c\n", extended ? 'Y' : 'N'); + unsigned char *next_addr = cur_addr + len; + + // CIE ID (always 0) or CIE Pointer (never 0) + uint32_t cie_ptr; + READ_U4(cie_ptr, cur_addr); + if (cie_ptr == 0) { + // Current block is a CIE + // printf_log(LOG_NONE, "[CIE] ID %02X\n", cie_ptr); + + // Version (always 1 or 3) + uint8_t cie_version; + READ_U1(cie_version, cur_addr); + // printf_log(LOG_NONE, "[CIE] Version %01X\n", cie_version); + if ((cie_version != 1) && (cie_version != 3)) { // Only 1 is in the LSBCS 5.0 but I found 3 exists as well + // Invalid CIE version + printf_log(LOG_DEBUG, "Invalid CIE version %d (should be 1 or 3), stopping parsing\n", cie_version); + // Failed to process CIE + *success = 0; + return 0; + } + + // Augmentation String + char *aug_str = (char*)cur_addr; cur_addr += strlen(aug_str) + 1; + // printf_log(LOG_NONE, "[CIE] Augmentation st %s\n", aug_str); + aug_fields = (aug_str[0] == 'z') ? AUG_AUGDATA : 0; + for (char *aug_str2 = aug_str + ((aug_fields & AUG_AUGDATA) ? 1 : 0); *aug_str2; ++aug_str2) { + if ((aug_str2[0] == 'e') && (aug_str2[1] == 'h')) { + // Use has also been left untold in the Linux Standard Base Core Specification 3.0RC1 + printf_log(LOG_DEBUG, "Warning: EH data detected but this was removed from the standard\n"); + aug_fields |= AUG_EHDATA; ++aug_str2; + } else if (aug_fields & AUG_AUGDATA) { + if (aug_str2[0] == 'L') { + aug_fields |= AUG_LSDA; + } else if (aug_str2[0] == 'P') { + aug_fields |= AUG_PARG; + printf_log(LOG_DEBUG, "Warning: augmentation string attribute 'P' unsupported\n"); + } else if (aug_str2[0] == 'S') { + aug_fields |= AUG_SIGHDLER; + printf_log(LOG_DEBUG, "Warning: augmentation string attribute 'S' ignored\n"); + } else if (aug_str2[0] == 'R') { + aug_fields |= AUG_FDEENC; + } else { + printf_log(LOG_DEBUG, "Error: invalid augmentation string %s\n", aug_str); + // Failed to process augmentation string + break; + } + } else { + printf_log(LOG_DEBUG, "Error: invalid augmentation string %s\n", aug_str); + // Failed to process augmentation string + break; + } + } + // printf_log(LOG_NONE, "[CIE] Augmentation fs %02x\n", aug_fields); + + // Code Alignment Factor + READ_ULEB128(code_alignment_factor, cur_addr); + // printf_log(LOG_NONE, "[CIE] Code AF %d\n", code_alignment_factor); + // Data Alignment Factor + READ_SLEB128(data_alignment_factor, cur_addr); + // printf_log(LOG_NONE, "[CIE] Data AF %d\n", data_alignment_factor); + + if (cie_version == 1) { + READ_U1(return_addr_reg, cur_addr); + } else { + READ_ULEB128(return_addr_reg, cur_addr); + } + // printf_log(LOG_NONE, "[CIE] Return addr reg %01X\n", return_addr_reg); + + lsda_encoding = DW_EH_PE_omit; + fde_encoding = DW_EH_PE_absptr; + if (aug_fields & AUG_AUGDATA) { + // Augmentation Data Length (optional) + READ_ULEB128(cie_aug_data_len, cur_addr); + // printf_log(LOG_NONE, "[CIE] Aug data len %01X\n", cie_aug_data_len); + // Augmentation Data (optional) + cie_aug_data = cur_addr; cur_addr += cie_aug_data_len; + char *cie_aug_data2 = (char*)cie_aug_data; + for (char *aug_str2 = aug_str + ((aug_fields & AUG_AUGDATA) ? 1 : 0); *aug_str2; ++aug_str2) { + if ((aug_str2[0] == 'e') && (aug_str2[1] == 'h')) { + break; // Unknown usage + } else if (aug_fields & AUG_AUGDATA) { + if (aug_str2[0] == 'L') { + READ_U1(lsda_encoding, cie_aug_data2); + if (lsda_encoding != DW_EH_PE_omit) printf_log(LOG_DEBUG, "Warning: LSDA unsupported\n"); + } else if (aug_str2[0] == 'P') { + unsigned char pencoding; + READ_U1(pencoding, cie_aug_data2); + SKIP_ENCODED(cie_aug_data2, pencoding); + } else if (aug_str2[0] == 'S') { + } else if (aug_str2[0] == 'R') { + READ_U1(fde_encoding, cie_aug_data2); + // printf_log(LOG_NONE, "[CIE] FDE encoding %02X\n", fde_encoding); + if (fde_encoding == DW_EH_PE_omit) printf_log(LOG_DEBUG, "Error: FDE encoding set to 'omit'\n"); + } else { + break; + } + } else { + break; + } + } + } + + // Initial Instructions + initCIEinstr = cur_addr; + endCIEinstr = next_addr; + } else if (initCIEinstr) { + // Current block is a FDE + // printf_log(LOG_NONE, "[FDE] Ptr %02X\n", cie_ptr); + + // PC Begin + uintptr_t pc_begin; + READ_ENCODED(pc_begin, cur_addr, fde_encoding, 1); + // printf_log(LOG_NONE, "[FDE] PC begin %016lX\n", pc_begin); + // PC Range + uint64_t pc_range; + READ_ENCODED(pc_range, cur_addr, fde_encoding, 0); + // printf_log(LOG_NONE, "[FDE] PC end %016lX\n", pc_begin + pc_range); + + uint64_t aug_data_len; + unsigned char *aug_data; + if (aug_fields & AUG_AUGDATA) { + // Augmentation Data Length (optional) + READ_ULEB128(aug_data_len, cur_addr); + // printf_log(LOG_NONE, "[FDE] Aug data len %01X\n", aug_data_len); + // Augmentation Data (optional) + aug_data = cur_addr; cur_addr += aug_data_len; + (void)aug_data; + } + + if (lsda_encoding != DW_EH_PE_omit) { + SKIP_ENCODED(cur_addr, lsda_encoding); + } + + // Call Frame Instructions + if ((pc_begin <= addr) && (addr < pc_begin + pc_range)) { + // Corresponding Frame Descriptor Entry found! + +#define setmax(a, b) a = (a < (b) ? (b) : a) + dwarf_unwind_constr_t unwind_constr; + unwind_constr.reg_count = return_addr_reg; + uint64_t maxstacksize = 0; + uint64_t curstacksize = 0; + unsigned char *cur_inst = initCIEinstr; + uint64_t nreg; +#define PARSE_INST \ + switch (inst >> 6) { \ + case 0b01: \ + break; \ + case 0b10: \ + setmax(unwind_constr.reg_count, inst & 0x3F); \ + SKIP_LEB128(cur_inst); \ + break; \ + case 0b11: \ + setmax(unwind_constr.reg_count, inst & 0x3F); \ + break; \ + case 0b00: \ + switch (inst & 0x3F) { \ + case 0b000000: \ + break; \ + case 0b001010: \ + ++curstacksize; \ + setmax(maxstacksize, curstacksize); \ + break; \ + case 0b001011: \ + if (!curstacksize) { \ + printf_log(LOG_DEBUG, "Negative stack size during CFI execution\n"); \ + *success = 0; \ + return 0; \ + } \ + --curstacksize; \ + break; \ + case 0b000010: \ + SKIP_1(cur_inst); \ + break; \ + case 0b000011: \ + SKIP_2(cur_inst); \ + break; \ + case 0b000100: \ + SKIP_4(cur_inst); \ + break; \ + case 0b000001: \ + SKIP_8(cur_inst); \ + break; \ + case 0b001110: \ + case 0b010011: \ + SKIP_LEB128(cur_inst); \ + break; \ + case 0b000110: \ + case 0b000111: \ + case 0b001000: \ + case 0b001101: \ + READ_ULEB128(nreg, cur_inst); \ + setmax(unwind_constr.reg_count, nreg); \ + break; \ + case 0b000101: \ + case 0b001100: \ + case 0b010001: \ + case 0b010010: \ + case 0b010100: \ + case 0b010101: \ + READ_ULEB128(nreg, cur_inst); \ + setmax(unwind_constr.reg_count, nreg); \ + SKIP_LEB128(cur_inst); \ + break; \ + case 0b001001: \ + READ_ULEB128(nreg, cur_inst); \ + setmax(unwind_constr.reg_count, nreg); \ + READ_ULEB128(nreg, cur_inst); \ + setmax(unwind_constr.reg_count, nreg); \ + break; \ + default: \ + /* Contains undefined, user and expression CFIs */ \ + printf_log(LOG_DEBUG, "Unknown CFI 0x%02X\n", inst); \ + *success = 0; \ + return 0; \ + } \ + break; \ + } + while (cur_inst < endCIEinstr) { + unsigned char inst; READ_U1(inst, cur_inst); + PARSE_INST + } + cur_inst = cur_addr; + while (cur_inst < next_addr) { + unsigned char inst; READ_U1(inst, cur_inst); + PARSE_INST + } +#undef PARSE_INST + + uint64_t cfa_reg = -1; + unsigned char cfa_signed; + union { uint64_t uoff; int64_t soff; } cfa_offset = { .uoff = 0 }; + ++unwind_constr.reg_count; + size_t tablelen = unwind_constr.reg_count * sizeof(uint64_t); + size_t statuseslen = ((unwind_constr.reg_count+1) >> 1) * sizeof(uint8_t); + unwind_constr.table = (uint64_t*)malloc(tablelen); + unwind_constr.statuses = (uint8_t*)calloc((unwind_constr.reg_count+1) >> 1, sizeof(uint8_t)); + // ~~undefined is 0: no initialization needed~~ still initialize the first 17 to same_val + for (int i = 0; (i < 17) && (i <= unwind_constr.reg_count); ++i) { + SET_STATUS(unwind_constr, i, REGSTATUS_same_val); + } + + curstacksize = 0; + uint64_t **table_stack = (uint64_t**)malloc(maxstacksize * sizeof(uint64_t*)); + for (uint64_t i = 0; i < maxstacksize; ++i) { + table_stack[i] = (uint64_t*)malloc(tablelen); + } + cur_inst = initCIEinstr; + uintptr_t cur_pointed_addr = pc_begin; + // Missing: + /* DW_CFA_def_cfa_expression 0 0x0f BLOCK */ + /* DW_CFA_expression 0 0x10 ULEB128 register BLOCK */ + /* DW_CFA_val_expression 0 0x16 ULEB128 BLOCK */ + /* DW_CFA_lo_user 0 0x1c */ + /* DW_CFA_GNU_args_size 0 0x2e ULEB128 argsize */ + /* DW_CFA_GNU_negative_offset_extended 0 0x2f ULEB128 register ULEB128 offset (obsoleted by DW_CFA_offset_extended_sf) */ + /* DW_CFA_hi_user 0 0x3f */ + // Known "bug": DW_CFA_set_loc can go backwards, this is ignored + uint64_t tmpreg; + uint64_t tmpuval; + int64_t tmpsval; +#define PARSE_INST \ + switch (inst >> 6) { \ + case 0b01: /* DW_CFA_advance_loc 0x1 delta */ \ + cur_pointed_addr += (inst & 0x3F) * code_alignment_factor; \ + break; \ + case 0b10: /* DW_CFA_offset 0x2 register ULEB128 offset */ \ + READ_ULEB128(tmpuval, cur_inst); \ + SET_STATUS(unwind_constr, inst & 0x3F, REGSTATUS_offset); \ + unwind_constr.table[inst & 0x3F] = (int64_t)tmpuval * data_alignment_factor; \ + break; \ + case 0b11: /* DW_CFA_restore 0x3 register */ \ + RESTORE_REG(inst & 0x3F) \ + break; \ + case 0b00: \ + switch (inst & 0x3F) { \ + case 0b000001: /* DW_CFA_set_loc 0 0x01 address */ \ + READ_U8(cur_pointed_addr, cur_inst); \ + break; \ + case 0b000010: /* DW_CFA_advance_loc1 0 0x02 1-byte delta */ \ + READ_U1(tmpuval, cur_inst); \ + cur_pointed_addr += tmpuval * code_alignment_factor; \ + break; \ + case 0b000011: /* DW_CFA_advance_loc2 0 0x03 2-byte delta */ \ + READ_U2(tmpuval, cur_inst); \ + cur_pointed_addr += tmpuval * code_alignment_factor; \ + break; \ + case 0b000100: /* DW_CFA_advance_loc4 0 0x04 4-byte delta */ \ + READ_U4(tmpuval, cur_inst); \ + cur_pointed_addr += tmpuval * code_alignment_factor; \ + break; \ + \ + case 0b001100: /* DW_CFA_def_cfa 0 0x0c ULEB128 register ULEB128 offset */ \ + READ_ULEB128(cfa_reg, cur_inst); \ + cfa_signed = 0; \ + READ_ULEB128(cfa_offset.uoff, cur_inst); \ + break; \ + case 0b010010: /* DW_CFA_def_cfa_sf 0 0x12 ULEB128 register SLEB128 offset */ \ + READ_ULEB128(cfa_reg, cur_inst); \ + cfa_signed = 1; \ + READ_SLEB128(cfa_offset.soff, cur_inst); \ + cfa_offset.soff *= data_alignment_factor; \ + break; \ + case 0b001101: /* DW_CFA_def_cfa_register 0 0x0d ULEB128 register */ \ + READ_ULEB128(cfa_reg, cur_inst); \ + break; \ + case 0b001110: /* DW_CFA_def_cfa_offset 0 0x0e ULEB128 offset */ \ + cfa_signed = 0; \ + READ_ULEB128(cfa_offset.uoff, cur_inst); \ + break; \ + case 0b010011: /* DW_CFA_def_cfa_offset_sf 0 0x13 SLEB128 offset */ \ + READ_SLEB128(cfa_offset.soff, cur_inst); \ + cfa_offset.soff *= data_alignment_factor; \ + break; \ + /* DW_CFA_def_cfa_expression */ \ + \ + case 0b000111: /* DW_CFA_undefined 0 0x07 ULEB128 register */ \ + READ_ULEB128(tmpreg, cur_inst); \ + SET_STATUS(unwind_constr, tmpreg, REGSTATUS_undefined); \ + break; \ + case 0b001000: /* DW_CFA_same_value 0 0x08 ULEB128 register */ \ + READ_ULEB128(tmpreg, cur_inst); \ + SET_STATUS(unwind_constr, tmpreg, REGSTATUS_same_val); \ + break; \ + case 0b000101: /* DW_CFA_offset_extended 0 0x05 ULEB128 register ULEB128 offset */ \ + READ_ULEB128(tmpreg, cur_inst); \ + READ_ULEB128(tmpuval, cur_inst); \ + SET_STATUS(unwind_constr, tmpreg, REGSTATUS_offset); \ + unwind_constr.table[tmpreg] = (int64_t)tmpuval * data_alignment_factor; \ + break; \ + case 0b010001: /* DW_CFA_offset_extended_sf 0 0x11 ULEB128 register SLEB128 offset */ \ + READ_ULEB128(tmpreg, cur_inst); \ + READ_SLEB128(tmpsval, cur_inst); \ + SET_STATUS(unwind_constr, tmpreg, REGSTATUS_offset); \ + unwind_constr.table[tmpreg] = tmpsval * data_alignment_factor; \ + break; \ + case 0b010100: /* DW_CFA_val_offset 0 0x14 ULEB128 register ULEB128 offset */ \ + READ_ULEB128(tmpreg, cur_inst); \ + READ_ULEB128(tmpuval, cur_inst); \ + SET_STATUS(unwind_constr, tmpreg, REGSTATUS_val_offset); \ + unwind_constr.table[tmpreg] = (int64_t)tmpuval * data_alignment_factor; \ + break; \ + case 0b010101: /* DW_CFA_val_offset_sf 0 0x15 ULEB128 register SLEB128 offset */ \ + READ_ULEB128(tmpreg, cur_inst); \ + READ_SLEB128(tmpsval, cur_inst); \ + SET_STATUS(unwind_constr, tmpreg, REGSTATUS_val_offset); \ + unwind_constr.table[tmpreg] = tmpsval * data_alignment_factor; \ + break; \ + case 0b001001: /* DW_CFA_register 0 0x09 ULEB128 register ULEB128 register */ \ + READ_ULEB128(tmpreg, cur_inst); \ + READ_ULEB128(tmpuval, cur_inst); \ + SET_STATUS(unwind_constr, tmpreg, REGSTATUS_register); \ + unwind_constr.table[tmpreg] = tmpuval; \ + break; \ + /* DW_CFA_expression */ \ + /* DW_CFA_val_expression */ \ + case 0b000110: /* DW_CFA_restore_extended 0 0x06 ULEB128 register */ \ + READ_ULEB128(tmpreg, cur_inst); \ + RESTORE_REG(tmpreg) \ + break; \ + \ + case 0b001010: /* DW_CFA_remember_state 0 0x0a */ \ + memcpy(table_stack[curstacksize], unwind_constr.table, tablelen); \ + ++curstacksize; \ + break; \ + case 0b001011: /* DW_CFA_restore_state 0 0x0b */ \ + --curstacksize; \ + memcpy(unwind_constr.table, table_stack[curstacksize], tablelen); \ + break; \ + \ + case 0b000000: /* DW_CFA_nop 0 0 */ \ + break; \ + default: \ + /* Contains undefined, user and expression CFIs */ \ + printf_log(LOG_DEBUG, "Unknown CFI 0x%02X\n", inst); \ + FAILED \ + } \ + break; \ + } +#define RESTORE_REG(reg) \ + printf_log(LOG_DEBUG, "Trying to restore register 0x%02lX while in the intial CFIs\n", (uint64_t)reg); \ + FAILED +#define FAILED \ + free(unwind_constr.statuses); \ + free(unwind_constr.table); \ + for (uint64_t i = 0; i < maxstacksize; ++i) free(table_stack[i]); \ + free(table_stack); \ + *success = 0; return 0; + while (cur_inst < endCIEinstr) { + unsigned char inst; READ_U1(inst, cur_inst); + // printf_log(LOG_NONE, "Executing pre 0x%02X\n", inst); + PARSE_INST + } +#undef FAILED +#undef RESTORE_REG + uint64_t *init_table = (uint64_t*)malloc(tablelen); + memcpy(init_table, unwind_constr.table, tablelen); + uint8_t *init_statuses = (uint8_t*)malloc(statuseslen); + memcpy(init_statuses, unwind_constr.statuses, statuseslen); + cur_inst = cur_addr; +#define RESTORE_REG(reg) \ + unwind_constr.table[reg] = init_table[reg]; \ + SET_STATUS(unwind_constr, (reg), ((init_statuses[(reg) >> 1] >> (((reg) & 1) << 2)) & 0xF)); +#define FAILED \ + free(init_statuses); \ + free(init_table); \ + free(unwind_constr.statuses); \ + free(unwind_constr.table); \ + for (uint64_t i = 0; i < maxstacksize; ++i) free(table_stack[i]); \ + free(table_stack); \ + *success = 0; return 0; + while ((cur_inst < next_addr) && (cur_pointed_addr <= addr)) { + unsigned char inst; READ_U1(inst, cur_inst); + // printf_log(LOG_NONE, "Executing post 0x%02X\n", inst); + PARSE_INST + } +#undef FAILED +#undef RESTORE_REG +#undef PARSE_INST + free(init_statuses); + free(init_table); + for (int i = 0; i < maxstacksize; ++i) { + free(table_stack[i]); + } + free(table_stack); + + dwarf_unwind_t new_unwind; + new_unwind.reg_count = unwind_constr.reg_count; + new_unwind.regs = calloc(unwind_constr.reg_count, sizeof(uint64_t)); + uintptr_t cfa = unwind->regs[cfa_reg]; + if (cfa_signed) cfa += cfa_offset.soff; + else cfa += cfa_offset.uoff; + + // printf_log(LOG_NONE, "Done, rewriting registers (CFA at r%d(0x%016lX) + %lld (%c) -> 0x%016lX\n", cfa_reg, unwind->regs[cfa_reg], cfa_offset.soff, cfa_signed ? 's' : 'u', cfa); + for (uint64_t i = 0; i < unwind_constr.reg_count; ++i) { + switch (GET_STATUS(unwind_constr, i)) { + case REGSTATUS_undefined: + // printf_log(LOG_NONE, "Register %02lX: (undefined)\n", i); + break; + case REGSTATUS_same_val: + if (i >= unwind->reg_count) { + printf_log(LOG_DEBUG, "Invalid register status (value copied from register 0x%02lX)\n", i); + free(unwind_constr.statuses); + free(unwind_constr.table); + free(new_unwind.regs); + *success = 0; + return 0; + } + new_unwind.regs[i] = unwind->regs[i]; + // printf_log(LOG_NONE, "Register %02lX: copy %016lX\n", i, new_unwind.regs[i]); + break; + case REGSTATUS_offset: + new_unwind.regs[i] = *(uint64_t*)(cfa + (int64_t)unwind_constr.table[i]); + // printf_log(LOG_NONE, "Register %02lX: offset %016lX [%016lX + %lld]\n", i, new_unwind.regs[i], cfa, (int64_t)unwind_constr.table[i]); + break; + case REGSTATUS_val_offset: + new_unwind.regs[i] = (uint64_t)(cfa + (int64_t)unwind_constr.table[i]); + // printf_log(LOG_NONE, "Register %02lX: voff %016lX\n", i, new_unwind.regs[i]); + break; + case REGSTATUS_register: + if (unwind_constr.table[i] >= unwind->reg_count) { + printf_log(LOG_DEBUG, "Invalid register status (value copied from register 0x%02lX)\n", unwind_constr.table[i]); + free(unwind_constr.statuses); + free(unwind_constr.table); + free(new_unwind.regs); + *success = 0; + return 0; + } + new_unwind.regs[i] = unwind->regs[unwind_constr.table[i]]; + // printf_log(LOG_NONE, "Register %02lX: reg %016lX\n", i, new_unwind.regs[i]); + break; + } + } + *success = (GET_STATUS(unwind_constr, return_addr_reg) == REGSTATUS_undefined) ? 0 : ((aug_fields & AUG_SIGHDLER) ? 2 : 1); + free(unwind_constr.statuses); + free(unwind_constr.table); + + free(unwind->regs); + unwind->reg_count = new_unwind.reg_count; + unwind->regs = new_unwind.regs; + + // Maybe? + unwind->regs[cfa_reg] = cfa; + + // printf_log(LOG_NONE, "Returning %016lX\n", unwind->regs[return_addr_reg]); + return unwind->regs[return_addr_reg]; + } + } else { + // printf_log(LOG_NONE, "[FDE] Ptr %02X\n", cie_ptr); + printf_log(LOG_DEBUG, "Unexpected FDE, corresponding CIE missing\n"); + return 0; + } + + cur_addr = next_addr; + } + *success = 0; + return 0; +} + +dwarf_unwind_t *init_dwarf_unwind_registers(x64emu_t *emu) { + dwarf_unwind_t *unwind_struct = (dwarf_unwind_t*)malloc(sizeof(dwarf_unwind_t)); + unwind_struct->reg_count = 17; + unwind_struct->regs = (uint64_t*)malloc(17*sizeof(uint64_t)); + /* x86_64-abi-0.99.pdf + * Register Name | Number | Abbreviation + * General Purpose Register RAX | 0 | %rax + * General Purpose Register RDX | 1 | %rdx + * General Purpose Register RCX | 2 | %rcx + * General Purpose Register RBX | 3 | %rbx + * General Purpose Register RSI | 4 | %rsi + * General Purpose Register RDI | 5 | %rdi + * Frame Pointer Register RBP | 6 | %rbp + * Stack Pointer Register RSP | 7 | %rsp + * Extended Integer Registers 8-15 | 8-15 | %r8-%r15 + * Return Address RA | 16 | + * Vector Registers 0-7 | 17-24 | %xmm0-%xmm7 + * Extended Vector Registers 8-15 | 25-32 | %xmm8-%xmm15 + * Floating Point Registers 0-7 | 33-40 | %st0-%st7 + * MMX Registers 0-7 | 41-48 | %mm0-%mm7 + * Flag Register | 49 | %rFLAGS + * Segment Register ES | 50 | %es + * Segment Register CS | 51 | %cs + * Segment Register SS | 52 | %ss + * Segment Register DS | 53 | %ds + * Segment Register FS | 54 | %fs + * Segment Register GS | 55 | %gs + * Reserved | 56-57 | + * FS Base address | 58 | %fs.base + * GS Base address | 59 | %gs.base + * Reserved | 60-61 | + * Task Register | 62 | %tr + * LDT Register | 63 | %ldtr + * 128-bit Media Control and Status | 64 | %mxcsr + * x87 Control Word | 65 | %fcw + * x87 Status Word | 66 | %fsw + */ + unwind_struct->regs[ 0] = emu->regs[_RAX].q[0]; + unwind_struct->regs[ 1] = emu->regs[_RDX].q[0]; + unwind_struct->regs[ 2] = emu->regs[_RCX].q[0]; + unwind_struct->regs[ 3] = emu->regs[_RBX].q[0]; + unwind_struct->regs[ 4] = emu->regs[_RSI].q[0]; + unwind_struct->regs[ 5] = emu->regs[_RDI].q[0]; + unwind_struct->regs[ 6] = emu->regs[_RBP].q[0]; + unwind_struct->regs[ 7] = emu->regs[_RSP].q[0] + 8; + unwind_struct->regs[ 8] = emu->regs[_R8 ].q[0]; + unwind_struct->regs[ 9] = emu->regs[_R9 ].q[0]; + unwind_struct->regs[10] = emu->regs[_R10].q[0]; + unwind_struct->regs[11] = emu->regs[_R11].q[0]; + unwind_struct->regs[12] = emu->regs[_R12].q[0]; + unwind_struct->regs[13] = emu->regs[_R13].q[0]; + unwind_struct->regs[14] = emu->regs[_R14].q[0]; + unwind_struct->regs[15] = emu->regs[_R15].q[0]; + unwind_struct->regs[16] = emu->ip.q[0]; + return unwind_struct; +} + +void free_dwarf_unwind_registers(dwarf_unwind_t **unwind_struct) { + free((*unwind_struct)->regs); + free(*unwind_struct); + *unwind_struct = NULL; +} diff --git a/src/elfs/elfdwarf_private.h b/src/elfs/elfdwarf_private.h new file mode 100644 index 000000000..2a1c39b02 --- /dev/null +++ b/src/elfs/elfdwarf_private.h @@ -0,0 +1,19 @@ +#ifndef __ELFDWARF_PRIVATE_H_ +#define __ELFDWARF_PRIVATE_H_ + +#include "emu/x64emu_private.h" + +typedef struct dwarf_unwind_s { + uint8_t reg_count; + uint64_t *regs; +} dwarf_unwind_t; +typedef struct elfheader_s elfheader_t; + +dwarf_unwind_t *init_dwarf_unwind_registers(x64emu_t *emu); +void free_dwarf_unwind_registers(dwarf_unwind_t **unwind_struct); + +// Returns the callee's address on success or NULL on failure (may be NULL regardless). +// If success equals 2, the frame is a signal frame. +uintptr_t get_parent_registers(dwarf_unwind_t *emu, const elfheader_t *ehdr, uintptr_t addr, char *success); + +#endif // __ELFDWARF_PRIVATE_H_ diff --git a/src/elfs/elfloader_private.h b/src/elfs/elfloader_private.h index 18671d758..97319305c 100755 --- a/src/elfs/elfloader_private.h +++ b/src/elfs/elfloader_private.h @@ -9,6 +9,8 @@ typedef struct library_s library_t; typedef struct needed_libs_s needed_libs_t; #include +#include +#include "elfloader.h" struct elfheader_s { char* name; @@ -66,6 +68,9 @@ struct elfheader_s { uintptr_t plt_end; uintptr_t text; size_t textsz; + uintptr_t ehframe; + uintptr_t ehframe_end; + uintptr_t ehframehdr; uintptr_t paddr; uintptr_t vaddr; diff --git a/src/elfs/elfparser.c b/src/elfs/elfparser.c index 07599df61..c92d7e4c1 100755 --- a/src/elfs/elfparser.c +++ b/src/elfs/elfparser.c @@ -349,6 +349,17 @@ elfheader_t* ParseElfHeader(FILE* f, const char* name, int exec) h->textsz = h->SHEntries[ii].sh_size; printf_log(LOG_DEBUG, "The .text is at address %p, and is %zu big\n", (void*)h->text, h->textsz); } + ii = FindSection(h->SHEntries, h->numSHEntries, h->SHStrTab, ".eh_frame"); + if(ii) { + h->ehframe = (uintptr_t)(h->SHEntries[ii].sh_addr); + h->ehframe_end = h->ehframe + h->SHEntries[ii].sh_size; + printf_log(LOG_DEBUG, "The .eh_frame section is at address %p..%p\n", (void*)h->ehframe, (void*)h->ehframe_end); + } + ii = FindSection(h->SHEntries, h->numSHEntries, h->SHStrTab, ".eh_frame_hdr"); + if(ii) { + h->ehframehdr = (uintptr_t)(h->SHEntries[ii].sh_addr); + printf_log(LOG_DEBUG, "The .eh_frame_hdr section is at address %p\n", (void*)h->ehframehdr); + } LoadNamedSection(f, h->SHEntries, h->numSHEntries, h->SHStrTab, ".dynstr", "DynSym Strings", SHT_STRTAB, (void**)&h->DynStr, NULL); LoadNamedSection(f, h->SHEntries, h->numSHEntries, h->SHStrTab, ".dynsym", "DynSym", SHT_DYNSYM, (void**)&h->DynSym, &h->numDynSym); diff --git a/src/emu/x64emu.c b/src/emu/x64emu.c index 907c0acc5..d27fb5fdd 100755 --- a/src/emu/x64emu.c +++ b/src/emu/x64emu.c @@ -459,6 +459,8 @@ void EmuCall(x64emu_t* emu, uintptr_t addr) uint64_t old_rsi = R_RSI; uint64_t old_rbp = R_RBP; uint64_t old_rip = R_RIP; + Push64(emu, GetRBP(emu)); // set frame pointer + SetRBP(emu, GetRSP(emu)); // save RSP PushExit(emu); R_RIP = addr; emu->df = d_none; diff --git a/src/libtools/threads.c b/src/libtools/threads.c index 8f94692ad..f16a7b084 100755 --- a/src/libtools/threads.c +++ b/src/libtools/threads.c @@ -234,9 +234,10 @@ static void* pthread_routine(void* p) et->emu->type = EMUTYPE_MAIN; // setup callstack and run... x64emu_t* emu = et->emu; - Push64(emu, 0); // PUSH BP + Push64(emu, 0); // PUSH 0 (backtrace marker: return address is 0) + Push64(emu, 0); // PUSH BP R_RBP = R_RSP; // MOV BP, SP - R_RSP -= 56; // Gard zone + R_RSP -= 56; // Guard zone PushExit(emu); R_RIP = et->fnc; R_RDI = (uintptr_t)et->arg; diff --git a/src/wrapped/wrappedlibc.c b/src/wrapped/wrappedlibc.c index 81726f3f2..2537a68bf 100755 --- a/src/wrapped/wrappedlibc.c +++ b/src/wrapped/wrappedlibc.c @@ -2502,35 +2502,34 @@ EXPORT int my_semctl(int semid, int semnum, int cmd, union semun b) } // Backtrace stuff -typedef struct i386_layout_s -{ - struct i386_layout_s *next; - void *return_address; -} i386_layout_t; +#include "elfs/elfdwarf_private.h" EXPORT int my_backtrace(x64emu_t* emu, void** buffer, int size) { - // Get current Framepointer - i386_layout_t *fp = (i386_layout_t*)R_RBP; - if((uintptr_t)fp == (uintptr_t)buffer) // cannot find the FramePointer if it's not set in BP properly - return 0; - uintptr_t stack_end = (uintptr_t)(emu->init_stack) + emu->size_stack; - uintptr_t stack_start = (uintptr_t)(emu->init_stack); - // check if fp is on another stack (in case of beeing call from a signal with altstack) - x64emu_t *thread_emu = thread_get_emu(); - if(emu!=thread_emu && (((uintptr_t)fp>(uintptr_t)(thread_emu->init_stack)) && ((uintptr_t)fp<((uintptr_t)(thread_emu->init_stack) + thread_emu->size_stack)))) { - stack_end = (uintptr_t)(thread_emu->init_stack) + thread_emu->size_stack; - stack_start = (uintptr_t)(thread_emu->init_stack); - } - int idx=0; - while(idx=stack_end) || ((uintptr_t)fp<=stack_start)) { - return idx; - } - buffer[idx] = fp->return_address; - fp = fp->next; - ++idx; + if (!size) return 0; + dwarf_unwind_t *unwind = init_dwarf_unwind_registers(emu); + int idx = 0; + char success = 0; + uintptr_t addr = *(uintptr_t*)R_RSP; + buffer[0] = (void*)addr; + while (++idx < size) { + uintptr_t ret_addr = get_parent_registers(unwind, FindElfAddress(my_context, addr), addr, &success); + if (ret_addr == (uintptr_t)GetExit()) { + // TODO: do something to be able to get the function name + buffer[idx] = (void*)ret_addr; + success = 2; + // See elfdwarf_private.c for the register mapping + unwind->regs[6] = unwind->regs[7]; // mov rsp, rbp + unwind->regs[7] = *(uint64_t*)unwind->regs[6]; // pop rbp + unwind->regs[6] += 8; + ret_addr = *(uint64_t*)unwind->regs[6]; // ret + unwind->regs[6] += 8; + if (++idx < size) buffer[idx] = (void*)ret_addr; + } else if (!success) break; + else buffer[idx] = (void*)ret_addr; + addr = ret_addr; } + free_dwarf_unwind_registers(&unwind); return idx; } @@ -2538,16 +2537,21 @@ EXPORT char** my_backtrace_symbols(x64emu_t* emu, uintptr_t* buffer, int size) { (void)emu; char** ret = (char**)calloc(1, size*sizeof(char*) + size*100); // capping each strings to 100 chars - char* s = (char*)(ret+size*sizeof(char*)); + char* s = (char*)(ret+size); for (int i=0; i=start && (buffer[i]<(start+sz) || !sz)) - snprintf(s, 100, "%s+%ld [%p]\n", symbname, buffer[i] - start, (void*)buffer[i]); - else - snprintf(s, 100, "??? [%p]\n", (void*)buffer[i]); - s+=100; + elfheader_t *hdr = FindElfAddress(my_context, buffer[i]); + const char* symbname = FindNearestSymbolName(hdr, (void*)buffer[i], &start, &sz); + if (symbname && buffer[i]>=start && (buffer[i]<(start+sz) || !sz)) { + snprintf(s, 100, "%s(%s+%lx) [%p]", ElfName(hdr), symbname, buffer[i] - start, (void*)buffer[i]); + } else if (hdr) { + snprintf(s, 100, "%s+%lx [%p]", ElfName(hdr), buffer[i] - (uintptr_t)GetBaseAddress(hdr), (void*)buffer[i]); + } else { + snprintf(s, 100, "??? [%p]", (void*)buffer[i]); + } + ret[i] = s; + s += 100; } return ret; } diff --git a/tests/ref19.txt b/tests/ref19.txt new file mode 100644 index 000000000..856320006 --- /dev/null +++ b/tests/ref19.txt @@ -0,0 +1,5 @@ +backtrace() returned 4 addresses +:myfunc3 +:main +??? +:_start diff --git a/tests/test19 b/tests/test19 new file mode 100755 index 0000000000000000000000000000000000000000..c3ab30337f63b7dc5264047768ae19dd0fc111b9 GIT binary patch literal 24408 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 literal 0 HcmV?d00001 diff --git a/tests/test19.c b/tests/test19.c new file mode 100644 index 000000000..545f8f999 --- /dev/null +++ b/tests/test19.c @@ -0,0 +1,73 @@ +#include +#include +#include +#include +#include +/// build with `gcc -march=core2 -g -O2 test19.c -o test19 -no-pie -rdynamic` + +#define BT_BUF_SIZE 100 + +void myfunc3() +{ + int nptrs; + void *buffer[BT_BUF_SIZE]; + char **strings; + + nptrs = backtrace(buffer, BT_BUF_SIZE); + printf("backtrace() returned %d addresses\n", nptrs); + + /* The call backtrace_symbols_fd(buffer, nptrs, STDOUT_FILENO) + would produce similar output to the following: */ + + strings = backtrace_symbols(buffer, nptrs); + if (strings == NULL) { + perror("backtrace_symbols"); + exit(EXIT_FAILURE); + } + + for (int j = 0; j < nptrs; j++) { + // clean-up output so it can be compared + char* p = strchr(strings[j], '['); + if(p) + p[-1] = '\0'; + p = strchr(strings[j], '('); + if(p) + *p = ':'; + p = strchr(p?p:strings[j], '+'); + if(p) + *p = '\0'; + p = strchr(p?p:strings[j], ')'); + if(p) + *p = '\0'; + p = strchr(strings[j], ':'); + if(!p) + p = strings[j]; + printf("%s\n", p); + } + free(strings); +} + +static void /* "static" means don't export the symbol... */ +myfunc2(void) +{ + myfunc3(); +} + +void myfunc(int ncalls) +{ + if (ncalls > 1) + myfunc(ncalls - 1); + else + myfunc2(); +} + +int main(int argc, char *argv[]) +{ + int ncall = 4; + if (argc == 2) { + ncall = atoi(argv[1]); + } + + myfunc(ncall); + exit(EXIT_SUCCESS); +}