From b3a6c044c41b34e6e12d1a1f7995cfb7527e37d9 Mon Sep 17 00:00:00 2001 From: Oleksandr Nemesh Date: Thu, 14 Mar 2024 02:11:20 +0200 Subject: [PATCH 1/2] crashlog: add method names from bindings Adds a basic system to read a "GeometryDash.bro" file (currently has to be placed manually into `Geometry Dash/geode` directory) and get function names from it to include them in the stacktrace. --- loader/src/platform/windows/crashlog.cpp | 76 ++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/loader/src/platform/windows/crashlog.cpp b/loader/src/platform/windows/crashlog.cpp index 66f58dd46..2536cc28a 100644 --- a/loader/src/platform/windows/crashlog.cpp +++ b/loader/src/platform/windows/crashlog.cpp @@ -90,6 +90,67 @@ static Mod* modFromAddress(PVOID exceptionAddress) { return nullptr; } +static uintptr_t findMethodStart(void const* address, size_t maxSearch = 0x1000) { + // Backtrack until we hit a 0xCC (int3) instruction + uintptr_t start = reinterpret_cast(address); + while (start > reinterpret_cast(address) - maxSearch) { + if (*reinterpret_cast(start) == 0xCC) { + return start + 1; + } + start--; + } + return reinterpret_cast(address); +} + +static std::string formatMethodName(std::string const& className, std::string const& line) { + // strip everything after first '=' sign to make sure we don't mess with text in the comments + auto eq = line.find('='); + if (eq != std::string::npos) { + return formatMethodName(className, line.substr(0, eq)); + } + + // Get only the method name (last word before '(') + auto end = line.find('('); + if (end == std::string::npos) { + return line; // this should never happen, but just in case + } + auto start = line.rfind(' ', end); + if (start == std::string::npos) { + return line; + } + + return className + "::" + line.substr(start + 1, end - start - 1); +} + +static bool findBromaMethod(uintptr_t methodOffset, std::string& output) { + auto bromaPath = dirs::getGeodeDir() / "GeometryDash.bro"; + if (!ghc::filesystem::exists(bromaPath)) { + return false; + } + + std::ifstream file(bromaPath); + if (!file.is_open()) { + return false; + } + + // Search for the method offset in the bindings file + std::string searchFilter = fmt::format("win 0x{:x}", methodOffset); + std::string line, className; + while (std::getline(file, line)) { + if (line.find("class") != std::string::npos) { + // Save the last class name to use it later + className = line.substr(6, line.find(' ', 6) - 6); + } + else if (line.find(searchFilter) != std::string::npos) { + // Found the method, format the output + output = formatMethodName(className, line); + return true; + } + } + + return false; +} + static void printAddr(std::ostream& stream, void const* addr, bool fullPath = true) { HMODULE module = nullptr; @@ -135,6 +196,21 @@ static void printAddr(std::ostream& stream, void const* addr, bool fullPath = tr } stream << ")"; + } else if (module == GetModuleHandle(nullptr)) { + uintptr_t methodStart = findMethodStart(addr); + if (methodStart == reinterpret_cast(addr)) { + return; + } + + uintptr_t baseOffset = methodStart - reinterpret_cast(module); + uintptr_t methodOffset = reinterpret_cast(addr) - methodStart; + + std::string bromaOutput; + if (findBromaMethod(baseOffset, bromaOutput)) { + stream << " (" << bromaOutput << " + " << std::hex << methodOffset << std::dec << ")"; + } else { + stream << " ( + " << methodOffset << std::dec << ")"; + } } } } From 163ac68061a444e861db99a00fa597e5405d8625 Mon Sep 17 00:00:00 2001 From: Oleksandr Nemesh Date: Wed, 17 Apr 2024 17:13:50 +0300 Subject: [PATCH 2/2] use CodegenData.txt instead of broma file this change adds a simple regex-based codegen into CMakeLists.txt, which creates a new header file containing an unordered_map with all function addresses mapped to their respective names --- CMakeLists.txt | 16 +++++++ loader/src/platform/windows/crashlog.cpp | 56 ++---------------------- 2 files changed, 20 insertions(+), 52 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3f50d30d8..26af60519 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -301,6 +301,22 @@ if (PROJECT_IS_TOP_LEVEL) if (SUPPORTS_W_NO_EVERYTHING) target_compile_options(geode-loader PRIVATE -Wno-inconsistent-missing-override) endif() + # On windows, prepare "CodegenData.txt" for embedding it into the binary (used for crashlog symbols) + if (WIN32) + file(STRINGS ${CMAKE_BINARY_DIR}/bindings/bindings/Geode/CodegenData.txt CODEGEN_DATA) + set(CODEGEN_DATA_INCLUDE "#pragma once\n#include \n\nstatic std::unordered_map s_codegenData = {\n") + foreach(line IN LISTS CODEGEN_DATA) + string(REGEX MATCH "([a-zA-Z0-9:_]+) - 0x([0-9a-fA-F]+)" MATCHED ${line}) + if (NOT MATCHED) + continue() + endif() + set(FUNC_NAME ${CMAKE_MATCH_1}) + set(FUNC_ADDR ${CMAKE_MATCH_2}) + set(CODEGEN_DATA_INCLUDE "${CODEGEN_DATA_INCLUDE}\t{0x${FUNC_ADDR}, \"${FUNC_NAME}\"},\n") + endforeach() + set(CODEGEN_DATA_INCLUDE "${CODEGEN_DATA_INCLUDE}}\;") + file(WRITE ${CMAKE_BINARY_DIR}/bindings/bindings/Geode/CodegenData.hpp ${CODEGEN_DATA_INCLUDE}) + endif() elseif (EXISTS ${GEODE_PLATFORM_BIN_PATH}) target_link_libraries(${PROJECT_NAME} INTERFACE "${GEODE_PLATFORM_BIN_PATH}") if (NOT GEODE_DISABLE_PRECOMPILED_HEADERS) diff --git a/loader/src/platform/windows/crashlog.cpp b/loader/src/platform/windows/crashlog.cpp index 06d466ed2..4a06ac16c 100644 --- a/loader/src/platform/windows/crashlog.cpp +++ b/loader/src/platform/windows/crashlog.cpp @@ -19,6 +19,7 @@ #include #include #include "ehdata_structs.hpp" +#include using namespace geode::prelude; @@ -102,55 +103,6 @@ static uintptr_t findMethodStart(void const* address, size_t maxSearch = 0x1000) return reinterpret_cast(address); } -static std::string formatMethodName(std::string const& className, std::string const& line) { - // strip everything after first '=' sign to make sure we don't mess with text in the comments - auto eq = line.find('='); - if (eq != std::string::npos) { - return formatMethodName(className, line.substr(0, eq)); - } - - // Get only the method name (last word before '(') - auto end = line.find('('); - if (end == std::string::npos) { - return line; // this should never happen, but just in case - } - auto start = line.rfind(' ', end); - if (start == std::string::npos) { - return line; - } - - return className + "::" + line.substr(start + 1, end - start - 1); -} - -static bool findBromaMethod(uintptr_t methodOffset, std::string& output) { - auto bromaPath = dirs::getGeodeDir() / "GeometryDash.bro"; - if (!ghc::filesystem::exists(bromaPath)) { - return false; - } - - std::ifstream file(bromaPath); - if (!file.is_open()) { - return false; - } - - // Search for the method offset in the bindings file - std::string searchFilter = fmt::format("win 0x{:x}", methodOffset); - std::string line, className; - while (std::getline(file, line)) { - if (line.find("class") != std::string::npos) { - // Save the last class name to use it later - className = line.substr(6, line.find(' ', 6) - 6); - } - else if (line.find(searchFilter) != std::string::npos) { - // Found the method, format the output - output = formatMethodName(className, line); - return true; - } - } - - return false; -} - static void printAddr(std::ostream& stream, void const* addr, bool fullPath = true) { HMODULE module = nullptr; @@ -205,9 +157,9 @@ static void printAddr(std::ostream& stream, void const* addr, bool fullPath = tr uintptr_t baseOffset = methodStart - reinterpret_cast(module); uintptr_t methodOffset = reinterpret_cast(addr) - methodStart; - std::string bromaOutput; - if (findBromaMethod(baseOffset, bromaOutput)) { - stream << " (" << bromaOutput << " + " << std::hex << methodOffset << std::dec << ")"; + auto it = s_codegenData.find(baseOffset); + if (it != s_codegenData.end()) { + stream << " (" << it->second << " + " << std::hex << methodOffset << std::dec << ")"; } else { stream << " ( + " << methodOffset << std::dec << ")"; }