diff --git a/CMakeLists.txt b/CMakeLists.txt index 5d85da2..d2b8ea2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -203,25 +203,31 @@ target_sources( target_sources( ${target_name} PRIVATE # src + src/binary/elf.cpp + src/binary/mach-o.cpp + src/binary/module_base.cpp + src/binary/object.cpp + src/binary/pe.cpp + src/binary/safe_dl.cpp src/cpptrace.cpp src/ctrace.cpp src/demangle/demangle_with_cxxabi.cpp - src/demangle/demangle_with_winapi.cpp src/demangle/demangle_with_nothing.cpp + src/demangle/demangle_with_winapi.cpp + src/snippets/snippet.cpp + src/symbols/symbols_core.cpp src/symbols/symbols_with_addr2line.cpp src/symbols/symbols_with_dbghelp.cpp src/symbols/symbols_with_dl.cpp src/symbols/symbols_with_libbacktrace.cpp src/symbols/symbols_with_libdwarf.cpp src/symbols/symbols_with_nothing.cpp - src/symbols/symbols_core.cpp + src/unwind/unwind_with_dbghelp.cpp src/unwind/unwind_with_execinfo.cpp src/unwind/unwind_with_libunwind.cpp src/unwind/unwind_with_nothing.cpp src/unwind/unwind_with_unwind.cpp src/unwind/unwind_with_winapi.cpp - src/unwind/unwind_with_dbghelp.cpp - src/snippets/snippet.cpp ) target_include_directories( diff --git a/src/binary/elf.cpp b/src/binary/elf.cpp new file mode 100644 index 0000000..ea2c1e6 --- /dev/null +++ b/src/binary/elf.cpp @@ -0,0 +1,100 @@ +#include "elf.hpp" + +#if IS_LINUX + +#include +#include +#include +#include +#include + +#include + +namespace cpptrace { +namespace detail { + template::value, int>::type = 0> + T elf_byteswap_if_needed(T value, bool elf_is_little) { + if(is_little_endian() == elf_is_little) { + return value; + } else { + return byteswap(value); + } + } + + template + static Result elf_get_module_image_base_from_program_table( + const std::string& object_path, + std::FILE* file, + bool is_little_endian + ) { + static_assert(Bits == 32 || Bits == 64, "Unexpected Bits argument"); + using Header = typename std::conditional::type; + using PHeader = typename std::conditional::type; + auto loaded_header = load_bytes
(file, 0); + if(loaded_header.is_error()) { + return std::move(loaded_header).unwrap_error(); + } + const Header& file_header = loaded_header.unwrap_value(); + if(file_header.e_ehsize != sizeof(Header)) { + return internal_error("ELF file header size mismatch" + object_path); + } + // PT_PHDR will occur at most once + // Should be somewhat reliable https://stackoverflow.com/q/61568612/15675011 + // It should occur at the beginning but may as well loop just in case + for(int i = 0; i < file_header.e_phnum; i++) { + auto loaded_ph = load_bytes(file, file_header.e_phoff + file_header.e_phentsize * i); + if(loaded_ph.is_error()) { + return std::move(loaded_ph).unwrap_error(); + } + const PHeader& program_header = loaded_ph.unwrap_value(); + if(elf_byteswap_if_needed(program_header.p_type, is_little_endian) == PT_PHDR) { + return elf_byteswap_if_needed(program_header.p_vaddr, is_little_endian) - + elf_byteswap_if_needed(program_header.p_offset, is_little_endian); + } + } + // Apparently some objects like shared objects can end up missing this header. 0 as a base seems correct. + return 0; + } + + Result elf_get_module_image_base(const std::string& object_path) { + auto file = raii_wrap(std::fopen(object_path.c_str(), "rb"), file_deleter); + if(file == nullptr) { + return internal_error("Unable to read object file " + object_path); + } + // Initial checks/metadata + auto magic = load_bytes>(file, 0); + if(magic.is_error()) { + return std::move(magic).unwrap_error(); + } + if(magic.unwrap_value() != (std::array{0x7F, 'E', 'L', 'F'})) { + return internal_error("File is not ELF " + object_path); + } + auto ei_class = load_bytes(file, 4); + if(ei_class.is_error()) { + return std::move(ei_class).unwrap_error(); + } + bool is_64 = ei_class.unwrap_value() == 2; + auto ei_data = load_bytes(file, 5); + if(ei_data.is_error()) { + return std::move(ei_data).unwrap_error(); + } + bool is_little_endian = ei_data.unwrap_value() == 1; + auto ei_version = load_bytes(file, 6); + if(ei_version.is_error()) { + return std::move(ei_version).unwrap_error(); + } + if(ei_version.unwrap_value() != 1) { + return internal_error("Unexpected ELF version " + object_path); + } + // get image base + if(is_64) { + return elf_get_module_image_base_from_program_table<64>(object_path, file, is_little_endian); + } else { + return elf_get_module_image_base_from_program_table<32>(object_path, file, is_little_endian); + } + } + +} +} + +#endif diff --git a/src/binary/elf.hpp b/src/binary/elf.hpp index 54a7366..094cf56 100644 --- a/src/binary/elf.hpp +++ b/src/binary/elf.hpp @@ -5,97 +5,13 @@ #include "../utils/utils.hpp" #if IS_LINUX -#include -#include -#include -#include -#include -#include +#include +#include namespace cpptrace { namespace detail { - template::value, int>::type = 0> - T elf_byteswap_if_needed(T value, bool elf_is_little) { - if(is_little_endian() == elf_is_little) { - return value; - } else { - return byteswap(value); - } - } - - template - static Result elf_get_module_image_base_from_program_table( - const std::string& object_path, - std::FILE* file, - bool is_little_endian - ) { - static_assert(Bits == 32 || Bits == 64, "Unexpected Bits argument"); - using Header = typename std::conditional::type; - using PHeader = typename std::conditional::type; - auto loaded_header = load_bytes
(file, 0); - if(loaded_header.is_error()) { - return std::move(loaded_header).unwrap_error(); - } - const Header& file_header = loaded_header.unwrap_value(); - if(file_header.e_ehsize != sizeof(Header)) { - return internal_error("ELF file header size mismatch" + object_path); - } - // PT_PHDR will occur at most once - // Should be somewhat reliable https://stackoverflow.com/q/61568612/15675011 - // It should occur at the beginning but may as well loop just in case - for(int i = 0; i < file_header.e_phnum; i++) { - auto loaded_ph = load_bytes(file, file_header.e_phoff + file_header.e_phentsize * i); - if(loaded_ph.is_error()) { - return std::move(loaded_ph).unwrap_error(); - } - const PHeader& program_header = loaded_ph.unwrap_value(); - if(elf_byteswap_if_needed(program_header.p_type, is_little_endian) == PT_PHDR) { - return elf_byteswap_if_needed(program_header.p_vaddr, is_little_endian) - - elf_byteswap_if_needed(program_header.p_offset, is_little_endian); - } - } - // Apparently some objects like shared objects can end up missing this header. 0 as a base seems correct. - return 0; - } - - static Result elf_get_module_image_base(const std::string& object_path) { - auto file = raii_wrap(std::fopen(object_path.c_str(), "rb"), file_deleter); - if(file == nullptr) { - return internal_error("Unable to read object file " + object_path); - } - // Initial checks/metadata - auto magic = load_bytes>(file, 0); - if(magic.is_error()) { - return std::move(magic).unwrap_error(); - } - if(magic.unwrap_value() != (std::array{0x7F, 'E', 'L', 'F'})) { - return internal_error("File is not ELF " + object_path); - } - auto ei_class = load_bytes(file, 4); - if(ei_class.is_error()) { - return std::move(ei_class).unwrap_error(); - } - bool is_64 = ei_class.unwrap_value() == 2; - auto ei_data = load_bytes(file, 5); - if(ei_data.is_error()) { - return std::move(ei_data).unwrap_error(); - } - bool is_little_endian = ei_data.unwrap_value() == 1; - auto ei_version = load_bytes(file, 6); - if(ei_version.is_error()) { - return std::move(ei_version).unwrap_error(); - } - if(ei_version.unwrap_value() != 1) { - return internal_error("Unexpected ELF version " + object_path); - } - // get image base - if(is_64) { - return elf_get_module_image_base_from_program_table<64>(object_path, file, is_little_endian); - } else { - return elf_get_module_image_base_from_program_table<32>(object_path, file, is_little_endian); - } - } + Result elf_get_module_image_base(const std::string& object_path); } } diff --git a/src/binary/mach-o.cpp b/src/binary/mach-o.cpp new file mode 100644 index 0000000..941c385 --- /dev/null +++ b/src/binary/mach-o.cpp @@ -0,0 +1,641 @@ +#include "mach-o.hpp" + +#include "../utils/common.hpp" +#include "../utils/utils.hpp" + +#if IS_APPLE + +// A number of mach-o functions are deprecated as of macos 13 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace cpptrace { +namespace detail { + bool is_mach_o(std::uint32_t magic) { + switch(magic) { + case FAT_MAGIC: + case FAT_CIGAM: + case MH_MAGIC: + case MH_CIGAM: + case MH_MAGIC_64: + case MH_CIGAM_64: + return true; + default: + return false; + } + } + + bool file_is_mach_o(const std::string& object_path) noexcept { + auto file = raii_wrap(std::fopen(object_path.c_str(), "rb"), file_deleter); + if(file == nullptr) { + return false; + } + auto magic = load_bytes(file, 0); + if(magic) { + return is_mach_o(magic.unwrap_value()); + } else { + return false; + } + } + + bool is_fat_magic(std::uint32_t magic) { + return magic == FAT_MAGIC || magic == FAT_CIGAM; + } + + // Based on https://github.com/AlexDenisov/segment_dumper/blob/master/main.c + // and https://lowlevelbits.org/parsing-mach-o-files/ + bool is_magic_64(std::uint32_t magic) { + return magic == MH_MAGIC_64 || magic == MH_CIGAM_64; + } + + bool should_swap_bytes(std::uint32_t magic) { + return magic == MH_CIGAM || magic == MH_CIGAM_64 || magic == FAT_CIGAM; + } + + void swap_mach_header(mach_header_64& header) { + swap_mach_header_64(&header, NX_UnknownByteOrder); + } + + void swap_mach_header(mach_header& header) { + swap_mach_header(&header, NX_UnknownByteOrder); + } + + void swap_segment_command(segment_command_64& segment) { + swap_segment_command_64(&segment, NX_UnknownByteOrder); + } + + void swap_segment_command(segment_command& segment) { + swap_segment_command(&segment, NX_UnknownByteOrder); + } + + void swap_nlist(struct nlist& entry) { + swap_nlist(&entry, 1, NX_UnknownByteOrder); + } + + void swap_nlist(struct nlist_64& entry) { + swap_nlist_64(&entry, 1, NX_UnknownByteOrder); + } + + #ifdef __LP64__ + #define LP(x) x##_64 + #else + #define LP(x) x + #endif + + Result mach_o::symtab_info_data::get_string(std::size_t index) const { + if(stringtab && index < symtab.strsize) { + return stringtab.get() + index; + } else { + return internal_error("can't retrieve symbol from symtab"); + } + } + + Result mach_o::load() { + if(magic == FAT_MAGIC || magic == FAT_CIGAM) { + return load_fat_mach(); + } else { + fat_index = 0; + if(is_magic_64(magic)) { + return load_mach<64>(); + } else { + return load_mach<32>(); + } + } + } + + Result mach_o::open_mach_o(const std::string& object_path) { + auto file = raii_wrap(std::fopen(object_path.c_str(), "rb"), file_deleter); + if(file == nullptr) { + return internal_error("Unable to read object file {}", object_path); + } + auto magic = load_bytes(file, 0); + if(!magic) { + return magic.unwrap_error(); + } + if(!is_mach_o(magic.unwrap_value())) { + return internal_error("File is not mach-o {}", object_path); + } + mach_o obj(std::move(file), object_path, magic.unwrap_value()); + auto result = obj.load(); + if(result.is_error()) { + return result.unwrap_error(); + } else { + return obj; + } + } + + Result mach_o::get_text_vmaddr() { + for(const auto& command : load_commands) { + if(command.cmd == LC_SEGMENT_64 || command.cmd == LC_SEGMENT) { + auto segment = command.cmd == LC_SEGMENT_64 + ? load_segment_command<64>(command.file_offset) + : load_segment_command<32>(command.file_offset); + if(segment.is_error()) { + return std::move(segment).unwrap_error(); + } + if(std::strcmp(segment.unwrap_value().segname, "__TEXT") == 0) { + return segment.unwrap_value().vmaddr; + } + } + } + // somehow no __TEXT section was found... + return internal_error("Couldn't find __TEXT section while parsing Mach-O object"); + } + + std::size_t mach_o::get_fat_index() const { + VERIFY(fat_index != std::numeric_limits::max()); + return fat_index; + } + + void mach_o::print_segments() const { + int i = 0; + for(const auto& command : load_commands) { + if(command.cmd == LC_SEGMENT_64 || command.cmd == LC_SEGMENT) { + auto segment_load = command.cmd == LC_SEGMENT_64 + ? load_segment_command<64>(command.file_offset) + : load_segment_command<32>(command.file_offset); + fprintf(stderr, "Load command %d\n", i); + if(segment_load.is_error()) { + fprintf(stderr, " error\n"); + segment_load.drop_error(); + continue; + } + auto& segment = segment_load.unwrap_value(); + fprintf(stderr, " cmd %u\n", segment.cmd); + fprintf(stderr, " cmdsize %u\n", segment.cmdsize); + fprintf(stderr, " segname %s\n", segment.segname); + fprintf(stderr, " vmaddr 0x%llx\n", segment.vmaddr); + fprintf(stderr, " vmsize 0x%llx\n", segment.vmsize); + fprintf(stderr, " off 0x%llx\n", segment.fileoff); + fprintf(stderr, " filesize %llu\n", segment.filesize); + fprintf(stderr, " nsects %u\n", segment.nsects); + } + i++; + } + } + + Result>, internal_error> mach_o::get_symtab_info() { + if(!symtab_info.has_value() && !tried_to_load_symtab) { + // don't try to load the symtab again if for some reason loading here fails + tried_to_load_symtab = true; + for(const auto& command : load_commands) { + if(command.cmd == LC_SYMTAB) { + symtab_info_data info; + auto symtab = load_symbol_table_command(command.file_offset); + if(!symtab) { + return std::move(symtab).unwrap_error(); + } + info.symtab = symtab.unwrap_value(); + auto string = load_string_table(info.symtab.stroff, info.symtab.strsize); + if(!string) { + return std::move(string).unwrap_error(); + } + info.stringtab = std::move(string).unwrap_value(); + symtab_info = std::move(info); + break; + } + } + } + return std::reference_wrapper>{symtab_info}; + } + + void mach_o::print_symbol_table_entry( + const nlist_64& entry, + const std::unique_ptr& stringtab, + std::size_t stringsize, + std::size_t j + ) const { + const char* type = ""; + if(entry.n_type & N_STAB) { + switch(entry.n_type) { + case N_SO: type = "N_SO"; break; + case N_OSO: type = "N_OSO"; break; + case N_BNSYM: type = "N_BNSYM"; break; + case N_ENSYM: type = "N_ENSYM"; break; + case N_FUN: type = "N_FUN"; break; + } + } else if((entry.n_type & N_TYPE) == N_SECT) { + type = "N_SECT"; + } + fprintf( + stderr, + "%5llu %8llx %2llx %7s %2llu %4llx %16llx %s\n", + to_ull(j), + to_ull(entry.n_un.n_strx), + to_ull(entry.n_type), + type, + to_ull(entry.n_sect), + to_ull(entry.n_desc), + to_ull(entry.n_value), + stringtab == nullptr + ? "Stringtab error" + : entry.n_un.n_strx < stringsize + ? stringtab.get() + entry.n_un.n_strx + : "String index out of bounds" + ); + } + + void mach_o::print_symbol_table() { + int i = 0; + for(const auto& command : load_commands) { + if(command.cmd == LC_SYMTAB) { + auto symtab_load = load_symbol_table_command(command.file_offset); + fprintf(stderr, "Load command %d\n", i); + if(symtab_load.is_error()) { + fprintf(stderr, " error\n"); + symtab_load.drop_error(); + continue; + } + auto& symtab = symtab_load.unwrap_value(); + fprintf(stderr, " cmd %llu\n", to_ull(symtab.cmd)); + fprintf(stderr, " cmdsize %llu\n", to_ull(symtab.cmdsize)); + fprintf(stderr, " symoff 0x%llu\n", to_ull(symtab.symoff)); + fprintf(stderr, " nsyms %llu\n", to_ull(symtab.nsyms)); + fprintf(stderr, " stroff 0x%llu\n", to_ull(symtab.stroff)); + fprintf(stderr, " strsize %llu\n", to_ull(symtab.strsize)); + auto stringtab = load_string_table(symtab.stroff, symtab.strsize); + if(!stringtab) { + stringtab.drop_error(); + } + for(std::size_t j = 0; j < symtab.nsyms; j++) { + auto entry = bits == 32 + ? load_symtab_entry<32>(symtab.symoff, j) + : load_symtab_entry<64>(symtab.symoff, j); + if(!entry) { + fprintf(stderr, "error loading symtab entry\n"); + entry.drop_error(); + continue; + } + print_symbol_table_entry( + entry.unwrap_value(), + std::move(stringtab).value_or(std::unique_ptr(nullptr)), + symtab.strsize, + j + ); + } + } + i++; + } + } + + // produce information similar to dsymutil -dump-debug-map + Result mach_o::get_debug_map() { + // we have a bunch of symbols in our binary we need to pair up with symbols from various .o files + // first collect symbols and the objects they come from + debug_map debug_map; + auto symtab_info_res = get_symtab_info(); + if(!symtab_info_res) { + return std::move(symtab_info_res).unwrap_error(); + } + if(!symtab_info_res.unwrap_value().get()) { + return internal_error("No symtab info"); + } + const auto& symtab_info = symtab_info_res.unwrap_value().get().unwrap(); + const auto& symtab = symtab_info.symtab; + // TODO: Take timestamp into account? + std::string current_module; + optional current_function; + for(std::size_t j = 0; j < symtab.nsyms; j++) { + auto load_entry = bits == 32 + ? load_symtab_entry<32>(symtab.symoff, j) + : load_symtab_entry<64>(symtab.symoff, j); + if(!load_entry) { + return std::move(load_entry).unwrap_error(); + } + auto& entry = load_entry.unwrap_value(); + // entry.n_type & N_STAB indicates symbolic debug info + if(!(entry.n_type & N_STAB)) { + continue; + } + switch(entry.n_type) { + case N_SO: + // pass - these encode path and filename for the module, if applicable + break; + case N_OSO: + { + // sets the module + auto str = symtab_info.get_string(entry.n_un.n_strx); + if(!str) { + return std::move(str).unwrap_error(); + } + current_module = str.unwrap_value(); + } + break; + case N_BNSYM: break; // pass + case N_ENSYM: break; // pass + case N_FUN: + { + auto str = symtab_info.get_string(entry.n_un.n_strx); + if(!str) { + return std::move(str).unwrap_error(); + } + if(str.unwrap_value()[0] == 0) { + // end of function scope + if(!current_function) { /**/ } + current_function.unwrap().size = entry.n_value; + debug_map[current_module].push_back(std::move(current_function).unwrap()); + } else { + current_function = debug_map_entry{}; + current_function.unwrap().source_address = entry.n_value; + current_function.unwrap().name = str.unwrap_value(); + } + } + break; + } + } + return debug_map; + } + + Result, internal_error> mach_o::symbol_table() { + // we have a bunch of symbols in our binary we need to pair up with symbols from various .o files + // first collect symbols and the objects they come from + std::vector symbols; + auto symtab_info_res = get_symtab_info(); + if(!symtab_info_res) { + return std::move(symtab_info_res).unwrap_error(); + } + if(!symtab_info_res.unwrap_value().get()) { + return internal_error("No symtab info"); + } + const auto& symtab_info = symtab_info_res.unwrap_value().get().unwrap(); + const auto& symtab = symtab_info.symtab; + // TODO: Take timestamp into account? + for(std::size_t j = 0; j < symtab.nsyms; j++) { + auto load_entry = bits == 32 + ? load_symtab_entry<32>(symtab.symoff, j) + : load_symtab_entry<64>(symtab.symoff, j); + if(!load_entry) { + return std::move(load_entry).unwrap_error(); + } + auto& entry = load_entry.unwrap_value(); + if(entry.n_type & N_STAB) { + continue; + } + if((entry.n_type & N_TYPE) == N_SECT) { + auto str = symtab_info.get_string(entry.n_un.n_strx); + if(!str) { + return std::move(str).unwrap_error(); + } + symbols.push_back({ + entry.n_value, + str.unwrap_value() + }); + } + } + return symbols; + } + + // produce information similar to dsymutil -dump-debug-map + void mach_o::print_debug_map(const debug_map& debug_map) { + for(const auto& entry : debug_map) { + std::cout< + Result mach_o::load_mach() { + static_assert(Bits == 32 || Bits == 64, "Unexpected Bits argument"); + bits = Bits; + using Mach_Header = typename std::conditional::type; + std::size_t header_size = sizeof(Mach_Header); + auto load_header = load_bytes(file, load_base); + if(!load_header) { + return load_header.unwrap_error(); + } + Mach_Header& header = load_header.unwrap_value(); + magic = header.magic; + if(should_swap()) { + swap_mach_header(header); + } + cputype = header.cputype; + cpusubtype = header.cpusubtype; + filetype = header.filetype; + n_load_commands = header.ncmds; + sizeof_load_commands = header.sizeofcmds; + flags = header.flags; + // handle load commands + std::uint32_t ncmds = header.ncmds; + std::uint32_t load_commands_offset = load_base + header_size; + // iterate load commands + std::uint32_t actual_offset = load_commands_offset; + for(std::uint32_t i = 0; i < ncmds; i++) { + auto load_cmd = load_bytes(file, actual_offset); + if(!load_cmd) { + return load_cmd.unwrap_error(); + } + load_command& cmd = load_cmd.unwrap_value(); + if(should_swap()) { + swap_load_command(&cmd, NX_UnknownByteOrder); + } + load_commands.push_back({ actual_offset, cmd.cmd, cmd.cmdsize }); + actual_offset += cmd.cmdsize; + } + return monostate{}; + } + + Result mach_o::load_fat_mach() { + std::size_t header_size = sizeof(fat_header); + std::size_t arch_size = sizeof(fat_arch); + auto load_header = load_bytes(file, 0); + if(!load_header) { + return load_header.unwrap_error(); + } + fat_header& header = load_header.unwrap_value(); + if(should_swap()) { + swap_fat_header(&header, NX_UnknownByteOrder); + } + // thread_local static struct LP(mach_header)* mhp = _NSGetMachExecuteHeader(); + // off_t arch_offset = (off_t)header_size; + // for(std::size_t i = 0; i < header.nfat_arch; i++) { + // fat_arch arch = load_bytes(file, arch_offset); + // if(should_swap()) { + // swap_fat_arch(&arch, 1, NX_UnknownByteOrder); + // } + // off_t mach_header_offset = (off_t)arch.offset; + // arch_offset += arch_size; + // std::uint32_t magic = load_bytes(file, mach_header_offset); + // std::cerr<<"xxx: "<cputype<(mhp->cpusubtype & ~CPU_SUBTYPE_MASK)<cputype && + // static_cast(mhp->cpusubtype & ~CPU_SUBTYPE_MASK) == arch.cpusubtype + // ) { + // load_base = mach_header_offset; + // fat_index = i; + // if(is_magic_64(magic)) { + // load_mach<64>(true); + // } else { + // load_mach<32>(true); + // } + // return; + // } + // } + std::vector fat_arches; + fat_arches.reserve(header.nfat_arch); + off_t arch_offset = (off_t)header_size; + for(std::size_t i = 0; i < header.nfat_arch; i++) { + auto load_arch = load_bytes(file, arch_offset); + if(!load_arch) { + return load_arch.unwrap_error(); + } + fat_arch& arch = load_arch.unwrap_value(); + if(should_swap()) { + swap_fat_arch(&arch, 1, NX_UnknownByteOrder); + } + fat_arches.push_back(arch); + arch_offset += arch_size; + } + thread_local static struct LP(mach_header)* mhp = _NSGetMachExecuteHeader(); + fat_arch* best = NXFindBestFatArch( + mhp->cputype, + mhp->cpusubtype, + fat_arches.data(), + header.nfat_arch + ); + if(best) { + off_t mach_header_offset = (off_t)best->offset; + auto magic = load_bytes(file, mach_header_offset); + if(!magic) { + return magic.unwrap_error(); + } + load_base = mach_header_offset; + fat_index = best - fat_arches.data(); + if(is_magic_64(magic.unwrap_value())) { + load_mach<64>(); + } else { + load_mach<32>(); + } + return monostate{}; + } + // If this is reached... something went wrong. The cpu we're on wasn't found. + return internal_error("Couldn't find appropriate architecture in fat Mach-O"); + } + + template + Result mach_o::load_segment_command(std::uint32_t offset) const { + using Segment_Command = typename std::conditional::type; + auto load_segment = load_bytes(file, offset); + if(!load_segment) { + return load_segment.unwrap_error(); + } + Segment_Command& segment = load_segment.unwrap_value(); + ASSERT(segment.cmd == LC_SEGMENT_64 || segment.cmd == LC_SEGMENT); + if(should_swap()) { + swap_segment_command(segment); + } + // fields match just u64 instead of u32 + segment_command_64 common; + common.cmd = segment.cmd; + common.cmdsize = segment.cmdsize; + static_assert(sizeof common.segname == 16 && sizeof segment.segname == 16, "xx"); + memcpy(common.segname, segment.segname, 16); + common.vmaddr = segment.vmaddr; + common.vmsize = segment.vmsize; + common.fileoff = segment.fileoff; + common.filesize = segment.filesize; + common.maxprot = segment.maxprot; + common.initprot = segment.initprot; + common.nsects = segment.nsects; + common.flags = segment.flags; + return common; + } + + Result mach_o::load_symbol_table_command(std::uint32_t offset) const { + auto load_symtab = load_bytes(file, offset); + if(!load_symtab) { + return load_symtab.unwrap_error(); + } + symtab_command& symtab = load_symtab.unwrap_value(); + ASSERT(symtab.cmd == LC_SYMTAB); + if(should_swap()) { + swap_symtab_command(&symtab, NX_UnknownByteOrder); + } + return symtab; + } + + template + Result mach_o::load_symtab_entry(std::uint32_t symbol_base, std::size_t index) const { + using Nlist = typename std::conditional::type; + uint32_t offset = load_base + symbol_base + index * sizeof(Nlist); + auto load_entry = load_bytes(file, offset); + if(!load_entry) { + return load_entry.unwrap_error(); + } + Nlist& entry = load_entry.unwrap_value(); + if(should_swap()) { + swap_nlist(entry); + } + // fields match just u64 instead of u32 + nlist_64 common; + common.n_un.n_strx = entry.n_un.n_strx; + common.n_type = entry.n_type; + common.n_sect = entry.n_sect; + common.n_desc = entry.n_desc; + common.n_value = entry.n_value; + return common; + } + + Result, internal_error> mach_o::load_string_table(std::uint32_t offset, std::uint32_t byte_count) const { + std::unique_ptr buffer(new char[byte_count + 1]); + if(std::fseek(file, load_base + offset, SEEK_SET) != 0) { + return internal_error("fseek error while loading mach-o symbol table"); + } + if(std::fread(buffer.get(), sizeof(char), byte_count, file) != byte_count) { + return internal_error("fread error while loading mach-o symbol table"); + } + buffer[byte_count] = 0; // just out of an abundance of caution + return buffer; + } + + bool mach_o::should_swap() const { + return should_swap_bytes(magic); + } + + Result macho_is_fat(const std::string& object_path) { + auto file = raii_wrap(std::fopen(object_path.c_str(), "rb"), file_deleter); + if(file == nullptr) { + return internal_error("Unable to read object file {}", object_path); + } + auto magic = load_bytes(file, 0); + if(!magic) { + return magic.unwrap_error(); + } else { + return is_fat_magic(magic.unwrap_value()); + } + } +} +} + +#pragma GCC diagnostic pop + +#endif diff --git a/src/binary/mach-o.hpp b/src/binary/mach-o.hpp index a872f18..7705ea4 100644 --- a/src/binary/mach-o.hpp +++ b/src/binary/mach-o.hpp @@ -6,100 +6,20 @@ #if IS_APPLE -// A number of mach-o functions are deprecated as of macos 13 -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" - -#include -#include +#include +#include +#include #include -#include #include #include -#include -#include - -#include -#include -#include -#include -#include -#include #include +#include +#include namespace cpptrace { namespace detail { - inline bool is_mach_o(std::uint32_t magic) { - switch(magic) { - case FAT_MAGIC: - case FAT_CIGAM: - case MH_MAGIC: - case MH_CIGAM: - case MH_MAGIC_64: - case MH_CIGAM_64: - return true; - default: - return false; - } - } - - inline bool file_is_mach_o(const std::string& object_path) noexcept { - auto file = raii_wrap(std::fopen(object_path.c_str(), "rb"), file_deleter); - if(file == nullptr) { - return false; - } - auto magic = load_bytes(file, 0); - if(magic) { - return is_mach_o(magic.unwrap_value()); - } else { - return false; - } - } - - inline bool is_fat_magic(std::uint32_t magic) { - return magic == FAT_MAGIC || magic == FAT_CIGAM; - } - - // Based on https://github.com/AlexDenisov/segment_dumper/blob/master/main.c - // and https://lowlevelbits.org/parsing-mach-o-files/ - inline bool is_magic_64(std::uint32_t magic) { - return magic == MH_MAGIC_64 || magic == MH_CIGAM_64; - } - - inline bool should_swap_bytes(std::uint32_t magic) { - return magic == MH_CIGAM || magic == MH_CIGAM_64 || magic == FAT_CIGAM; - } - - inline void swap_mach_header(mach_header_64& header) { - swap_mach_header_64(&header, NX_UnknownByteOrder); - } - - inline void swap_mach_header(mach_header& header) { - swap_mach_header(&header, NX_UnknownByteOrder); - } - - inline void swap_segment_command(segment_command_64& segment) { - swap_segment_command_64(&segment, NX_UnknownByteOrder); - } - - inline void swap_segment_command(segment_command& segment) { - swap_segment_command(&segment, NX_UnknownByteOrder); - } - - inline void swap_nlist(struct nlist& entry) { - swap_nlist(&entry, 1, NX_UnknownByteOrder); - } - - inline void swap_nlist(struct nlist_64& entry) { - swap_nlist_64(&entry, 1, NX_UnknownByteOrder); - } - - #ifdef __LP64__ - #define LP(x) x##_64 - #else - #define LP(x) x - #endif + bool file_is_mach_o(const std::string& object_path) noexcept; struct load_command_entry { std::uint32_t file_offset; @@ -127,13 +47,7 @@ namespace detail { struct symtab_info_data { symtab_command symtab; std::unique_ptr stringtab; - Result get_string(std::size_t index) const { - if(stringtab && index < symtab.strsize) { - return stringtab.get() + index; - } else { - return internal_error("can't retrieve symbol from symtab"); - } - } + Result get_string(std::size_t index) const; }; bool tried_to_load_symtab = false; @@ -148,197 +62,30 @@ namespace detail { object_path(object_path), magic(magic) {} - Result load() { - if(magic == FAT_MAGIC || magic == FAT_CIGAM) { - return load_fat_mach(); - } else { - fat_index = 0; - if(is_magic_64(magic)) { - return load_mach<64>(); - } else { - return load_mach<32>(); - } - } - } + Result load(); public: - static inline NODISCARD Result open_mach_o(const std::string& object_path) { - auto file = raii_wrap(std::fopen(object_path.c_str(), "rb"), file_deleter); - if(file == nullptr) { - return internal_error("Unable to read object file {}", object_path); - } - auto magic = load_bytes(file, 0); - if(!magic) { - return magic.unwrap_error(); - } - if(!is_mach_o(magic.unwrap_value())) { - return internal_error("File is not mach-o {}", object_path); - } - mach_o obj(std::move(file), object_path, magic.unwrap_value()); - auto result = obj.load(); - if(result.is_error()) { - return result.unwrap_error(); - } else { - return obj; - } - } + static NODISCARD Result open_mach_o(const std::string& object_path); mach_o(mach_o&&) = default; ~mach_o() = default; - Result get_text_vmaddr() { - for(const auto& command : load_commands) { - if(command.cmd == LC_SEGMENT_64 || command.cmd == LC_SEGMENT) { - auto segment = command.cmd == LC_SEGMENT_64 - ? load_segment_command<64>(command.file_offset) - : load_segment_command<32>(command.file_offset); - if(segment.is_error()) { - return std::move(segment).unwrap_error(); - } - if(std::strcmp(segment.unwrap_value().segname, "__TEXT") == 0) { - return segment.unwrap_value().vmaddr; - } - } - } - // somehow no __TEXT section was found... - return internal_error("Couldn't find __TEXT section while parsing Mach-O object"); - } + Result get_text_vmaddr(); - std::size_t get_fat_index() const { - VERIFY(fat_index != std::numeric_limits::max()); - return fat_index; - } + std::size_t get_fat_index() const; - void print_segments() const { - int i = 0; - for(const auto& command : load_commands) { - if(command.cmd == LC_SEGMENT_64 || command.cmd == LC_SEGMENT) { - auto segment_load = command.cmd == LC_SEGMENT_64 - ? load_segment_command<64>(command.file_offset) - : load_segment_command<32>(command.file_offset); - fprintf(stderr, "Load command %d\n", i); - if(segment_load.is_error()) { - fprintf(stderr, " error\n"); - segment_load.drop_error(); - continue; - } - auto& segment = segment_load.unwrap_value(); - fprintf(stderr, " cmd %u\n", segment.cmd); - fprintf(stderr, " cmdsize %u\n", segment.cmdsize); - fprintf(stderr, " segname %s\n", segment.segname); - fprintf(stderr, " vmaddr 0x%llx\n", segment.vmaddr); - fprintf(stderr, " vmsize 0x%llx\n", segment.vmsize); - fprintf(stderr, " off 0x%llx\n", segment.fileoff); - fprintf(stderr, " filesize %llu\n", segment.filesize); - fprintf(stderr, " nsects %u\n", segment.nsects); - } - i++; - } - } + void print_segments() const; - Result>, internal_error> get_symtab_info() { - if(!symtab_info.has_value() && !tried_to_load_symtab) { - // don't try to load the symtab again if for some reason loading here fails - tried_to_load_symtab = true; - for(const auto& command : load_commands) { - if(command.cmd == LC_SYMTAB) { - symtab_info_data info; - auto symtab = load_symbol_table_command(command.file_offset); - if(!symtab) { - return std::move(symtab).unwrap_error(); - } - info.symtab = symtab.unwrap_value(); - auto string = load_string_table(info.symtab.stroff, info.symtab.strsize); - if(!string) { - return std::move(string).unwrap_error(); - } - info.stringtab = std::move(string).unwrap_value(); - symtab_info = std::move(info); - break; - } - } - } - return std::reference_wrapper>{symtab_info}; - } + Result>, internal_error> get_symtab_info(); void print_symbol_table_entry( const nlist_64& entry, const std::unique_ptr& stringtab, std::size_t stringsize, std::size_t j - ) const { - const char* type = ""; - if(entry.n_type & N_STAB) { - switch(entry.n_type) { - case N_SO: type = "N_SO"; break; - case N_OSO: type = "N_OSO"; break; - case N_BNSYM: type = "N_BNSYM"; break; - case N_ENSYM: type = "N_ENSYM"; break; - case N_FUN: type = "N_FUN"; break; - } - } else if((entry.n_type & N_TYPE) == N_SECT) { - type = "N_SECT"; - } - fprintf( - stderr, - "%5llu %8llx %2llx %7s %2llu %4llx %16llx %s\n", - to_ull(j), - to_ull(entry.n_un.n_strx), - to_ull(entry.n_type), - type, - to_ull(entry.n_sect), - to_ull(entry.n_desc), - to_ull(entry.n_value), - stringtab == nullptr - ? "Stringtab error" - : entry.n_un.n_strx < stringsize - ? stringtab.get() + entry.n_un.n_strx - : "String index out of bounds" - ); - } + ) const; - void print_symbol_table() { - int i = 0; - for(const auto& command : load_commands) { - if(command.cmd == LC_SYMTAB) { - auto symtab_load = load_symbol_table_command(command.file_offset); - fprintf(stderr, "Load command %d\n", i); - if(symtab_load.is_error()) { - fprintf(stderr, " error\n"); - symtab_load.drop_error(); - continue; - } - auto& symtab = symtab_load.unwrap_value(); - fprintf(stderr, " cmd %llu\n", to_ull(symtab.cmd)); - fprintf(stderr, " cmdsize %llu\n", to_ull(symtab.cmdsize)); - fprintf(stderr, " symoff 0x%llu\n", to_ull(symtab.symoff)); - fprintf(stderr, " nsyms %llu\n", to_ull(symtab.nsyms)); - fprintf(stderr, " stroff 0x%llu\n", to_ull(symtab.stroff)); - fprintf(stderr, " strsize %llu\n", to_ull(symtab.strsize)); - auto stringtab = load_string_table(symtab.stroff, symtab.strsize); - if(!stringtab) { - stringtab.drop_error(); - } - for(std::size_t j = 0; j < symtab.nsyms; j++) { - auto entry = bits == 32 - ? load_symtab_entry<32>(symtab.symoff, j) - : load_symtab_entry<64>(symtab.symoff, j); - if(!entry) { - fprintf(stderr, "error loading symtab entry\n"); - entry.drop_error(); - continue; - } - print_symbol_table_entry( - entry.unwrap_value(), - std::move(stringtab).value_or(std::unique_ptr(nullptr)), - symtab.strsize, - j - ); - } - } - i++; - } - } + void print_symbol_table(); struct debug_map_entry { uint64_t source_address; @@ -355,349 +102,36 @@ namespace detail { using debug_map = std::unordered_map>; // produce information similar to dsymutil -dump-debug-map - Result get_debug_map() { - // we have a bunch of symbols in our binary we need to pair up with symbols from various .o files - // first collect symbols and the objects they come from - debug_map debug_map; - auto symtab_info_res = get_symtab_info(); - if(!symtab_info_res) { - return std::move(symtab_info_res).unwrap_error(); - } - if(!symtab_info_res.unwrap_value().get()) { - return internal_error("No symtab info"); - } - const auto& symtab_info = symtab_info_res.unwrap_value().get().unwrap(); - const auto& symtab = symtab_info.symtab; - // TODO: Take timestamp into account? - std::string current_module; - optional current_function; - for(std::size_t j = 0; j < symtab.nsyms; j++) { - auto load_entry = bits == 32 - ? load_symtab_entry<32>(symtab.symoff, j) - : load_symtab_entry<64>(symtab.symoff, j); - if(!load_entry) { - return std::move(load_entry).unwrap_error(); - } - auto& entry = load_entry.unwrap_value(); - // entry.n_type & N_STAB indicates symbolic debug info - if(!(entry.n_type & N_STAB)) { - continue; - } - switch(entry.n_type) { - case N_SO: - // pass - these encode path and filename for the module, if applicable - break; - case N_OSO: - { - // sets the module - auto str = symtab_info.get_string(entry.n_un.n_strx); - if(!str) { - return std::move(str).unwrap_error(); - } - current_module = str.unwrap_value(); - } - break; - case N_BNSYM: break; // pass - case N_ENSYM: break; // pass - case N_FUN: - { - auto str = symtab_info.get_string(entry.n_un.n_strx); - if(!str) { - return std::move(str).unwrap_error(); - } - if(str.unwrap_value()[0] == 0) { - // end of function scope - if(!current_function) { /**/ } - current_function.unwrap().size = entry.n_value; - debug_map[current_module].push_back(std::move(current_function).unwrap()); - } else { - current_function = debug_map_entry{}; - current_function.unwrap().source_address = entry.n_value; - current_function.unwrap().name = str.unwrap_value(); - } - } - break; - } - } - return debug_map; - } + Result get_debug_map(); - Result, internal_error> symbol_table() { - // we have a bunch of symbols in our binary we need to pair up with symbols from various .o files - // first collect symbols and the objects they come from - std::vector symbols; - auto symtab_info_res = get_symtab_info(); - if(!symtab_info_res) { - return std::move(symtab_info_res).unwrap_error(); - } - if(!symtab_info_res.unwrap_value().get()) { - return internal_error("No symtab info"); - } - const auto& symtab_info = symtab_info_res.unwrap_value().get().unwrap(); - const auto& symtab = symtab_info.symtab; - // TODO: Take timestamp into account? - for(std::size_t j = 0; j < symtab.nsyms; j++) { - auto load_entry = bits == 32 - ? load_symtab_entry<32>(symtab.symoff, j) - : load_symtab_entry<64>(symtab.symoff, j); - if(!load_entry) { - return std::move(load_entry).unwrap_error(); - } - auto& entry = load_entry.unwrap_value(); - if(entry.n_type & N_STAB) { - continue; - } - if((entry.n_type & N_TYPE) == N_SECT) { - auto str = symtab_info.get_string(entry.n_un.n_strx); - if(!str) { - return std::move(str).unwrap_error(); - } - symbols.push_back({ - entry.n_value, - str.unwrap_value() - }); - } - } - return symbols; - } + Result, internal_error> symbol_table(); // produce information similar to dsymutil -dump-debug-map - static void print_debug_map(const debug_map& debug_map) { - for(const auto& entry : debug_map) { - std::cout< - Result load_mach() { - static_assert(Bits == 32 || Bits == 64, "Unexpected Bits argument"); - bits = Bits; - using Mach_Header = typename std::conditional::type; - std::size_t header_size = sizeof(Mach_Header); - auto load_header = load_bytes(file, load_base); - if(!load_header) { - return load_header.unwrap_error(); - } - Mach_Header& header = load_header.unwrap_value(); - magic = header.magic; - if(should_swap()) { - swap_mach_header(header); - } - cputype = header.cputype; - cpusubtype = header.cpusubtype; - filetype = header.filetype; - n_load_commands = header.ncmds; - sizeof_load_commands = header.sizeofcmds; - flags = header.flags; - // handle load commands - std::uint32_t ncmds = header.ncmds; - std::uint32_t load_commands_offset = load_base + header_size; - // iterate load commands - std::uint32_t actual_offset = load_commands_offset; - for(std::uint32_t i = 0; i < ncmds; i++) { - auto load_cmd = load_bytes(file, actual_offset); - if(!load_cmd) { - return load_cmd.unwrap_error(); - } - load_command& cmd = load_cmd.unwrap_value(); - if(should_swap()) { - swap_load_command(&cmd, NX_UnknownByteOrder); - } - load_commands.push_back({ actual_offset, cmd.cmd, cmd.cmdsize }); - actual_offset += cmd.cmdsize; - } - return monostate{}; - } + Result load_mach(); - Result load_fat_mach() { - std::size_t header_size = sizeof(fat_header); - std::size_t arch_size = sizeof(fat_arch); - auto load_header = load_bytes(file, 0); - if(!load_header) { - return load_header.unwrap_error(); - } - fat_header& header = load_header.unwrap_value(); - if(should_swap()) { - swap_fat_header(&header, NX_UnknownByteOrder); - } - // thread_local static struct LP(mach_header)* mhp = _NSGetMachExecuteHeader(); - // off_t arch_offset = (off_t)header_size; - // for(std::size_t i = 0; i < header.nfat_arch; i++) { - // fat_arch arch = load_bytes(file, arch_offset); - // if(should_swap()) { - // swap_fat_arch(&arch, 1, NX_UnknownByteOrder); - // } - // off_t mach_header_offset = (off_t)arch.offset; - // arch_offset += arch_size; - // std::uint32_t magic = load_bytes(file, mach_header_offset); - // std::cerr<<"xxx: "<cputype<(mhp->cpusubtype & ~CPU_SUBTYPE_MASK)<cputype && - // static_cast(mhp->cpusubtype & ~CPU_SUBTYPE_MASK) == arch.cpusubtype - // ) { - // load_base = mach_header_offset; - // fat_index = i; - // if(is_magic_64(magic)) { - // load_mach<64>(true); - // } else { - // load_mach<32>(true); - // } - // return; - // } - // } - std::vector fat_arches; - fat_arches.reserve(header.nfat_arch); - off_t arch_offset = (off_t)header_size; - for(std::size_t i = 0; i < header.nfat_arch; i++) { - auto load_arch = load_bytes(file, arch_offset); - if(!load_arch) { - return load_arch.unwrap_error(); - } - fat_arch& arch = load_arch.unwrap_value(); - if(should_swap()) { - swap_fat_arch(&arch, 1, NX_UnknownByteOrder); - } - fat_arches.push_back(arch); - arch_offset += arch_size; - } - thread_local static struct LP(mach_header)* mhp = _NSGetMachExecuteHeader(); - fat_arch* best = NXFindBestFatArch( - mhp->cputype, - mhp->cpusubtype, - fat_arches.data(), - header.nfat_arch - ); - if(best) { - off_t mach_header_offset = (off_t)best->offset; - auto magic = load_bytes(file, mach_header_offset); - if(!magic) { - return magic.unwrap_error(); - } - load_base = mach_header_offset; - fat_index = best - fat_arches.data(); - if(is_magic_64(magic.unwrap_value())) { - load_mach<64>(); - } else { - load_mach<32>(); - } - return monostate{}; - } - // If this is reached... something went wrong. The cpu we're on wasn't found. - return internal_error("Couldn't find appropriate architecture in fat Mach-O"); - } + Result load_fat_mach(); template - Result load_segment_command(std::uint32_t offset) const { - using Segment_Command = typename std::conditional::type; - auto load_segment = load_bytes(file, offset); - if(!load_segment) { - return load_segment.unwrap_error(); - } - Segment_Command& segment = load_segment.unwrap_value(); - ASSERT(segment.cmd == LC_SEGMENT_64 || segment.cmd == LC_SEGMENT); - if(should_swap()) { - swap_segment_command(segment); - } - // fields match just u64 instead of u32 - segment_command_64 common; - common.cmd = segment.cmd; - common.cmdsize = segment.cmdsize; - static_assert(sizeof common.segname == 16 && sizeof segment.segname == 16, "xx"); - memcpy(common.segname, segment.segname, 16); - common.vmaddr = segment.vmaddr; - common.vmsize = segment.vmsize; - common.fileoff = segment.fileoff; - common.filesize = segment.filesize; - common.maxprot = segment.maxprot; - common.initprot = segment.initprot; - common.nsects = segment.nsects; - common.flags = segment.flags; - return common; - } + Result load_segment_command(std::uint32_t offset) const; - Result load_symbol_table_command(std::uint32_t offset) const { - auto load_symtab = load_bytes(file, offset); - if(!load_symtab) { - return load_symtab.unwrap_error(); - } - symtab_command& symtab = load_symtab.unwrap_value(); - ASSERT(symtab.cmd == LC_SYMTAB); - if(should_swap()) { - swap_symtab_command(&symtab, NX_UnknownByteOrder); - } - return symtab; - } + Result load_symbol_table_command(std::uint32_t offset) const; template - Result load_symtab_entry(std::uint32_t symbol_base, std::size_t index) const { - using Nlist = typename std::conditional::type; - uint32_t offset = load_base + symbol_base + index * sizeof(Nlist); - auto load_entry = load_bytes(file, offset); - if(!load_entry) { - return load_entry.unwrap_error(); - } - Nlist& entry = load_entry.unwrap_value(); - if(should_swap()) { - swap_nlist(entry); - } - // fields match just u64 instead of u32 - nlist_64 common; - common.n_un.n_strx = entry.n_un.n_strx; - common.n_type = entry.n_type; - common.n_sect = entry.n_sect; - common.n_desc = entry.n_desc; - common.n_value = entry.n_value; - return common; - } + Result load_symtab_entry(std::uint32_t symbol_base, std::size_t index) const; - Result, internal_error> load_string_table(std::uint32_t offset, std::uint32_t byte_count) const { - std::unique_ptr buffer(new char[byte_count + 1]); - if(std::fseek(file, load_base + offset, SEEK_SET) != 0) { - return internal_error("fseek error while loading mach-o symbol table"); - } - if(std::fread(buffer.get(), sizeof(char), byte_count, file) != byte_count) { - return internal_error("fread error while loading mach-o symbol table"); - } - buffer[byte_count] = 0; // just out of an abundance of caution - return buffer; - } + Result, internal_error> load_string_table(std::uint32_t offset, std::uint32_t byte_count) const; - bool should_swap() const { - return should_swap_bytes(magic); - } + bool should_swap() const; }; - inline Result macho_is_fat(const std::string& object_path) { - auto file = raii_wrap(std::fopen(object_path.c_str(), "rb"), file_deleter); - if(file == nullptr) { - return internal_error("Unable to read object file {}", object_path); - } - auto magic = load_bytes(file, 0); - if(!magic) { - return magic.unwrap_error(); - } else { - return is_fat_magic(magic.unwrap_value()); - } - } + Result macho_is_fat(const std::string& object_path); } } -#pragma GCC diagnostic pop - #endif #endif diff --git a/src/binary/module_base.cpp b/src/binary/module_base.cpp new file mode 100644 index 0000000..8b3d00b --- /dev/null +++ b/src/binary/module_base.cpp @@ -0,0 +1,95 @@ +#include "module_base.hpp" + +#include "../utils/common.hpp" +#include "../utils/utils.hpp" + +#include +#include +#include +#include + +#if IS_LINUX || IS_APPLE + #include + #include + #if IS_APPLE + #include "mach-o.hpp" + #else + #include "elf.hpp" + #endif +#elif IS_WINDOWS + #include + #include "pe.hpp" +#endif + +namespace cpptrace { +namespace detail { + #if IS_LINUX + Result get_module_image_base(const std::string& object_path) { + static std::mutex mutex; + std::lock_guard lock(mutex); + static std::unordered_map cache; + auto it = cache.find(object_path); + if(it == cache.end()) { + // arguably it'd be better to release the lock while computing this, but also arguably it's good to not + // have two threads try to do the same computation + auto base = elf_get_module_image_base(object_path); + // TODO: Cache the error + if(base.is_error()) { + return std::move(base).unwrap_error(); + } + cache.insert(it, {object_path, base.unwrap_value()}); + return base; + } else { + return it->second; + } + } + #elif IS_APPLE + Result get_module_image_base(const std::string& object_path) { + // We have to parse the Mach-O to find the offset of the text section..... + // I don't know how addresses are handled if there is more than one __TEXT load command. I'm assuming for + // now that there is only one, and I'm using only the first section entry within that load command. + static std::mutex mutex; + std::lock_guard lock(mutex); + static std::unordered_map cache; + auto it = cache.find(object_path); + if(it == cache.end()) { + // arguably it'd be better to release the lock while computing this, but also arguably it's good to not + // have two threads try to do the same computation + auto obj = mach_o::open_mach_o(object_path); + // TODO: Cache the error + if(!obj) { + return obj.unwrap_error(); + } + auto base = obj.unwrap_value().get_text_vmaddr(); + if(!base) { + return std::move(base).unwrap_error(); + } + cache.insert(it, {object_path, base.unwrap_value()}); + return base; + } else { + return it->second; + } + } + #else // Windows + Result get_module_image_base(const std::string& object_path) { + static std::mutex mutex; + std::lock_guard lock(mutex); + static std::unordered_map cache; + auto it = cache.find(object_path); + if(it == cache.end()) { + // arguably it'd be better to release the lock while computing this, but also arguably it's good to not + // have two threads try to do the same computation + auto base = pe_get_module_image_base(object_path); + // TODO: Cache the error + if(!base) { + return std::move(base).unwrap_error(); + } + cache.insert(it, {object_path, base.unwrap_value()}); + return base; + } else { + return it->second; + } + } + #endif +} +} diff --git a/src/binary/module_base.hpp b/src/binary/module_base.hpp index 0dab56d..42fa796 100644 --- a/src/binary/module_base.hpp +++ b/src/binary/module_base.hpp @@ -4,94 +4,12 @@ #include "../utils/common.hpp" #include "../utils/utils.hpp" +#include #include -#include -#include -#include - -#if IS_LINUX || IS_APPLE - #include - #include - #if IS_APPLE - #include "mach-o.hpp" - #else - #include "elf.hpp" - #endif -#elif IS_WINDOWS - #include - #include "pe.hpp" -#endif namespace cpptrace { namespace detail { - #if IS_LINUX - inline Result get_module_image_base(const std::string& object_path) { - static std::mutex mutex; - std::lock_guard lock(mutex); - static std::unordered_map cache; - auto it = cache.find(object_path); - if(it == cache.end()) { - // arguably it'd be better to release the lock while computing this, but also arguably it's good to not - // have two threads try to do the same computation - auto base = elf_get_module_image_base(object_path); - // TODO: Cache the error - if(base.is_error()) { - return std::move(base).unwrap_error(); - } - cache.insert(it, {object_path, base.unwrap_value()}); - return base; - } else { - return it->second; - } - } - #elif IS_APPLE - inline Result get_module_image_base(const std::string& object_path) { - // We have to parse the Mach-O to find the offset of the text section..... - // I don't know how addresses are handled if there is more than one __TEXT load command. I'm assuming for - // now that there is only one, and I'm using only the first section entry within that load command. - static std::mutex mutex; - std::lock_guard lock(mutex); - static std::unordered_map cache; - auto it = cache.find(object_path); - if(it == cache.end()) { - // arguably it'd be better to release the lock while computing this, but also arguably it's good to not - // have two threads try to do the same computation - auto obj = mach_o::open_mach_o(object_path); - // TODO: Cache the error - if(!obj) { - return obj.unwrap_error(); - } - auto base = obj.unwrap_value().get_text_vmaddr(); - if(!base) { - return std::move(base).unwrap_error(); - } - cache.insert(it, {object_path, base.unwrap_value()}); - return base; - } else { - return it->second; - } - } - #else // Windows - inline Result get_module_image_base(const std::string& object_path) { - static std::mutex mutex; - std::lock_guard lock(mutex); - static std::unordered_map cache; - auto it = cache.find(object_path); - if(it == cache.end()) { - // arguably it'd be better to release the lock while computing this, but also arguably it's good to not - // have two threads try to do the same computation - auto base = pe_get_module_image_base(object_path); - // TODO: Cache the error - if(!base) { - return std::move(base).unwrap_error(); - } - cache.insert(it, {object_path, base.unwrap_value()}); - return base; - } else { - return it->second; - } - } - #endif + Result get_module_image_base(const std::string& object_path); } } diff --git a/src/binary/object.cpp b/src/binary/object.cpp new file mode 100644 index 0000000..ff307a5 --- /dev/null +++ b/src/binary/object.cpp @@ -0,0 +1,172 @@ +#include "object.hpp" + +#include "../utils/common.hpp" +#include "../utils/utils.hpp" +#include "module_base.hpp" + +#include +#include +#include +#include + +#if IS_LINUX || IS_APPLE + #include + #include + #if IS_LINUX + #include // needed for dladdr1's link_map info + #endif +#elif IS_WINDOWS + #include +#endif + +namespace cpptrace { +namespace detail { + #if IS_LINUX + std::string resolve_l_name(const char* l_name) { + if(l_name != nullptr && l_name[0] != 0) { + return l_name; + } else { + // empty l_name, this means it's the currently running executable + // TODO: Caching and proper handling + char buffer[CPPTRACE_PATH_MAX + 1]{}; + auto res = readlink("/proc/self/exe", buffer, CPPTRACE_PATH_MAX); + if(res == -1) { + return ""; // TODO + } else { + return buffer; + } + } + } + #ifdef CPPTRACE_HAS_DL_FIND_OBJECT // we don't even check for this on apple + object_frame get_frame_object_info(frame_ptr address) { + // Use _dl_find_object when we can, it's orders of magnitude faster + object_frame frame; + frame.raw_address = address; + frame.object_address = 0; + dl_find_object result; + if(_dl_find_object(reinterpret_cast(address), &result) == 0) { // thread safe + frame.object_path = resolve_l_name(result.dlfo_link_map->l_name); + frame.object_address = address - to_frame_ptr(result.dlfo_link_map->l_addr); + } + return frame; + } + #else + // dladdr queries are needed to get pre-ASLR addresses and targets to run addr2line on + object_frame get_frame_object_info(frame_ptr address) { + // reference: https://github.com/bminor/glibc/blob/master/debug/backtracesyms.c + // https://github.com/bminor/glibc/blob/91695ee4598b39d181ab8df579b888a8863c4cab/elf/dl-addr.c#L26 + Dl_info info; + link_map* link_map_info; + object_frame frame; + frame.raw_address = address; + frame.object_address = 0; + if( + // thread safe + dladdr1(reinterpret_cast(address), &info, reinterpret_cast(&link_map_info), RTLD_DL_LINKMAP) + ) { + frame.object_path = resolve_l_name(link_map_info->l_name); + auto base = get_module_image_base(frame.object_path); + if(base.has_value()) { + frame.object_address = address + - reinterpret_cast(info.dli_fbase) + + base.unwrap_value(); + } else { + base.drop_error(); + } + } + return frame; + } + #endif + #elif IS_APPLE + // macos doesn't have dladdr1 but it seems its dli_fname behaves more sensibly? + // dladdr queries are needed to get pre-ASLR addresses and targets to run addr2line on + object_frame get_frame_object_info(frame_ptr address) { + // reference: https://github.com/bminor/glibc/blob/master/debug/backtracesyms.c + Dl_info info; + object_frame frame; + frame.raw_address = address; + frame.object_address = 0; + if(dladdr(reinterpret_cast(address), &info)) { // thread safe + frame.object_path = info.dli_fname; + auto base = get_module_image_base(info.dli_fname); + if(base.has_value()) { + frame.object_address = address + - reinterpret_cast(info.dli_fbase) + + base.unwrap_value(); + } else { + base.drop_error(); + } + } + return frame; + } + #else + std::string get_module_name(HMODULE handle) { + static std::mutex mutex; + std::lock_guard lock(mutex); + static std::unordered_map cache; + auto it = cache.find(handle); + if(it == cache.end()) { + char path[MAX_PATH]; + if(GetModuleFileNameA(handle, path, sizeof(path))) { + ///std::fprintf(stderr, "path: %s base: %p\n", path, handle); + cache.insert(it, {handle, path}); + return path; + } else { + std::fprintf(stderr, "%s\n", std::system_error(GetLastError(), std::system_category()).what()); + cache.insert(it, {handle, ""}); + return ""; + } + } else { + return it->second; + } + } + + object_frame get_frame_object_info(frame_ptr address) { + object_frame frame; + frame.raw_address = address; + frame.object_address = 0; + HMODULE handle; + // Multithread safe as long as another thread doesn't come along and free the module + if(GetModuleHandleExA( + GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT | GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, + reinterpret_cast(address), + &handle + )) { + frame.object_path = get_module_name(handle); + auto base = get_module_image_base(frame.object_path); + if(base.has_value()) { + frame.object_address = address + - reinterpret_cast(handle) + + base.unwrap_value(); + } else { + base.drop_error(); + } + } else { + std::fprintf(stderr, "%s\n", std::system_error(GetLastError(), std::system_category()).what()); + } + return frame; + } + #endif + + std::vector get_frames_object_info(const std::vector& addresses) { + std::vector frames; + frames.reserve(addresses.size()); + for(const frame_ptr address : addresses) { + frames.push_back(get_frame_object_info(address)); + } + return frames; + } + + object_frame resolve_safe_object_frame(const safe_object_frame& frame) { + auto base = get_module_image_base(frame.object_path); + if(base.is_error()) { + throw base.unwrap_error(); // This throw is intentional + } + return { + frame.raw_address, + frame.address_relative_to_object_start + base.unwrap_value(), + frame.object_path + }; + } +} +} diff --git a/src/binary/object.hpp b/src/binary/object.hpp index 2c812e5..bbbfdae 100644 --- a/src/binary/object.hpp +++ b/src/binary/object.hpp @@ -7,168 +7,14 @@ #include #include -#include -#include - -#if IS_LINUX || IS_APPLE - #include - #include - #if IS_LINUX - #include // needed for dladdr1's link_map info - #endif -#elif IS_WINDOWS - #include -#endif namespace cpptrace { namespace detail { - #if IS_LINUX - inline std::string resolve_l_name(const char* l_name) { - if(l_name != nullptr && l_name[0] != 0) { - return l_name; - } else { - // empty l_name, this means it's the currently running executable - // TODO: Caching and proper handling - char buffer[CPPTRACE_PATH_MAX + 1]{}; - auto res = readlink("/proc/self/exe", buffer, CPPTRACE_PATH_MAX); - if(res == -1) { - return ""; // TODO - } else { - return buffer; - } - } - } - #ifdef CPPTRACE_HAS_DL_FIND_OBJECT // we don't even check for this on apple - inline object_frame get_frame_object_info(frame_ptr address) { - // Use _dl_find_object when we can, it's orders of magnitude faster - object_frame frame; - frame.raw_address = address; - frame.object_address = 0; - dl_find_object result; - if(_dl_find_object(reinterpret_cast(address), &result) == 0) { // thread safe - frame.object_path = resolve_l_name(result.dlfo_link_map->l_name); - frame.object_address = address - to_frame_ptr(result.dlfo_link_map->l_addr); - } - return frame; - } - #else - // dladdr queries are needed to get pre-ASLR addresses and targets to run addr2line on - inline object_frame get_frame_object_info(frame_ptr address) { - // reference: https://github.com/bminor/glibc/blob/master/debug/backtracesyms.c - // https://github.com/bminor/glibc/blob/91695ee4598b39d181ab8df579b888a8863c4cab/elf/dl-addr.c#L26 - Dl_info info; - link_map* link_map_info; - object_frame frame; - frame.raw_address = address; - frame.object_address = 0; - if( - // thread safe - dladdr1(reinterpret_cast(address), &info, reinterpret_cast(&link_map_info), RTLD_DL_LINKMAP) - ) { - frame.object_path = resolve_l_name(link_map_info->l_name); - auto base = get_module_image_base(frame.object_path); - if(base.has_value()) { - frame.object_address = address - - reinterpret_cast(info.dli_fbase) - + base.unwrap_value(); - } else { - base.drop_error(); - } - } - return frame; - } - #endif - #elif IS_APPLE - // macos doesn't have dladdr1 but it seems its dli_fname behaves more sensibly? - // dladdr queries are needed to get pre-ASLR addresses and targets to run addr2line on - inline object_frame get_frame_object_info(frame_ptr address) { - // reference: https://github.com/bminor/glibc/blob/master/debug/backtracesyms.c - Dl_info info; - object_frame frame; - frame.raw_address = address; - frame.object_address = 0; - if(dladdr(reinterpret_cast(address), &info)) { // thread safe - frame.object_path = info.dli_fname; - auto base = get_module_image_base(info.dli_fname); - if(base.has_value()) { - frame.object_address = address - - reinterpret_cast(info.dli_fbase) - + base.unwrap_value(); - } else { - base.drop_error(); - } - } - return frame; - } - #else - inline std::string get_module_name(HMODULE handle) { - static std::mutex mutex; - std::lock_guard lock(mutex); - static std::unordered_map cache; - auto it = cache.find(handle); - if(it == cache.end()) { - char path[MAX_PATH]; - if(GetModuleFileNameA(handle, path, sizeof(path))) { - ///std::fprintf(stderr, "path: %s base: %p\n", path, handle); - cache.insert(it, {handle, path}); - return path; - } else { - std::fprintf(stderr, "%s\n", std::system_error(GetLastError(), std::system_category()).what()); - cache.insert(it, {handle, ""}); - return ""; - } - } else { - return it->second; - } - } + object_frame get_frame_object_info(frame_ptr address); - inline object_frame get_frame_object_info(frame_ptr address) { - object_frame frame; - frame.raw_address = address; - frame.object_address = 0; - HMODULE handle; - // Multithread safe as long as another thread doesn't come along and free the module - if(GetModuleHandleExA( - GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT | GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, - reinterpret_cast(address), - &handle - )) { - frame.object_path = get_module_name(handle); - auto base = get_module_image_base(frame.object_path); - if(base.has_value()) { - frame.object_address = address - - reinterpret_cast(handle) - + base.unwrap_value(); - } else { - base.drop_error(); - } - } else { - std::fprintf(stderr, "%s\n", std::system_error(GetLastError(), std::system_category()).what()); - } - return frame; - } - #endif + std::vector get_frames_object_info(const std::vector& addresses); - inline std::vector get_frames_object_info(const std::vector& addresses) { - std::vector frames; - frames.reserve(addresses.size()); - for(const frame_ptr address : addresses) { - frames.push_back(get_frame_object_info(address)); - } - return frames; - } - - inline object_frame resolve_safe_object_frame(const safe_object_frame& frame) { - auto base = get_module_image_base(frame.object_path); - if(base.is_error()) { - throw base.unwrap_error(); // This throw is intentional - } - return { - frame.raw_address, - frame.address_relative_to_object_start + base.unwrap_value(), - frame.object_path - }; - } + object_frame resolve_safe_object_frame(const safe_object_frame& frame); } } diff --git a/src/binary/pe.cpp b/src/binary/pe.cpp new file mode 100644 index 0000000..bb28024 --- /dev/null +++ b/src/binary/pe.cpp @@ -0,0 +1,95 @@ +#include "pe.hpp" + +#include "../utils/common.hpp" +#include "../utils/error.hpp" +#include "../utils/utils.hpp" + +#if IS_WINDOWS +#include +#include +#include +#include +#include + +#include + +namespace cpptrace { +namespace detail { + template::value, int>::type = 0> + T pe_byteswap_if_needed(T value) { + // PE header values are little endian, I think dos e_lfanew should be too + if(!is_little_endian()) { + return byteswap(value); + } else { + return value; + } + } + + Result pe_get_module_image_base(const std::string& object_path) { + // https://drive.google.com/file/d/0B3_wGJkuWLytbnIxY1J5WUs4MEk/view?pli=1&resourcekey=0-n5zZ2UW39xVTH8ZSu6C2aQ + // https://0xrick.github.io/win-internals/pe3/ + // Endianness should always be little for dos and pe headers + std::FILE* file_ptr; + errno_t ret = fopen_s(&file_ptr, object_path.c_str(), "rb"); + auto file = raii_wrap(std::move(file_ptr), file_deleter); + if(ret != 0 || file == nullptr) { + return internal_error("Unable to read object file {}", object_path); + } + auto magic = load_bytes>(file, 0); + if(!magic) { + return std::move(magic).unwrap_error(); + } + if(std::memcmp(magic.unwrap_value().data(), "MZ", 2) != 0) { + return internal_error("File is not a PE file {}", object_path); + } + auto e_lfanew = load_bytes(file, 0x3c); // dos header + 0x3c + if(!e_lfanew) { + return std::move(e_lfanew).unwrap_error(); + } + DWORD nt_header_offset = pe_byteswap_if_needed(e_lfanew.unwrap_value()); + auto signature = load_bytes>(file, nt_header_offset); // nt header + 0 + if(!signature) { + return std::move(signature).unwrap_error(); + } + if(std::memcmp(signature.unwrap_value().data(), "PE\0\0", 4) != 0) { + return internal_error("File is not a PE file {}", object_path); + } + auto size_of_optional_header_raw = load_bytes(file, nt_header_offset + 4 + 0x10); // file header + 0x10 + if(!size_of_optional_header_raw) { + return std::move(size_of_optional_header_raw).unwrap_error(); + } + WORD size_of_optional_header = pe_byteswap_if_needed(size_of_optional_header_raw.unwrap_value()); + if(size_of_optional_header == 0) { + return internal_error("Unexpected optional header size for PE file"); + } + auto optional_header_magic_raw = load_bytes(file, nt_header_offset + 0x18); // optional header + 0x0 + if(!optional_header_magic_raw) { + return std::move(optional_header_magic_raw).unwrap_error(); + } + WORD optional_header_magic = pe_byteswap_if_needed(optional_header_magic_raw.unwrap_value()); + VERIFY( + optional_header_magic == IMAGE_NT_OPTIONAL_HDR_MAGIC, + "PE file does not match expected bit-mode " + object_path + ); + // finally get image base + if(optional_header_magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) { + // 32 bit + auto bytes = load_bytes(file, nt_header_offset + 0x18 + 0x1c); // optional header + 0x1c + if(!bytes) { + return std::move(bytes).unwrap_error(); + } + return to(pe_byteswap_if_needed(bytes.unwrap_value())); + } else { + // 64 bit + // I get an "error: 'QWORD' was not declared in this scope" for some reason when using QWORD + auto bytes = load_bytes(file, nt_header_offset + 0x18 + 0x18); // optional header + 0x18 + if(!bytes) { + return std::move(bytes).unwrap_error(); + } + return to(pe_byteswap_if_needed(bytes.unwrap_value())); + } + } +} +} + +#endif diff --git a/src/binary/pe.hpp b/src/binary/pe.hpp index 683c57e..9426c14 100644 --- a/src/binary/pe.hpp +++ b/src/binary/pe.hpp @@ -2,94 +2,15 @@ #define PE_HPP #include "../utils/common.hpp" -#include "../utils/error.hpp" #include "../utils/utils.hpp" #if IS_WINDOWS -#include -#include -#include -#include +#include #include -#include - namespace cpptrace { namespace detail { - template::value, int>::type = 0> - T pe_byteswap_if_needed(T value) { - // PE header values are little endian, I think dos e_lfanew should be too - if(!is_little_endian()) { - return byteswap(value); - } else { - return value; - } - } - - inline Result pe_get_module_image_base(const std::string& object_path) { - // https://drive.google.com/file/d/0B3_wGJkuWLytbnIxY1J5WUs4MEk/view?pli=1&resourcekey=0-n5zZ2UW39xVTH8ZSu6C2aQ - // https://0xrick.github.io/win-internals/pe3/ - // Endianness should always be little for dos and pe headers - std::FILE* file_ptr; - errno_t ret = fopen_s(&file_ptr, object_path.c_str(), "rb"); - auto file = raii_wrap(std::move(file_ptr), file_deleter); - if(ret != 0 || file == nullptr) { - return internal_error("Unable to read object file {}", object_path); - } - auto magic = load_bytes>(file, 0); - if(!magic) { - return std::move(magic).unwrap_error(); - } - if(std::memcmp(magic.unwrap_value().data(), "MZ", 2) != 0) { - return internal_error("File is not a PE file {}", object_path); - } - auto e_lfanew = load_bytes(file, 0x3c); // dos header + 0x3c - if(!e_lfanew) { - return std::move(e_lfanew).unwrap_error(); - } - DWORD nt_header_offset = pe_byteswap_if_needed(e_lfanew.unwrap_value()); - auto signature = load_bytes>(file, nt_header_offset); // nt header + 0 - if(!signature) { - return std::move(signature).unwrap_error(); - } - if(std::memcmp(signature.unwrap_value().data(), "PE\0\0", 4) != 0) { - return internal_error("File is not a PE file {}", object_path); - } - auto size_of_optional_header_raw = load_bytes(file, nt_header_offset + 4 + 0x10); // file header + 0x10 - if(!size_of_optional_header_raw) { - return std::move(size_of_optional_header_raw).unwrap_error(); - } - WORD size_of_optional_header = pe_byteswap_if_needed(size_of_optional_header_raw.unwrap_value()); - if(size_of_optional_header == 0) { - return internal_error("Unexpected optional header size for PE file"); - } - auto optional_header_magic_raw = load_bytes(file, nt_header_offset + 0x18); // optional header + 0x0 - if(!optional_header_magic_raw) { - return std::move(optional_header_magic_raw).unwrap_error(); - } - WORD optional_header_magic = pe_byteswap_if_needed(optional_header_magic_raw.unwrap_value()); - VERIFY( - optional_header_magic == IMAGE_NT_OPTIONAL_HDR_MAGIC, - "PE file does not match expected bit-mode " + object_path - ); - // finally get image base - if(optional_header_magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) { - // 32 bit - auto bytes = load_bytes(file, nt_header_offset + 0x18 + 0x1c); // optional header + 0x1c - if(!bytes) { - return std::move(bytes).unwrap_error(); - } - return to(pe_byteswap_if_needed(bytes.unwrap_value())); - } else { - // 64 bit - // I get an "error: 'QWORD' was not declared in this scope" for some reason when using QWORD - auto bytes = load_bytes(file, nt_header_offset + 0x18 + 0x18); // optional header + 0x18 - if(!bytes) { - return std::move(bytes).unwrap_error(); - } - return to(pe_byteswap_if_needed(bytes.unwrap_value())); - } - } + Result pe_get_module_image_base(const std::string& object_path); } } diff --git a/src/binary/safe_dl.cpp b/src/binary/safe_dl.cpp new file mode 100644 index 0000000..5c7ad6e --- /dev/null +++ b/src/binary/safe_dl.cpp @@ -0,0 +1,67 @@ +#include "safe_dl.hpp" + +#include "../utils/common.hpp" +#include "../utils/utils.hpp" +#include "../utils/program_name.hpp" + +#include +#include +#include +#include +#include +#include + +#ifdef CPPTRACE_HAS_DL_FIND_OBJECT +#if IS_LINUX || IS_APPLE + #include + #include + #include +#endif + +namespace cpptrace { +namespace detail { + void get_safe_object_frame(frame_ptr address, safe_object_frame* out) { + out->raw_address = address; + dl_find_object result; + if(_dl_find_object(reinterpret_cast(address), &result) == 0) { + out->address_relative_to_object_start = address - to_frame_ptr(result.dlfo_link_map->l_addr); + if(result.dlfo_link_map->l_name != nullptr && result.dlfo_link_map->l_name[0] != 0) { + std::size_t path_length = std::strlen(result.dlfo_link_map->l_name); + std::memcpy( + out->object_path, + result.dlfo_link_map->l_name, + std::min(path_length + 1, std::size_t(CPPTRACE_PATH_MAX + 1)) + ); + } else { + // empty l_name, this means it's the currently running executable + memset(out->object_path, 0, CPPTRACE_PATH_MAX + 1); + auto res = readlink("/proc/self/exe", out->object_path, CPPTRACE_PATH_MAX); + if(res == -1) { + // error handling? + } + // TODO: Special handling for /proc/pid/exe unlink edge case + } + } else { + // std::cout<<"error"<address_relative_to_object_start = 0; + out->object_path[0] = 0; + } + // TODO: Handle this part of the documentation? + // The address can be a code address or data address. On architectures using function descriptors, no attempt is + // made to decode the function descriptor. Depending on how these descriptors are implemented, _dl_find_object + // may return the object that defines the function descriptor (and not the object that contains the code + // implementing the function), or fail to find any object at all. + } +} +} +#else +namespace cpptrace { +namespace detail { + void get_safe_object_frame(frame_ptr address, safe_object_frame* out) { + out->raw_address = address; + out->address_relative_to_object_start = 0; + out->object_path[0] = 0; + } +} +} +#endif diff --git a/src/binary/safe_dl.hpp b/src/binary/safe_dl.hpp index 5132808..6e789d0 100644 --- a/src/binary/safe_dl.hpp +++ b/src/binary/safe_dl.hpp @@ -2,69 +2,11 @@ #define SAFE_DL_HPP #include "../utils/common.hpp" -#include "../utils/utils.hpp" -#include "../utils/program_name.hpp" - -#include -#include -#include -#include -#include -#include - -#ifdef CPPTRACE_HAS_DL_FIND_OBJECT -#if IS_LINUX || IS_APPLE - #include - #include - #include -#endif namespace cpptrace { namespace detail { - inline void get_safe_object_frame(frame_ptr address, safe_object_frame* out) { - out->raw_address = address; - dl_find_object result; - if(_dl_find_object(reinterpret_cast(address), &result) == 0) { - out->address_relative_to_object_start = address - to_frame_ptr(result.dlfo_link_map->l_addr); - if(result.dlfo_link_map->l_name != nullptr && result.dlfo_link_map->l_name[0] != 0) { - std::size_t path_length = std::strlen(result.dlfo_link_map->l_name); - std::memcpy( - out->object_path, - result.dlfo_link_map->l_name, - std::min(path_length + 1, std::size_t(CPPTRACE_PATH_MAX + 1)) - ); - } else { - // empty l_name, this means it's the currently running executable - memset(out->object_path, 0, CPPTRACE_PATH_MAX + 1); - auto res = readlink("/proc/self/exe", out->object_path, CPPTRACE_PATH_MAX); - if(res == -1) { - // error handling? - } - // TODO: Special handling for /proc/pid/exe unlink edge case - } - } else { - // std::cout<<"error"<address_relative_to_object_start = 0; - out->object_path[0] = 0; - } - // TODO: Handle this part of the documentation? - // The address can be a code address or data address. On architectures using function descriptors, no attempt is - // made to decode the function descriptor. Depending on how these descriptors are implemented, _dl_find_object - // may return the object that defines the function descriptor (and not the object that contains the code - // implementing the function), or fail to find any object at all. - } + void get_safe_object_frame(frame_ptr address, safe_object_frame* out); } } -#else -namespace cpptrace { -namespace detail { - inline void get_safe_object_frame(frame_ptr address, safe_object_frame* out) { - out->raw_address = address; - out->address_relative_to_object_start = 0; - out->object_path[0] = 0; - } -} -} -#endif #endif diff --git a/src/symbols/symbols.hpp b/src/symbols/symbols.hpp index 060c93f..8264c47 100644 --- a/src/symbols/symbols.hpp +++ b/src/symbols/symbols.hpp @@ -5,6 +5,7 @@ #include #include +#include #include "../binary/object.hpp" diff --git a/src/symbols/symbols_with_dbghelp.cpp b/src/symbols/symbols_with_dbghelp.cpp index 0efbecf..d00bb6f 100644 --- a/src/symbols/symbols_with_dbghelp.cpp +++ b/src/symbols/symbols_with_dbghelp.cpp @@ -5,6 +5,7 @@ #include "../utils/dbghelp_syminit_manager.hpp" #include +#include #include #include #include diff --git a/src/symbols/symbols_with_libdwarf.cpp b/src/symbols/symbols_with_libdwarf.cpp index 956ab7e..059af85 100644 --- a/src/symbols/symbols_with_libdwarf.cpp +++ b/src/symbols/symbols_with_libdwarf.cpp @@ -6,6 +6,7 @@ #include "../utils/dwarf.hpp" // has dwarf #includes #include "../utils/error.hpp" #include "../binary/object.hpp" +#include "../binary/mach-o.hpp" #include "../utils/utils.hpp" #include "../utils/program_name.hpp" // For CPPTRACE_MAX_PATH