diff --git a/README.md b/README.md index f039bb2..a09c26f 100644 --- a/README.md +++ b/README.md @@ -251,6 +251,8 @@ A couple things I'd like to fix in the future: - On Windows unwinding with `CaptureStackBackTrace` (msvc/clang) can sometimes produce program counters that are after the call instruction. Execinfo suffers from the same problem, but libgcc's `_Unwind` provides a means to detect this. I would like to find a solution on windows so stack traces are more accurate. +- Support for universal binaries on macos (fat mach-o files) is hacky and inefficient at the moment. Libdwarf should be + adding proper support for these soon thanks to an awesome maintainer. ### FAQ: What about C++23 ``? diff --git a/ci/build-in-all-configs.py b/ci/build-in-all-configs.py index 92ae881..d8801d2 100644 --- a/ci/build-in-all-configs.py +++ b/ci/build-in-all-configs.py @@ -169,7 +169,7 @@ def main(): run_matrix(matrix, exclude, build_full_or_auto) if platform.system() == "Darwin": matrix = { - "compiler": ["g++-13", "clang++"], + "compiler": ["g++-12", "clang++"], "target": ["Debug"], "std": ["11", "20"], "unwind": [ @@ -192,7 +192,7 @@ def main(): exclude = [] run_matrix(matrix, exclude, build) matrix = { - "compiler": ["g++-13", "clang++"], + "compiler": ["g++-12", "clang++"], "target": ["Debug"], "std": ["11", "20"], "config": [""] diff --git a/ci/test-all-configs.py b/ci/test-all-configs.py index 65da12f..a8b4acb 100644 --- a/ci/test-all-configs.py +++ b/ci/test-all-configs.py @@ -323,7 +323,7 @@ def main(): run_matrix(matrix, exclude, build_and_test_full_or_auto) if platform.system() == "Darwin": matrix = { - "compiler": ["g++-13", "clang++"], + "compiler": ["g++-12", "clang++"], "target": ["Debug"], "std": ["11", "20"], "unwind": [ @@ -346,7 +346,7 @@ def main(): exclude = [] run_matrix(matrix, exclude, build_and_test) matrix = { - "compiler": ["g++-13", "clang++"], + "compiler": ["g++-12", "clang++"], "target": ["Debug"], "std": ["11", "20"], "config": [""] diff --git a/src/platform/mach-o.hpp b/src/platform/mach-o.hpp index 97eab07..da86aa5 100644 --- a/src/platform/mach-o.hpp +++ b/src/platform/mach-o.hpp @@ -5,6 +5,11 @@ #include "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 @@ -12,18 +17,9 @@ #include #include #include - -#if defined(__aarch64__) - #define CURRENT_CPU CPU_TYPE_ARM64 -#elif defined(__arm__) && defined(__thumb__) - #define CURRENT_CPU CPU_TYPE_ARM -#elif defined(__amd64__) - #define CURRENT_CPU CPU_TYPE_X86_64 -#elif defined(__i386__) - #define CURRENT_CPU CPU_TYPE_I386 -#else - #error "Unknown CPU architecture" -#endif +#include +#include +#include namespace cpptrace { namespace detail { @@ -41,6 +37,10 @@ namespace detail { } } + static bool is_fat_magic(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/ static bool is_magic_64(uint32_t magic) { @@ -67,8 +67,20 @@ namespace detail { swap_segment_command(&segment, NX_UnknownByteOrder); } + #ifdef __LP64__ + #define LP(x) x##_64 + #else + #define LP(x) x + #endif + template - static uintptr_t macho_get_text_vmaddr_mach(FILE* obj_file, off_t offset, bool should_swap) { + static optional macho_get_text_vmaddr_mach( + FILE* obj_file, + const std::string& obj_path, + off_t offset, + bool should_swap, + bool allow_arch_mismatch + ) { static_assert(Bits == 32 || Bits == 64, "Unexpected Bits argument"); using Mach_Header = typename std::conditional::type; using Segment_Command = typename std::conditional::type; @@ -76,12 +88,28 @@ namespace detail { off_t load_commands_offset = offset; size_t header_size = sizeof(Mach_Header); Mach_Header header = load_bytes(obj_file, offset); - if(header.cputype != CURRENT_CPU) { - return 0; - } if(should_swap) { swap_mach_header(header); } + thread_local static struct LP(mach_header)* mhp = _NSGetMachExecuteHeader(); + //fprintf( + // stderr, + // "----> %d %d; %d %d\n", + // header.cputype, + // mhp->cputype, + // static_cast(mhp->cpusubtype & ~CPU_SUBTYPE_MASK), + // header.cpusubtype + //); + if( + header.cputype != mhp->cputype || + static_cast(mhp->cpusubtype & ~CPU_SUBTYPE_MASK) != header.cpusubtype + ) { + if(allow_arch_mismatch) { + return nullopt; + } else { + CPPTRACE_VERIFY(false, "Mach-O file cpu type and subtype do not match current machine " + obj_path); + } + } ncmds = header.ncmds; load_commands_offset += header_size; // iterate load commands @@ -91,6 +119,7 @@ namespace detail { if(should_swap) { swap_load_command(&cmd, NX_UnknownByteOrder); } + // TODO: This is a mistake? Need to check cmd.cmd == LC_SEGMENT_64 / cmd.cmd == LC_SEGMENT Segment_Command segment = load_bytes(obj_file, actual_offset); if(should_swap) { swap_segment_command(segment); @@ -105,7 +134,7 @@ namespace detail { return 0; } - static uintptr_t macho_get_text_vmaddr_fat(FILE* obj_file, bool should_swap) { + static uintptr_t macho_get_text_vmaddr_fat(FILE* obj_file, const std::string& obj_path, bool should_swap) { size_t header_size = sizeof(fat_header); size_t arch_size = sizeof(fat_arch); fat_header header = load_bytes(obj_file, 0); @@ -113,7 +142,7 @@ namespace detail { swap_fat_header(&header, NX_UnknownByteOrder); } off_t arch_offset = (off_t)header_size; - uintptr_t text_vmaddr = 0; + optional text_vmaddr; for(uint32_t i = 0; i < header.nfat_arch; i++) { fat_arch arch = load_bytes(obj_file, arch_offset); if(should_swap) { @@ -125,27 +154,31 @@ namespace detail { if(is_magic_64(magic)) { text_vmaddr = macho_get_text_vmaddr_mach<64>( obj_file, + obj_path, mach_header_offset, - should_swap_bytes(magic) + should_swap_bytes(magic), + true ); } else { text_vmaddr = macho_get_text_vmaddr_mach<32>( obj_file, + obj_path, mach_header_offset, - should_swap_bytes(magic) + should_swap_bytes(magic), + true ); } - if(text_vmaddr != 0) { - return text_vmaddr; + if(text_vmaddr.has_value()) { + return text_vmaddr.unwrap(); } } // If this is reached... something went wrong. The cpu we're on wasn't found. - // TODO: Disabled temporarily for CI - /////CPPTRACE_VERIFY(false, "Couldn't find appropriate architecture in fat Mach-O"); + CPPTRACE_VERIFY(false, "Couldn't find appropriate architecture in fat Mach-O"); return 0; } static uintptr_t macho_get_text_vmaddr(const std::string& obj_path) { + //fprintf(stderr, "--%s--\n", obj_path.c_str()); auto file = raii_wrap(fopen(obj_path.c_str(), "rb"), file_deleter); if(file == nullptr) { throw file_error("Unable to read object file " + obj_path); @@ -155,18 +188,70 @@ namespace detail { bool is_64 = is_magic_64(magic); bool should_swap = should_swap_bytes(magic); if(magic == FAT_MAGIC || magic == FAT_CIGAM) { - return macho_get_text_vmaddr_fat(file, should_swap); + return macho_get_text_vmaddr_fat(file, obj_path, should_swap); } else { if(is_64) { - return macho_get_text_vmaddr_mach<64>(file, 0, should_swap); + return macho_get_text_vmaddr_mach<64>(file, obj_path, 0, should_swap, false).unwrap(); } else { - return macho_get_text_vmaddr_mach<32>(file, 0, should_swap); + return macho_get_text_vmaddr_mach<32>(file, obj_path, 0, should_swap, false).unwrap(); } } } + + inline bool macho_is_fat(const std::string& obj_path) { + auto file = raii_wrap(fopen(obj_path.c_str(), "rb"), file_deleter); + if(file == nullptr) { + throw file_error("Unable to read object file " + obj_path); + } + uint32_t magic = load_bytes(file, 0); + return is_fat_magic(magic); + } + + struct fat_info { + uint32_t offset; + uint32_t size; + }; + + // returns offset, file size + // TODO: Code duplication with macho_get_text_vmaddr_fat + inline fat_info get_fat_macho_information(const std::string& obj_path) { + auto file = raii_wrap(fopen(obj_path.c_str(), "rb"), file_deleter); + if(file == nullptr) { + throw file_error("Unable to read object file " + obj_path); + } + uint32_t magic = load_bytes(file, 0); + CPPTRACE_VERIFY(is_fat_magic(magic)); + bool should_swap = should_swap_bytes(magic); + size_t header_size = sizeof(fat_header); + size_t arch_size = sizeof(fat_arch); + fat_header header = load_bytes(file, 0); + if(should_swap) { + swap_fat_header(&header, NX_UnknownByteOrder); + } + off_t arch_offset = (off_t)header_size; + thread_local static struct LP(mach_header)* mhp = _NSGetMachExecuteHeader(); + for(uint32_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); + } + arch_offset += arch_size; + if( + arch.cputype == mhp->cputype && + static_cast(mhp->cpusubtype & ~CPU_SUBTYPE_MASK) == arch.cpusubtype + ) { + return { arch.offset, arch.size }; + } + } + // If this is reached... something went wrong. The cpu we're on wasn't found. + CPPTRACE_VERIFY(false, "Couldn't find appropriate architecture in fat Mach-O"); + return { 0, 0 }; + } } } +#pragma GCC diagnostic pop + #endif #endif diff --git a/src/platform/utils.hpp b/src/platform/utils.hpp index eb92667..9c5fd7b 100644 --- a/src/platform/utils.hpp +++ b/src/platform/utils.hpp @@ -152,7 +152,7 @@ namespace detail { 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> + template::value, int>::type = 0> T load_bytes(FILE* obj_file, off_t offset) { T object; CPPTRACE_VERIFY(fseek(obj_file, offset, SEEK_SET) == 0, "fseek error"); @@ -260,7 +260,7 @@ namespace detail { return holds_value; } - operator bool() const { + explicit operator bool() const { return holds_value; } @@ -347,7 +347,8 @@ namespace detail { typename T, typename D, typename std::enable_if< - std::is_same()(std::declval())), void>::value, int + std::is_same()(std::declval())), void>::value, + int >::type = 0 > class raii_wrapper { @@ -362,7 +363,7 @@ namespace detail { raii_wrapper& operator=(raii_wrapper&&) = delete; raii_wrapper& operator=(const raii_wrapper&) = delete; ~raii_wrapper() { - if(deleter) { + if(deleter.has_value()) { deleter.unwrap()(obj); } } @@ -378,7 +379,8 @@ namespace detail { typename T, typename D, typename std::enable_if< - std::is_same()(std::declval())), void>::value, int + std::is_same()(std::declval())), void>::value, + int >::type = 0 > raii_wrapper raii_wrap(T&& obj, D deleter) { diff --git a/src/symbols/symbols_with_libdwarf.cpp b/src/symbols/symbols_with_libdwarf.cpp index f505171..7d98a42 100644 --- a/src/symbols/symbols_with_libdwarf.cpp +++ b/src/symbols/symbols_with_libdwarf.cpp @@ -515,6 +515,9 @@ namespace libdwarf { std::unordered_map line_contexts; std::unordered_map> subprograms_cache; + // Exists only for cleaning up an awful mach-o hack + std::string tmp_object_path; + CPPTRACE_FORCE_NO_INLINE_FOR_PROFILING dwarf_resolver(const std::string& object_path) { obj_path = object_path; @@ -522,6 +525,27 @@ namespace libdwarf { if(directory_exists(obj_path + ".dSYM")) { obj_path += ".dSYM/Contents/Resources/DWARF/" + basename(object_path); } + if(macho_is_fat(obj_path)) { + // If the object is fat, we'll copy out the mach-o object we care about + // Awful hack until libdwarf supports fat mach + auto sub_object = get_fat_macho_information(obj_path); + char tmp_template[] = "/tmp/tmp.cpptrace.XXXXXX"; + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wdeprecated-declarations" + CPPTRACE_VERIFY(mktemp(tmp_template) != nullptr); + #pragma GCC diagnostic pop + std::string tmp_path = tmp_template; + auto file = raii_wrap(fopen(obj_path.c_str(), "rb"), file_deleter); + auto tmp = raii_wrap(fopen(tmp_path.c_str(), "wb"), file_deleter); + CPPTRACE_VERIFY(file != nullptr); + CPPTRACE_VERIFY(tmp != nullptr); + std::unique_ptr buffer(new char[sub_object.size]); + CPPTRACE_VERIFY(fseek(file, sub_object.offset, SEEK_SET) == 0); + CPPTRACE_VERIFY(fread(buffer.get(), 1, sub_object.size, file) == sub_object.size); + CPPTRACE_VERIFY(fwrite(buffer.get(), 1, sub_object.size, tmp) == sub_object.size); + obj_path = tmp_path; + tmp_object_path = std::move(tmp_path); + } #endif Dwarf_Ptr errarg = 0; @@ -553,6 +577,10 @@ namespace libdwarf { // subprograms_cache needs to be destroyed before dbg otherwise there will be another use after free subprograms_cache.clear(); dwarf_finish(dbg); + // cleanup awful mach-o hack + if(!tmp_object_path.empty()) { + unlink(tmp_object_path.c_str()); + } } // walk die list, callback is called on each die and should return true to @@ -616,7 +644,7 @@ namespace libdwarf { } else if(auto linkage_name = die.get_string_attribute(DW_AT_name)) { name = std::move(linkage_name); } - if(name) { + if(name.has_value()) { frame.symbol = std::move(name).unwrap(); } else { if(die.has_attr(DW_AT_specification)) {