diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 32d6fe60..024432d0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -35,10 +35,6 @@ elseif(UNIX) add_compile_options(-Wno-ambiguous-reversed-operator) endif() - if(NOT APPLE) - add_link_options(-rdynamic) - endif() - add_compile_options(-Wno-multichar -Wno-invalid-offsetof -Wno-switch -Wno-ignored-attributes -Wno-deprecated-enum-enum-conversion) endif() diff --git a/src/Common/CMakeLists.txt b/src/Common/CMakeLists.txt index ca7ea238..7e5825f1 100644 --- a/src/Common/CMakeLists.txt +++ b/src/Common/CMakeLists.txt @@ -43,6 +43,13 @@ PRIVATE ) endif() +if(UNIX AND NOT APPLE) + target_sources(CemuCommon PRIVATE + ExceptionHandler/ELFSymbolTable.cpp + ExceptionHandler/ELFSymbolTable.h + ) +endif() + # All the targets wanting to use the precompiled.h header # have to link to CemuCommon target_precompile_headers(CemuCommon PUBLIC precompiled.h) diff --git a/src/Common/ExceptionHandler/ELFSymbolTable.cpp b/src/Common/ExceptionHandler/ELFSymbolTable.cpp new file mode 100644 index 00000000..0f297bdd --- /dev/null +++ b/src/Common/ExceptionHandler/ELFSymbolTable.cpp @@ -0,0 +1,108 @@ +#include "Common/ExceptionHandler/ELFSymbolTable.h" +#include "Common/FileStream.h" +#include +#include + +uint16 ELFSymbolTable::FindSection(int type, const std::string_view& name) +{ + if (!shTable || !shStrTable) + return 0; + + for (uint16 i = 0; i < header->e_shnum; ++i) + { + auto& entry = shTable[i]; + if(entry.sh_type == type && std::string_view{&shStrTable[entry.sh_name]} == name) + { + return i; + } + } + return 0; +} + +void* ELFSymbolTable::SectionPointer(uint16 index) +{ + return SectionPointer(shTable[index]); +} + +void* ELFSymbolTable::SectionPointer(const Elf64_Shdr& section) +{ + return (void*)(mappedExecutable + section.sh_offset); +} + +ELFSymbolTable::ELFSymbolTable() +{ + // create file handle + int fd = open("/proc/self/exe", O_RDONLY); + if (!fd) + return; + + // retrieve file size. + struct stat filestats; + if (fstat(fd, &filestats)) + { + close(fd); + return; + } + mappedExecutableSize = filestats.st_size; + + // attempt to map the file + mappedExecutable = (uint8*)(mmap(nullptr, mappedExecutableSize, PROT_READ, MAP_PRIVATE, fd, 0)); + close(fd); + if (!mappedExecutable) + return; + + // verify signature + header = (Elf64_Ehdr*)(mappedExecutable); + constexpr uint8 signature[] = {0x7f, 0x45, 0x4c, 0x46}; + for (size_t i = 0; i < 4; ++i) + { + if (signature[i] != header->e_ident[i]) + { + return; + } + } + + shTable = (Elf64_Shdr*)(mappedExecutable + header->e_shoff); + + Elf64_Shdr& shStrn = shTable[header->e_shstrndx]; + shStrTable = (char*)(mappedExecutable + shStrn.sh_offset); + + strTable = (char*)SectionPointer(FindSection(SHT_STRTAB, ".strtab")); + + Elf64_Shdr& symTabShdr = shTable[FindSection(SHT_SYMTAB, ".symtab")]; + if (symTabShdr.sh_entsize == 0) + return; + + symTableLen = symTabShdr.sh_size / symTabShdr.sh_entsize; + symTable = (Elf64_Sym*)(SectionPointer(symTabShdr)); +} + +ELFSymbolTable::~ELFSymbolTable() +{ + if (mappedExecutable) + munmap(mappedExecutable, mappedExecutableSize); +} + +std::string_view ELFSymbolTable::OffsetToSymbol(uint64 ptr, uint64& fromStart) const +{ + if(!symTable || !strTable) + { + fromStart = -1; + return {}; + } + + for (auto entry = symTable+1; entry < symTable+symTableLen; ++entry) + { + if (ELF64_ST_TYPE(entry->st_info) != STT_FUNC) + continue; + auto begin = entry->st_value; + auto size = entry->st_size; + if(ptr >= begin && ptr < begin+size) + { + fromStart = ptr-begin; + return &strTable[entry->st_name]; + } + } + fromStart = -1; + return {}; +} diff --git a/src/Common/ExceptionHandler/ELFSymbolTable.h b/src/Common/ExceptionHandler/ELFSymbolTable.h new file mode 100644 index 00000000..89248bbb --- /dev/null +++ b/src/Common/ExceptionHandler/ELFSymbolTable.h @@ -0,0 +1,32 @@ +#pragma once +#include +#include + +class ELFSymbolTable +{ +public: + std::string_view OffsetToSymbol(uint64 ptr, uint64& fromStart) const; + + ELFSymbolTable(); + ~ELFSymbolTable(); +private: + uint8* mappedExecutable = nullptr; + size_t mappedExecutableSize = 0; + + Elf64_Ehdr* header = nullptr; + + Elf64_Shdr* shTable = nullptr; + char* shStrTable = nullptr; + + Elf64_Sym* symTable = nullptr; + uint64 symTableLen = 0; + char* strTable = nullptr; + + uint16 FindSection(int type, const std::string_view& name); + + void* SectionPointer (uint16 index); + void* SectionPointer(const Elf64_Shdr& section); + + // ownership of mapped memory, cannot copy. + ELFSymbolTable(const ELFSymbolTable&) = delete; +}; diff --git a/src/Common/ExceptionHandler/ExceptionHandler_posix.cpp b/src/Common/ExceptionHandler/ExceptionHandler_posix.cpp index 86d83bb3..3a5620c2 100644 --- a/src/Common/ExceptionHandler/ExceptionHandler_posix.cpp +++ b/src/Common/ExceptionHandler/ExceptionHandler_posix.cpp @@ -2,41 +2,60 @@ #include #include #include - #include "config/CemuConfig.h" +#include "util/helpers/StringHelpers.h" +#if BOOST_OS_LINUX +#include "ELFSymbolTable.h" +#endif + +#if BOOST_OS_LINUX void demangleAndPrintBacktrace(char** backtrace, size_t size) { + ELFSymbolTable symTable; for (char** i = backtrace; i < backtrace + size; i++) { - std::string traceLine{*i}; + std::string_view traceLine{*i}; + + // basic check to see if the backtrace line matches expected format size_t parenthesesOpen = traceLine.find_last_of('('); size_t parenthesesClose = traceLine.find_last_of(')'); size_t offsetPlus = traceLine.find_last_of('+'); if (!parenthesesOpen || !parenthesesClose || !offsetPlus || offsetPlus < parenthesesOpen || offsetPlus > parenthesesClose) { - // something unexpected was read. fall back to default string + // fall back to default string std::cerr << traceLine << std::endl; continue; } - std::string symbolName = traceLine.substr(parenthesesOpen+1,offsetPlus-parenthesesOpen-1); - int status = -1; - char* demangled = abi::__cxa_demangle(symbolName.c_str(), nullptr, nullptr, &status); - if (demangled) + // attempt to resolve symbol from regular symbol table if missing from dynamic symbol table + uint64 newOffset = -1; + std::string_view symbolName = traceLine.substr(parenthesesOpen+1, offsetPlus-parenthesesOpen-1); + if (symbolName.empty()) { - std::cerr << traceLine.substr(0, parenthesesOpen+1); - std::cerr << demangled; - std::cerr << traceLine.substr(offsetPlus) << std::endl; - free(demangled); + uint64 symbolOffset = StringHelpers::ToInt64(traceLine.substr(offsetPlus+1,offsetPlus+1-parenthesesClose-1)); + symbolName = symTable.OffsetToSymbol(symbolOffset, newOffset); + } + + std::cerr << traceLine.substr(0, parenthesesOpen+1); + + std::cerr << boost::core::demangle(symbolName.empty() ? "" : symbolName.data()); + + // print relative or existing symbol offset. + std::cerr << '+'; + if (newOffset != -1) + { + std::cerr << std::hex << newOffset; + std::cerr << traceLine.substr(parenthesesClose) << std::endl; } else { - std::cerr << traceLine << std::endl; + std::cerr << traceLine.substr(offsetPlus+1) << std::endl; } } } +#endif // handle signals that would dump core, print stacktrace and then dump depending on config void handlerDumpingSignal(int sig) @@ -61,6 +80,7 @@ void handlerDumpingSignal(int sig) // print out all the frames to stderr fprintf(stderr, "Error: signal %d:\n", sig); +#if BOOST_OS_LINUX char** symbol_trace = backtrace_symbols(array, size); if (symbol_trace) @@ -72,6 +92,9 @@ void handlerDumpingSignal(int sig) { std::cerr << "Failed to read backtrace" << std::endl; } +#else + backtrace_symbols_fd(array, size, STDERR_FILENO); +#endif if (GetConfig().crash_dump == CrashDump::Enabled) {