From ee5f99fdb494421bc9f4c8bd24375e32b1e2f1e6 Mon Sep 17 00:00:00 2001 From: Michael Scire Date: Thu, 10 Mar 2022 15:10:13 -0800 Subject: [PATCH] Implement working macOS backtrace symbolization --- .../impl/diag_dump_stack_trace.os.generic.cpp | 2 +- .../diag/impl/diag_symbol_impl.os.generic.cpp | 7 - .../diag/impl/diag_symbol_impl.os.macos.cpp | 298 +++++++++++++++++- 3 files changed, 293 insertions(+), 14 deletions(-) diff --git a/libraries/libstratosphere/source/diag/impl/diag_dump_stack_trace.os.generic.cpp b/libraries/libstratosphere/source/diag/impl/diag_dump_stack_trace.os.generic.cpp index 560b53a4c..1b0543294 100644 --- a/libraries/libstratosphere/source/diag/impl/diag_dump_stack_trace.os.generic.cpp +++ b/libraries/libstratosphere/source/diag/impl/diag_dump_stack_trace.os.generic.cpp @@ -30,7 +30,7 @@ namespace ams::diag::impl { for (size_t i = 0; i < num_items; ++i) { char symbol_name[0x200]; if (const uintptr_t symbol_base = ::ams::diag::GetSymbolName(symbol_name, sizeof(symbol_name), backtrace[i] - 1); symbol_base != 0) { - AMS_SDK_LOG("0x%016" PRIX64 " [ %s+0x%" PRIX64 " ]\n", static_cast(backtrace[i]), symbol_name, static_cast(backtrace[i] - (symbol_base + 1))); + AMS_SDK_LOG("0x%016" PRIX64 " [ %s+0x%" PRIX64 " ]\n", static_cast(backtrace[i]), symbol_name, static_cast(backtrace[i] - symbol_base)); } else { AMS_SDK_LOG("0x%016" PRIX64 " [ unknown ]\n", static_cast(backtrace[i])); } diff --git a/libraries/libstratosphere/source/diag/impl/diag_symbol_impl.os.generic.cpp b/libraries/libstratosphere/source/diag/impl/diag_symbol_impl.os.generic.cpp index 888fc7a5a..a5a8afe49 100644 --- a/libraries/libstratosphere/source/diag/impl/diag_symbol_impl.os.generic.cpp +++ b/libraries/libstratosphere/source/diag/impl/diag_symbol_impl.os.generic.cpp @@ -263,13 +263,6 @@ namespace ams::diag::impl { return; } } - #elif defined(ATMOSPHERE_OS_MACOS) - { - if (_NSGetExecutablePath(dst, dst_size) != 0) { - dst[0] = 0; - return; - } - } #else #error "Unknown OS for BfdHelper GetExecutablePath" #endif diff --git a/libraries/libstratosphere/source/diag/impl/diag_symbol_impl.os.macos.cpp b/libraries/libstratosphere/source/diag/impl/diag_symbol_impl.os.macos.cpp index a0f1f9848..a2e143821 100644 --- a/libraries/libstratosphere/source/diag/impl/diag_symbol_impl.os.macos.cpp +++ b/libraries/libstratosphere/source/diag/impl/diag_symbol_impl.os.macos.cpp @@ -16,18 +16,304 @@ #include #include "diag_symbol_impl.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +extern "C" { + + void __module_offset_helper() { /* ... */ } + +} + namespace ams::diag::impl { + namespace { + + class CurrentExecutableHelper { + private: + struct SymbolInfo { + uintptr_t address; + const char *name; + }; + private: + os::NativeHandle m_fd; + void *m_file_map; + size_t m_file_size; + SymbolInfo *m_symbols; + size_t m_num_symbol; + const char *m_module_name; + uintptr_t m_module_address; + size_t m_module_size; + uintptr_t m_module_displacement; + private: + CurrentExecutableHelper() : m_fd(-1), m_file_map(nullptr), m_file_size(0), m_symbols(nullptr), m_num_symbol(0), m_module_name(nullptr), m_module_address(0), m_module_size(0), m_module_displacement(0) { + /* Get the current executable name. */ + char exe_path[4_KB] = {}; + GetExecutablePath(exe_path, sizeof(exe_path)); + + /* Open the current executable. */ + os::NativeHandle fd; + do { + fd = ::open(exe_path, O_RDONLY); + } while (fd < 0 && errno == EINTR); + if (fd < 0) { + return; + } + ON_SCOPE_EXIT { if (fd >= 0) { s32 ret; do { ret = ::close(fd); } while (ret < 0 && errno == EINTR); } }; + + /* Get the file size. */ + struct stat st; + if (fstat(fd, std::addressof(st)) < 0) { + return; + } + + /* Check that the file can be mapped. */ + const size_t exe_size = st.st_size; + if (exe_size == 0) { + return; + } + + /* Map the executable. */ + void *exe_map = mmap(nullptr, exe_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (exe_map == MAP_FAILED) { + return; + } + ON_SCOPE_EXIT { if (exe_map != nullptr) { munmap(exe_map, exe_size); } }; + + /* Get the file's u32 magic. */ + const uintptr_t exe_start = reinterpret_cast(exe_map); + const u32 magic = *reinterpret_cast(exe_start); + + /* Get/parse the mach header. */ + u32 ncmds; + bool is_64; + if (magic == MH_MAGIC) { + const auto *header = reinterpret_cast(exe_start); + ncmds = header->ncmds; + is_64 = false; + } else if (magic == MH_MAGIC_64) { + const auto *header = reinterpret_cast(exe_start); + ncmds = header->ncmds; + is_64 = true; + } else { + return; + } + + /* Find the symbol load command. */ + const auto *lc = reinterpret_cast(exe_start + (is_64 ? sizeof(struct mach_header_64) : sizeof(struct mach_header))); + for (u32 i = 0; i < ncmds; ++i) { + /* If we encounter the symbol table, parse it. */ + if (lc->cmd == LC_SYMTAB) { + if (is_64) { + this->ParseSymbolTable(exe_start, reinterpret_cast(lc)); + } else { + + this->ParseSymbolTable(exe_start, reinterpret_cast(lc)); + } + break; + } else if (lc->cmd == LC_SEGMENT) { + const auto *sc = reinterpret_cast(lc); + if (std::strcmp(sc->segname, "__TEXT") == 0) { + AMS_ASSERT(m_module_address == 0); + m_module_address = sc->vmaddr; + m_module_size = sc->vmsize; + AMS_ASSERT(m_module_address != 0); + } + } else if (lc->cmd == LC_SEGMENT_64) { + const auto *sc = reinterpret_cast(lc); + if (std::strcmp(sc->segname, "__TEXT") == 0) { + AMS_ASSERT(m_module_address == 0); + m_module_address = sc->vmaddr; + m_module_size = sc->vmsize; + AMS_ASSERT(m_module_address != 0); + } + } + + /* Advance to the next load command. */ + lc = reinterpret_cast(reinterpret_cast(lc) + lc->cmdsize); + } + + for (size_t i = 0; i < m_num_symbol; ++i) { + if (std::strcmp(m_symbols[i].name, "___module_offset_helper") == 0) { + m_module_displacement = reinterpret_cast(&__module_offset_helper) - m_symbols[i].address; + break; + } + } + + if (m_module_address > 0 && m_module_size > 0 && m_num_symbol > 0) { + std::swap(m_fd, fd); + std::swap(m_file_map, exe_map); + m_file_size = exe_size; + } + } + + ~CurrentExecutableHelper() { + if (m_file_map != nullptr) { + munmap(m_file_map, m_file_size); + } + + if (m_fd >= 0) { + s32 ret; + do { ret = ::close(m_fd); } while (ret < 0 && errno == EINTR); + } + } + public: + static CurrentExecutableHelper &GetInstance() { + AMS_FUNCTION_LOCAL_STATIC(CurrentExecutableHelper, s_current_executable_helper_instance); + return s_current_executable_helper_instance; + } + private: + template + void ParseSymbolTable(uintptr_t exe_start, const struct symtab_command *c) { + /* Check pre-conditions. */ + AMS_ASSERT(m_fd == -1); + AMS_ASSERT(m_file_map == nullptr); + AMS_ASSERT(m_symbols == nullptr); + + /* Get the strtab/symtab. */ + const auto *symtab = reinterpret_cast(exe_start + c->symoff); + const char *strtab = reinterpret_cast(exe_start + c->stroff); + + /* Determine the number of functions. */ + size_t funcs = 0; + + for (size_t i = 0; i < c->nsyms; ++i) { + if (symtab[i].n_type != N_FUN || symtab[i].n_sect == NO_SECT) { + continue; + } + + ++funcs; + } + + /* Allocate functions. */ + m_symbols = reinterpret_cast(std::malloc(sizeof(SymbolInfo) * funcs)); + if (m_symbols == nullptr) { + return; + } + + /* Set all symbols. */ + m_num_symbol = 0; + for (size_t i = 0; i < c->nsyms; ++i) { + if (symtab[i].n_type != N_FUN || symtab[i].n_sect == NO_SECT) { + continue; + } + + m_symbols[m_num_symbol].address = symtab[i].n_value; + m_symbols[m_num_symbol].name = strtab + symtab[i].n_un.n_strx; + + ++m_num_symbol; + } + AMS_ASSERT(m_num_symbol == funcs); + + /* Sort the symbols. */ + std::sort(m_symbols + 0, m_symbols + m_num_symbol, [] (const SymbolInfo &lhs, const SymbolInfo &rhs) { + return lhs.address < rhs.address; + }); + } + + size_t GetSymbolSizeImpl(const SymbolInfo *symbol) const { + /* Do our best to guess. */ + if (symbol != m_symbols + m_num_symbol - 1) { + return (symbol + 1)->address - symbol->address; + } else if (m_module_address + m_module_size >= symbol->address) { + return m_module_address + m_module_size - symbol->address; + } else { + return 0; + } + } + + const SymbolInfo *GetBestSymbol(uintptr_t address) const { + address -= m_module_displacement; + + const SymbolInfo *best_symbol = std::lower_bound(m_symbols + 0, m_symbols + m_num_symbol, address, [](const SymbolInfo &lhs, uintptr_t rhs) { + return lhs.address < rhs; + }); + + if (best_symbol == m_symbols + m_num_symbol) { + return nullptr; + } + + if (best_symbol->address != address && best_symbol > m_symbols) { + --best_symbol; + } + + const auto vma = best_symbol->address; + const auto end = vma + this->GetSymbolSizeImpl(best_symbol); + + if (vma <= address && address < end) { + return best_symbol; + } else { + return nullptr; + } + } + public: + uintptr_t GetSymbolName(char *dst, size_t dst_size, uintptr_t address) const { + if (m_fd < 0) { + return 0; + } + + /* Get the symbol. */ + const auto *symbol = this->GetBestSymbol(address); + if (symbol == nullptr) { + return 0; + } + + /* Print the symbol. */ + const char *name = symbol->name; + + int cpp_name_status = 0; + if (char *demangled = abi::__cxa_demangle(name, nullptr, 0, std::addressof(cpp_name_status)); cpp_name_status == 0) { + AMS_ASSERT(demangled != nullptr); + util::TSNPrintf(dst, dst_size, "%s", demangled); + std::free(demangled); + } else { + util::TSNPrintf(dst, dst_size, "%s", name); + } + + return symbol->address + m_module_displacement; + } + + size_t GetSymbolSize(uintptr_t address) const { + if (m_fd < 0) { + return 0; + } + + /* Get the symbol. */ + const auto *symbol = this->GetBestSymbol(address); + if (symbol == nullptr) { + return 0; + } + + return this->GetSymbolSizeImpl(symbol); + } + private: + static void GetExecutablePath(char *dst, size_t dst_size) { + u32 len = dst_size; + if (_NSGetExecutablePath(dst, std::addressof(len)) != 0) { + dst[0] = 0; + return; + } + } + }; + + } + uintptr_t GetSymbolNameImpl(char *dst, size_t dst_size, uintptr_t address) { - /* TODO: How should we do this on macOS? */ - AMS_UNUSED(dst, dst_size, address); - return 0; + return CurrentExecutableHelper::GetInstance().GetSymbolName(dst, dst_size, address); } size_t GetSymbolSizeImpl(uintptr_t address) { - /* TODO: How should we do this on macOS? */ - AMS_UNUSED(address); - return 0; + return CurrentExecutableHelper::GetInstance().GetSymbolSize(address); } }