From acaa4f42e619863f044b06efd62673727029c573 Mon Sep 17 00:00:00 2001 From: Jeremy Rifkin <51220084+jeremy-rifkin@users.noreply.github.com> Date: Sat, 30 Mar 2024 13:35:52 -0500 Subject: [PATCH] Add a Result type and replace some exceptions with it (#109) --- src/binary/elf.hpp | 51 ++++- src/binary/mach-o.hpp | 250 ++++++++++++++++------ src/binary/module_base.hpp | 30 ++- src/binary/object.hpp | 28 ++- src/binary/pe.hpp | 65 ++++-- src/symbols/symbols_with_dbghelp.cpp | 22 +- src/symbols/symbols_with_dl.cpp | 5 +- src/symbols/symbols_with_libbacktrace.cpp | 2 +- src/symbols/symbols_with_libdwarf.cpp | 27 ++- src/unwind/unwind_with_dbghelp.cpp | 4 +- src/utils/common.hpp | 8 + src/utils/dbghelp_syminit_manager.hpp | 2 +- src/utils/dwarf.hpp | 2 +- src/utils/error.hpp | 46 ++-- src/utils/microfmt.hpp | 4 +- src/utils/utils.hpp | 160 +++++++++++--- 16 files changed, 514 insertions(+), 192 deletions(-) diff --git a/src/binary/elf.hpp b/src/binary/elf.hpp index a6532f1..54a7366 100644 --- a/src/binary/elf.hpp +++ b/src/binary/elf.hpp @@ -25,7 +25,7 @@ namespace detail { } template - static std::uintptr_t elf_get_module_image_base_from_program_table( + static Result elf_get_module_image_base_from_program_table( const std::string& object_path, std::FILE* file, bool is_little_endian @@ -33,33 +33,62 @@ namespace detail { static_assert(Bits == 32 || Bits == 64, "Unexpected Bits argument"); using Header = typename std::conditional::type; using PHeader = typename std::conditional::type; - Header file_header = load_bytes
(file, 0); - VERIFY(file_header.e_ehsize == sizeof(Header), "ELF file header size mismatch" + object_path); + 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++) { - PHeader program_header = load_bytes(file, file_header.e_phoff + file_header.e_phentsize * 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 file. 0 as a base seems correct. + // Apparently some objects like shared objects can end up missing this header. 0 as a base seems correct. return 0; } - static std::uintptr_t elf_get_module_image_base(const std::string& object_path) { + 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) { - throw file_error("Unable to read object file " + object_path); + return internal_error("Unable to read object file " + object_path); } // Initial checks/metadata auto magic = load_bytes>(file, 0); - VERIFY(magic == (std::array{0x7F, 'E', 'L', 'F'}), "File is not ELF " + object_path); - bool is_64 = load_bytes(file, 4) == 2; - bool is_little_endian = load_bytes(file, 5) == 1; - VERIFY(load_bytes(file, 6) == 1, "Unexpected ELF endianness " + object_path); + 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); diff --git a/src/binary/mach-o.hpp b/src/binary/mach-o.hpp index 7769089..21e9592 100644 --- a/src/binary/mach-o.hpp +++ b/src/binary/mach-o.hpp @@ -45,14 +45,14 @@ namespace detail { } inline bool file_is_mach_o(const std::string& object_path) noexcept { - try { - FILE* file = std::fopen(object_path.c_str(), "rb"); - if(file == nullptr) { - return false; - } - auto magic = load_bytes(file, 0); - return is_mach_o(magic); - } catch(...) { + 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; } } @@ -108,7 +108,7 @@ namespace detail { }; class mach_o { - std::FILE* file = nullptr; + file_wrapper file; std::string object_path; std::uint32_t magic; cpu_type_t cputype; @@ -127,11 +127,11 @@ namespace detail { struct symtab_info_data { symtab_command symtab; std::unique_ptr stringtab; - const char* get_string(std::size_t index) const { + Result get_string(std::size_t index) const { if(stringtab && index < symtab.strsize) { return stringtab.get() + index; } else { - throw std::runtime_error("can't retrieve symbol from symtab"); + return internal_error("can't retrieve symbol from symtab"); } } }; @@ -139,45 +139,69 @@ namespace detail { bool tried_to_load_symtab = false; optional symtab_info; - public: - mach_o(const std::string& object_path) : object_path(object_path) { - file = std::fopen(object_path.c_str(), "rb"); - if(file == nullptr) { - throw file_error("Unable to read object file " + object_path); - } - magic = load_bytes(file, 0); - VERIFY(is_mach_o(magic), "File is not Mach-O " + object_path); + mach_o( + file_wrapper file, + const std::string& object_path, + std::uint32_t magic + ) : + file(std::move(file)), + object_path(object_path), + magic(magic) {} + + Result load() { if(magic == FAT_MAGIC || magic == FAT_CIGAM) { - load_fat_mach(); + return load_fat_mach(); } else { fat_index = 0; if(is_magic_64(magic)) { - load_mach<64>(); + return load_mach<64>(); } else { - load_mach<32>(); + return load_mach<32>(); } } } - ~mach_o() { - if(file) { - std::fclose(file); + 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; } } - std::uintptr_t get_text_vmaddr() { + 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(std::strcmp(segment.segname, "__TEXT") == 0) { - return segment.vmaddr; + if(segment.is_error()) { + segment.drop_error(); + } + if(std::strcmp(segment.unwrap_value().segname, "__TEXT") == 0) { + return segment.unwrap_value().vmaddr; } } } // somehow no __TEXT section was found... - throw std::runtime_error("Couldn't find __TEXT section while parsing Mach-O object"); + return internal_error("Couldn't find __TEXT section while parsing Mach-O object"); } std::size_t get_fat_index() const { @@ -189,10 +213,16 @@ namespace detail { int i = 0; for(const auto& command : load_commands) { if(command.cmd == LC_SEGMENT_64 || command.cmd == LC_SEGMENT) { - auto segment = command.cmd == LC_SEGMENT_64 + 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); @@ -213,8 +243,16 @@ namespace detail { for(const auto& command : load_commands) { if(command.cmd == LC_SYMTAB) { symtab_info_data info; - info.symtab = load_symbol_table_command(command.file_offset); - info.stringtab = load_string_table(info.symtab.stroff, info.symtab.strsize); + auto symtab = load_symbol_table_command(command.file_offset); + if(!symtab) { + // TODO + } + info.symtab = symtab.unwrap_value(); + auto string = load_string_table(info.symtab.stroff, info.symtab.strsize); + if(!string) { + // TODO + } + info.stringtab = std::move(string).unwrap_value(); symtab_info = std::move(info); break; } @@ -263,8 +301,14 @@ namespace detail { int i = 0; for(const auto& command : load_commands) { if(command.cmd == LC_SYMTAB) { - auto symtab = load_symbol_table_command(command.file_offset); + 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)); @@ -272,11 +316,24 @@ namespace detail { 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++) { - nlist_64 entry = bits == 32 + auto entry = bits == 32 ? load_symtab_entry<32>(symtab.symoff, j) : load_symtab_entry<64>(symtab.symoff, j); - print_symbol_table_entry(entry, stringtab, symtab.strsize, 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++; @@ -308,9 +365,13 @@ namespace detail { std::string current_module; optional current_function; for(std::size_t j = 0; j < symtab.nsyms; j++) { - nlist_64 entry = bits == 32 + auto load_entry = bits == 32 ? load_symtab_entry<32>(symtab.symoff, j) : load_symtab_entry<64>(symtab.symoff, j); + if(!load_entry) { + // TODO + } + auto& entry = load_entry.unwrap_value(); // entry.n_type & N_STAB indicates symbolic debug info if(!(entry.n_type & N_STAB)) { continue; @@ -320,15 +381,24 @@ namespace detail { // pass - these encode path and filename for the module, if applicable break; case N_OSO: - // sets the module - current_module = symtab_info.get_string(entry.n_un.n_strx); + { + // sets the module + auto str = symtab_info.get_string(entry.n_un.n_strx); + if(!str) { + // TODO + } + current_module = str.unwrap_value(); + } break; case N_BNSYM: break; // pass case N_ENSYM: break; // pass case N_FUN: { - const char* str = symtab_info.get_string(entry.n_un.n_strx); - if(str[0] == 0) { + auto str = symtab_info.get_string(entry.n_un.n_strx); + if(!str) { + // TODO + } + if(str.unwrap_value()[0] == 0) { // end of function scope if(!current_function) { /**/ } current_function.unwrap().size = entry.n_value; @@ -336,7 +406,7 @@ namespace detail { } else { current_function = debug_map_entry{}; current_function.unwrap().source_address = entry.n_value; - current_function.unwrap().name = str; + current_function.unwrap().name = str.unwrap_value(); } } break; @@ -353,16 +423,24 @@ namespace detail { const auto& symtab = symtab_info.symtab; // TODO: Take timestamp into account? for(std::size_t j = 0; j < symtab.nsyms; j++) { - nlist_64 entry = bits == 32 + auto load_entry = bits == 32 ? load_symtab_entry<32>(symtab.symoff, j) : load_symtab_entry<64>(symtab.symoff, j); + if(!load_entry) { + // TODO + } + 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) { + // TODO + } symbols.push_back({ entry.n_value, - symtab_info.get_string(entry.n_un.n_strx) + str.unwrap_value() }); } } @@ -390,12 +468,16 @@ namespace detail { private: template - void load_mach() { + 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); - Mach_Header header = load_bytes(file, load_base); + 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); @@ -412,19 +494,28 @@ namespace detail { // iterate load commands std::uint32_t actual_offset = load_commands_offset; for(std::uint32_t i = 0; i < ncmds; i++) { - load_command cmd = load_bytes(file, actual_offset); + 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{}; } - void load_fat_mach() { + Result load_fat_mach() { std::size_t header_size = sizeof(fat_header); std::size_t arch_size = sizeof(fat_arch); - fat_header header = load_bytes(file, 0); + 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); } @@ -458,7 +549,11 @@ namespace detail { 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++) { - fat_arch arch = load_bytes(file, arch_offset); + 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); } @@ -474,24 +569,31 @@ namespace detail { ); if(best) { off_t mach_header_offset = (off_t)best->offset; - std::uint32_t magic = load_bytes(file, mach_header_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)) { + if(is_magic_64(magic.unwrap_value())) { load_mach<64>(); } else { load_mach<32>(); } - return; + return monostate{}; } // If this is reached... something went wrong. The cpu we're on wasn't found. - throw std::runtime_error("Couldn't find appropriate architecture in fat Mach-O"); + return internal_error("Couldn't find appropriate architecture in fat Mach-O"); } template - segment_command_64 load_segment_command(std::uint32_t offset) const { + Result load_segment_command(std::uint32_t offset) const { using Segment_Command = typename std::conditional::type; - Segment_Command segment = load_bytes(file, offset); + 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); @@ -513,8 +615,12 @@ namespace detail { return common; } - symtab_command load_symbol_table_command(std::uint32_t offset) const { - symtab_command symtab = load_bytes(file, offset); + 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); @@ -523,10 +629,14 @@ namespace detail { } template - nlist_64 load_symtab_entry(std::uint32_t symbol_base, std::size_t index) const { + 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); - Nlist entry = load_bytes(file, offset); + 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); } @@ -540,10 +650,14 @@ namespace detail { return common; } - std::unique_ptr load_string_table(std::uint32_t offset, std::uint32_t byte_count) 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]); - VERIFY(std::fseek(file, load_base + offset, SEEK_SET) == 0, "fseek error"); - VERIFY(std::fread(buffer.get(), sizeof(char), byte_count, file) == byte_count, "fread error"); + 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; } @@ -553,13 +667,17 @@ namespace detail { } }; - inline bool macho_is_fat(const std::string& object_path) { + 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) { - throw file_error("Unable to read object file " + object_path); + 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()); } - std::uint32_t magic = load_bytes(file, 0); - return is_fat_magic(magic); } } } diff --git a/src/binary/module_base.hpp b/src/binary/module_base.hpp index 15c701b..133682d 100644 --- a/src/binary/module_base.hpp +++ b/src/binary/module_base.hpp @@ -25,7 +25,7 @@ namespace cpptrace { namespace detail { #if IS_LINUX - inline std::uintptr_t get_module_image_base(const std::string& object_path) { + 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; @@ -34,14 +34,18 @@ namespace detail { // 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); - cache.insert(it, {object_path, base}); + // TODO: Cache the error + if(base.is_error()) { + return base.unwrap_error(); + } + cache.insert(it, {object_path, base.unwrap_value()}); return base; } else { return it->second; } } #elif IS_APPLE - inline std::uintptr_t get_module_image_base(const std::string& object_path) { + 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. @@ -52,15 +56,23 @@ namespace detail { 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 = mach_o(object_path).get_text_vmaddr(); - cache.insert(it, {object_path, base}); + 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 base.unwrap_error(); + } + cache.insert(it, {object_path, base.unwrap_value()}); return base; } else { return it->second; } } #else // Windows - inline std::uintptr_t get_module_image_base(const std::string& object_path) { + 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; @@ -69,7 +81,11 @@ namespace detail { // 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); - cache.insert(it, {object_path, base}); + // TODO: Cache the error + if(!base) { + return base.unwrap_error(); + } + cache.insert(it, {object_path, base.unwrap_value()}); return base; } else { return it->second; diff --git a/src/binary/object.hpp b/src/binary/object.hpp index bc41737..1b2a3b2 100644 --- a/src/binary/object.hpp +++ b/src/binary/object.hpp @@ -58,9 +58,14 @@ namespace detail { frame.object_address = 0; if(dladdr(reinterpret_cast(address), &info)) { // thread safe frame.object_path = info.dli_fname; - frame.object_address = address - - reinterpret_cast(info.dli_fbase) - + get_module_image_base(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; } @@ -99,9 +104,14 @@ namespace detail { &handle )) { frame.object_path = get_module_name(handle); - frame.object_address = address - - reinterpret_cast(handle) - + get_module_image_base(frame.object_path); + 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()); } @@ -119,9 +129,13 @@ namespace detail { } 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 + get_module_image_base(frame.object_path), + frame.address_relative_to_object_start + base.unwrap_value(), frame.object_path }; } diff --git a/src/binary/pe.hpp b/src/binary/pe.hpp index 452f3b1..0a7b6d3 100644 --- a/src/binary/pe.hpp +++ b/src/binary/pe.hpp @@ -26,7 +26,7 @@ namespace detail { } } - inline std::uintptr_t pe_get_module_image_base(const std::string& object_path) { + 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 @@ -34,21 +34,40 @@ namespace detail { 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) { - throw file_error("Unable to read object file " + object_path); + throw internal_error("Unable to read object file {}", object_path); } auto magic = load_bytes>(file, 0); - VERIFY(std::memcmp(magic.data(), "MZ", 2) == 0, "File is not a PE file " + object_path); - DWORD e_lfanew = pe_byteswap_if_needed(load_bytes(file, 0x3c)); // dos header + 0x3c - DWORD nt_header_offset = e_lfanew; + if(!magic) { + return 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 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 - VERIFY(std::memcmp(signature.data(), "PE\0\0", 4) == 0, "File is not a PE file " + object_path); - WORD size_of_optional_header = pe_byteswap_if_needed( - load_bytes(file, nt_header_offset + 4 + 0x10) // file header + 0x10 - ); - VERIFY(size_of_optional_header != 0); - WORD optional_header_magic = pe_byteswap_if_needed( - load_bytes(file, nt_header_offset + 0x18) // optional header + 0x0 - ); + if(!signature) { + return 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 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 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 @@ -56,19 +75,19 @@ namespace detail { // finally get image base if(optional_header_magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) { // 32 bit - return to( - pe_byteswap_if_needed( - load_bytes(file, nt_header_offset + 0x18 + 0x1c) // optional header + 0x1c - ) - ); + auto bytes = load_bytes(file, nt_header_offset + 0x18 + 0x1c); // optional header + 0x1c + if(!bytes) { + return 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 - return to( - pe_byteswap_if_needed( - load_bytes(file, nt_header_offset + 0x18 + 0x18) // optional header + 0x18 - ) - ); + auto bytes = load_bytes(file, nt_header_offset + 0x18 + 0x18); // optional header + 0x18 + if(!bytes) { + return bytes.unwrap_error(); + } + return to(pe_byteswap_if_needed(bytes.unwrap_value())); } } } diff --git a/src/symbols/symbols_with_dbghelp.cpp b/src/symbols/symbols_with_dbghelp.cpp index 31c46db..107ba0b 100644 --- a/src/symbols/symbols_with_dbghelp.cpp +++ b/src/symbols/symbols_with_dbghelp.cpp @@ -70,9 +70,8 @@ namespace dbghelp { if(FAILABLE) { return (T)-1; } else { - throw std::logic_error( - std::string("SymGetTypeInfo failed: ") - + std::system_error(GetLastError(), std::system_category()).what() + throw internal_error( + "SymGetTypeInfo failed: {}", std::system_error(GetLastError(), std::system_category()).what() ); } } @@ -85,9 +84,8 @@ namespace dbghelp { if( !SymGetTypeInfo(proc, modbase, type_index, static_cast<::IMAGEHLP_SYMBOL_TYPE_INFO>(SymType), &info) ) { - throw std::logic_error( - std::string("SymGetTypeInfo failed: ") - + std::system_error(GetLastError(), std::system_category()).what() + throw internal_error( + "SymGetTypeInfo failed: {}", std::system_error(GetLastError(), std::system_category()).what() ); } // special case to properly free a buffer and convert string to narrow chars, only used for @@ -247,15 +245,15 @@ namespace dbghelp { children ) ) { - throw std::logic_error( - std::string("SymGetTypeInfo failed: ") - + std::system_error(GetLastError(), std::system_category()).what() + throw internal_error( + "SymGetTypeInfo failed: {}", + std::system_error(GetLastError(), std::system_category()).what() ); } // get children type std::string extent = "("; if(children->Start != 0) { - throw std::logic_error("Error: children->Start == 0"); + throw internal_error("Error: children->Start == 0"); } for(std::size_t i = 0; i < n_children; i++) { extent += (i == 0 ? "" : ", ") + resolve_type(children->ChildId[i], proc, modbase); @@ -428,7 +426,7 @@ namespace dbghelp { get_syminit_manager().init(proc); } else { if(!SymInitialize(proc, NULL, TRUE)) { - throw std::logic_error("Cpptrace SymInitialize failed"); + throw internal_error("Cpptrace SymInitialize failed"); } } for(const auto frame : frames) { @@ -445,7 +443,7 @@ namespace dbghelp { } if(get_cache_mode() != cache_mode::prioritize_speed) { if(!SymCleanup(proc)) { - throw std::logic_error("Cpptrace SymCleanup failed"); + throw internal_error("Cpptrace SymCleanup failed"); } } return trace; diff --git a/src/symbols/symbols_with_dl.cpp b/src/symbols/symbols_with_dl.cpp index 457a584..b4b5eb2 100644 --- a/src/symbols/symbols_with_dl.cpp +++ b/src/symbols/symbols_with_dl.cpp @@ -15,9 +15,12 @@ namespace libdl { stacktrace_frame resolve_frame(const frame_ptr addr) { Dl_info info; if(dladdr(reinterpret_cast(addr), &info)) { // thread-safe + auto base = get_module_image_base(info.dli_fname); return { addr, - addr - reinterpret_cast(info.dli_fbase) + get_module_image_base(info.dli_fname), + base.has_value() + ? addr - reinterpret_cast(info.dli_fbase) + base.unwrap_value() + : 0, nullable::null(), nullable::null(), info.dli_fname ? info.dli_fname : "", diff --git a/src/symbols/symbols_with_libbacktrace.cpp b/src/symbols/symbols_with_libbacktrace.cpp index c719bba..fc8011e 100644 --- a/src/symbols/symbols_with_libbacktrace.cpp +++ b/src/symbols/symbols_with_libbacktrace.cpp @@ -38,7 +38,7 @@ namespace libbacktrace { } void error_callback(void*, const char* msg, int errnum) { - throw std::runtime_error(microfmt::format("Libbacktrace error: {}, code {}\n", msg, errnum)); + throw internal_error("Libbacktrace error: {}, code {}", msg, errnum); } backtrace_state* get_backtrace_state() { diff --git a/src/symbols/symbols_with_libdwarf.cpp b/src/symbols/symbols_with_libdwarf.cpp index f768494..64849fb 100644 --- a/src/symbols/symbols_with_libdwarf.cpp +++ b/src/symbols/symbols_with_libdwarf.cpp @@ -149,8 +149,15 @@ namespace libdwarf { } use_buffer = false; // we resolved dSYM above as appropriate } - if(macho_is_fat(object_path)) { - universal_number = mach_o(object_path).get_fat_index(); + auto result = macho_is_fat(object_path); + if(result.is_error()) { + result.drop_error(); + } else if(result.unwrap_value()) { + auto obj = mach_o::open_mach_o(object_path); + if(!obj) { + // TODO + } + universal_number = obj.unwrap_value().get_fat_index(); } #endif @@ -901,7 +908,9 @@ namespace libdwarf { retrieve_symbol(vec_it->die, pc, vec_it->dwversion, frame, inlines); } } else { - ASSERT(cu_cache.size() == 0, "Vec should be empty?"); + // I've had this happen for _start, where there is a cached CU for the object but _start is outside + // of the CU's PC range + // ASSERT(cu_cache.size() == 0, "Vec should be empty?"); } } } @@ -992,7 +1001,11 @@ namespace libdwarf { // the path doesn't exist std::unordered_map symbols; this->symbols = symbols; - auto symbol_table = mach_o(object_path).symbol_table(); + auto obj = mach_o::open_mach_o(object_path); + if(!obj) { + // TODO + } + auto symbol_table = obj.unwrap_value().symbol_table(); for(const auto& symbol : symbol_table) { symbols[symbol.name] = symbol.address; } @@ -1045,7 +1058,11 @@ namespace libdwarf { debug_map_resolver(const std::string& source_object_path) { // load mach-o // TODO: Cache somehow? - mach_o source_mach(source_object_path); + auto obj = mach_o::open_mach_o(source_object_path); + if(!obj) { + // TODO + } + mach_o& source_mach = obj.unwrap_value(); auto source_debug_map = source_mach.get_debug_map(); // get symbol entries from debug map, as well as the various object files used to make this binary for(auto& entry : source_debug_map) { diff --git a/src/unwind/unwind_with_dbghelp.cpp b/src/unwind/unwind_with_dbghelp.cpp index 5e49fe5..6e9d744 100644 --- a/src/unwind/unwind_with_dbghelp.cpp +++ b/src/unwind/unwind_with_dbghelp.cpp @@ -110,7 +110,7 @@ namespace detail { get_syminit_manager().init(proc); } else { if(!SymInitialize(proc, NULL, TRUE)) { - throw std::logic_error("Cpptrace SymInitialize failed"); + throw internal_error("Cpptrace SymInitialize failed"); } } while(trace.size() < max_depth) { @@ -147,7 +147,7 @@ namespace detail { } if(get_cache_mode() != cache_mode::prioritize_speed) { if(!SymCleanup(proc)) { - throw std::logic_error("Cpptrace SymCleanup failed"); + throw internal_error("Cpptrace SymCleanup failed"); } } return trace; diff --git a/src/utils/common.hpp b/src/utils/common.hpp index b6426fe..ad4c191 100644 --- a/src/utils/common.hpp +++ b/src/utils/common.hpp @@ -50,6 +50,14 @@ #define MAGENTA ESC "35m" #define CYAN ESC "36m" +#if IS_GCC || IS_CLANG + #define NODISCARD __attribute__((warn_unused_result)) +// #elif IS_MSVC && _MSC_VER >= 1700 +// #define NODISCARD _Check_return_ +#else + #define NODISCARD +#endif + namespace cpptrace { namespace detail { static const stacktrace_frame null_frame { diff --git a/src/utils/dbghelp_syminit_manager.hpp b/src/utils/dbghelp_syminit_manager.hpp index 688b349..51d5d80 100644 --- a/src/utils/dbghelp_syminit_manager.hpp +++ b/src/utils/dbghelp_syminit_manager.hpp @@ -25,7 +25,7 @@ namespace detail { void init(HANDLE proc) { if(set.count(proc) == 0) { if(!SymInitialize(proc, NULL, TRUE)) { - throw std::logic_error(microfmt::format("SymInitialize failed {}", GetLastError())); + throw internal_error("SymInitialize failed {}", GetLastError()); } set.insert(proc); } diff --git a/src/utils/dwarf.hpp b/src/utils/dwarf.hpp index 3a282de..9f065c3 100644 --- a/src/utils/dwarf.hpp +++ b/src/utils/dwarf.hpp @@ -28,7 +28,7 @@ namespace libdwarf { char* msg = dwarf_errmsg(error); (void)dbg; // dwarf_dealloc_error(dbg, error); - throw std::runtime_error(microfmt::format("Cpptrace dwarf error {} {}\n", ev, msg)); + throw internal_error("Cpptrace dwarf error {} {}", ev, msg); } struct die_object { diff --git a/src/utils/error.hpp b/src/utils/error.hpp index 962844f..241daf7 100644 --- a/src/utils/error.hpp +++ b/src/utils/error.hpp @@ -17,10 +17,12 @@ namespace cpptrace { namespace detail { - class file_error : public std::exception { + class internal_error : public std::exception { std::string msg; public: - file_error(std::string path) : msg("Unable to read file " + std::move(path)) {} + internal_error(std::string message) : msg(std::move(message)) {} + template + internal_error(const char* format, Args&&... args) : msg(microfmt::format(format, args...)) {} const char* what() const noexcept override { return msg.c_str(); } @@ -57,22 +59,18 @@ namespace detail { const char* action = assert_actions[static_cast::type>(type)]; const char* name = assert_names[static_cast::type>(type)]; if(message == "") { - throw std::logic_error( - microfmt::format( - "Cpptrace {} failed at {}:{}: {}\n" - " %s(%s);\n", - action, location.file, location.line, signature, - name, expression - ) + throw internal_error( + "Cpptrace {} failed at {}:{}: {}\n" + " %s(%s);\n", + action, location.file, location.line, signature, + name, expression ); } else { - throw std::logic_error( - microfmt::format( - "Cpptrace {} failed at {}:{}: {}: {}\n" - " %s(%s);\n", - action, location.file, location.line, signature, message.c_str(), - name, expression - ) + throw internal_error( + "Cpptrace {} failed at {}:{}: {}: {}\n" + " %s(%s);\n", + action, location.file, location.line, signature, message.c_str(), + name, expression ); } } @@ -83,18 +81,14 @@ namespace detail { const std::string& message = "" ) { if(message == "") { - throw std::logic_error( - microfmt::format( - "Cpptrace panic {}:{}: {}\n", - location.file, location.line, signature - ) + throw internal_error( + "Cpptrace panic {}:{}: {}\n", + location.file, location.line, signature ); } else { - throw std::logic_error( - microfmt::format( - "Cpptrace panic {}:{}: {}: {}\n", - location.file, location.line, signature, message.c_str() - ) + throw internal_error( + "Cpptrace panic {}:{}: {}: {}\n", + location.file, location.line, signature, message.c_str() ); } } diff --git a/src/utils/microfmt.hpp b/src/utils/microfmt.hpp index b9d7f4c..3642071 100644 --- a/src/utils/microfmt.hpp +++ b/src/utils/microfmt.hpp @@ -254,7 +254,7 @@ namespace microfmt { } else if(*it == '{') { // try to parse variable width MICROFMT_ASSERT(peek() == '}'); it += 2; - MICROFMT_ASSERT(arg_i < N); + MICROFMT_ASSERT(arg_i < args.size()); options.width = args[arg_i++].unwrap_int(); } // try to parse fill/base @@ -277,7 +277,7 @@ namespace microfmt { } } MICROFMT_ASSERT(*it == '}'); - MICROFMT_ASSERT(arg_i < N); + MICROFMT_ASSERT(arg_i < args.size()); args[arg_i++].write(str, options); } } else if(*it == '}') { diff --git a/src/utils/utils.hpp b/src/utils/utils.hpp index 6ea0c6c..226d0df 100644 --- a/src/utils/utils.hpp +++ b/src/utils/utils.hpp @@ -192,15 +192,6 @@ namespace detail { static_assert(n_digits(11) == 2, "n_digits utility producing the wrong result"); static_assert(n_digits(1024) == 4, "n_digits utility producing the wrong result"); - // TODO: Re-evaluate use of off_t - template::value, int>::type = 0> - T load_bytes(std::FILE* object_file, off_t offset) { - T object; - VERIFY(std::fseek(object_file, offset, SEEK_SET) == 0, "fseek error"); - VERIFY(std::fread(&object, sizeof(T), 1, object_file) == 1, "fread error"); - return object; - } - struct nullopt_t {}; static constexpr nullopt_t nullopt; @@ -309,45 +300,156 @@ namespace detail { holds_value = false; } - T& unwrap() & { - if(!holds_value) { - throw std::runtime_error{"Optional does not contain a value"}; - } + NODISCARD T& unwrap() & { + ASSERT(holds_value, "Optional does not contain a value"); return uvalue; } - const T& unwrap() const & { - if(!holds_value) { - throw std::runtime_error{"Optional does not contain a value"}; - } + NODISCARD const T& unwrap() const & { + ASSERT(holds_value, "Optional does not contain a value"); return uvalue; } - T&& unwrap() && { - if(!holds_value) { - throw std::runtime_error{"Optional does not contain a value"}; - } + NODISCARD T&& unwrap() && { + ASSERT(holds_value, "Optional does not contain a value"); return std::move(uvalue); } - const T&& unwrap() const && { - if(!holds_value) { - throw std::runtime_error{"Optional does not contain a value"}; - } + NODISCARD const T&& unwrap() const && { + ASSERT(holds_value, "Optional does not contain a value"); return std::move(uvalue); } template - T value_or(U&& default_value) const & { + NODISCARD T value_or(U&& default_value) const & { return holds_value ? uvalue : static_cast(std::forward(default_value)); } template - T value_or(U&& default_value) && { + NODISCARD T value_or(U&& default_value) && { return holds_value ? std::move(uvalue) : static_cast(std::forward(default_value)); } }; + // TODO: Better dump error + // TODO: Explicit constructors for value, then add Ok()/Error() helpers + template::value, int>::type = 0> + class Result { + // Not using a union because I don't want to have to deal with that + union { + T value_; + E error_; + }; + enum class member { value, error }; + member active; + public: + Result(T value) : value_(std::move(value)), active(member::value) {} + Result(E error) : error_(std::move(error)), active(member::error) {} + Result(Result&& other) : active(other.active) { + if(other.active == member::value) { + new (&value_) T(std::move(other.value_)); + } else { + new (&error_) E(std::move(other.error_)); + } + } + ~Result() { + if(active == member::value) { + value_.~T(); + } else { + error_.~E(); + } + } + + bool has_value() const { + return active == member::value; + } + + bool is_error() const { + return active == member::error; + } + + explicit operator bool() const { + return has_value(); + } + + NODISCARD optional value() const & { + return has_value() ? value_ : nullopt; + } + + NODISCARD optional error() const & { + return is_error() ? error_ : nullopt; + } + + NODISCARD optional value() && { + return has_value() ? std::move(value_) : nullopt; + } + + NODISCARD optional error() && { + return is_error() ? std::move(error_) : nullopt; + } + + NODISCARD T& unwrap_value() & { + ASSERT(has_value(), "Result does not contain a value"); + return value_; + } + + NODISCARD const T& unwrap_value() const & { + ASSERT(has_value(), "Result does not contain a value"); + return value_; + } + + NODISCARD T unwrap_value() && { + ASSERT(has_value(), "Result does not contain a value"); + return std::move(value_); + } + + NODISCARD E& unwrap_error() & { + ASSERT(is_error(), "Result does not contain an error"); + return error_; + } + + NODISCARD const E& unwrap_error() const & { + ASSERT(is_error(), "Result does not contain an error"); + return error_; + } + + NODISCARD E unwrap_error() && { + ASSERT(is_error(), "Result does not contain an error"); + return std::move(error_); + } + + template + NODISCARD T value_or(U&& default_value) const & { + return has_value() ? value_ : static_cast(std::forward(default_value)); + } + + template + NODISCARD T value_or(U&& default_value) && { + return has_value() ? std::move(value_) : static_cast(std::forward(default_value)); + } + + void drop_error() const { + if(is_error()) { + std::fprintf(stderr, "%s\n", unwrap_error().what()); + } + } + }; + + struct monostate {}; + + // TODO: Re-evaluate use of off_t + template::value, int>::type = 0> + Result load_bytes(std::FILE* object_file, off_t offset) { + T object; + if(std::fseek(object_file, offset, SEEK_SET) != 0) { + return internal_error("fseek error"); + } + if(std::fread(&object, sizeof(T), 1, object_file) != 1) { + return internal_error("fread error"); + } + return object; + } + // shamelessly stolen from stackoverflow inline bool directory_exists(const std::string& path) { #if IS_WINDOWS @@ -385,6 +487,8 @@ namespace detail { return static_cast(v); } + // TODO: Rework some stuff here. Not sure deleters should be optional or moved. + // Also allow file_wrapper file = std::fopen(object_path.c_str(), "rb"); template< typename T, typename D @@ -460,6 +564,8 @@ namespace detail { fclose(ptr); } } + + using file_wrapper = raii_wrapper; } }