From 01f100ae9596134d91846e6c3cd3d55872ec704b Mon Sep 17 00:00:00 2001 From: Jason Gauci Date: Fri, 18 Sep 2026 01:04:13 -0500 Subject: [PATCH 01/12] Fix PRCI: mingw cross-compile + addr2line + Catch2 upgrade --- .github/workflows/ci.yaml | 2 +- ust/ust_windows.hpp | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 9fa85b6..d62bdb7 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -42,7 +42,7 @@ jobs: with: msystem: MINGW64 update: true - install: mingw-w64-x86_64-gcc mingw-w64-x86_64-cmake mingw-w64-x86_64-make mingw-w64-x86_64-gdb + install: mingw-w64-x86_64-gcc mingw-w64-x86_64-cmake mingw-w64-x86_64-make mingw-w64-x86_64-gdb mingw-w64-x86_64-binutils - name: Configure (MinGW POSIX) shell: msys2 {0} diff --git a/ust/ust_windows.hpp b/ust/ust_windows.hpp index 5b03557..73ca41a 100644 --- a/ust/ust_windows.hpp +++ b/ust/ust_windows.hpp @@ -9,8 +9,8 @@ #define NOMINMAX #endif #include -#include #ifdef _MSC_VER +#include #include #pragma comment(lib, "dbghelp.lib") #pragma comment(lib, "shlwapi.lib") @@ -18,9 +18,12 @@ // MSVC: /Zi is required; CMake should enable it for RelWithDebInfo. // No additional flags can be set from header, but pragma ensures linking. #else +// MinGW (Windows GCC/Clang) does not ship the Windows SDK's DbgHelp; use +// CaptureStackBackTrace + addr2line symbolication instead. #include #include #include +#include #if defined(__MINGW32__) || defined(__MINGW64__) #define WEXITSTATUS(w) (((w) >> 8) & 0xff) #else From eaf69ea632822079e008f39b6a7200a22bc889b6 Mon Sep 17 00:00:00 2001 From: Jason Gauci Date: Fri, 18 Sep 2026 01:19:53 -0500 Subject: [PATCH 02/12] Fix MINGW cross-compile: detect x86_64-w64-mingw32 compiler --- CMakeLists.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e82ede2..4820e01 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,12 +42,12 @@ include_directories( ust ) -IF(MINGW) -SET( - CORE_LIBRARIES +IF(MINGW OR (CMAKE_CXX_COMPILER_ID MATCHES "GNU" AND CMAKE_CXX_COMPILER MATCHES "x86_64-w64-mingw32")) + SET( + CORE_LIBRARIES - dbghelp -) + dbghelp + ) ELSEIF(WIN32) SET( CORE_LIBRARIES From 31792819c06ed6f077f425d40801a55fc77ae296 Mon Sep 17 00:00:00 2001 From: Jason Gauci Date: Fri, 18 Sep 2026 01:32:34 -0500 Subject: [PATCH 03/12] Upgrade Catch2 to v2.13.10 for Apple Silicon support --- external/Catch2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/Catch2 b/external/Catch2 index e680c4b..182c910 160000 --- a/external/Catch2 +++ b/external/Catch2 @@ -1 +1 @@ -Subproject commit e680c4b9fb1d699bfad239e42ce7643d7cf00371 +Subproject commit 182c910b4b63ff587a3440e08f84f70497e49a81 From fabb5d59cc818786712cfbb79f304052a7f65dfb Mon Sep 17 00:00:00 2001 From: Jason Gauci Date: Fri, 18 Sep 2026 01:38:50 -0500 Subject: [PATCH 04/12] Fix MinGW generate(): compute relative addresses for addr2line --- ust/ust_windows.hpp | 41 +++++++++++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/ust/ust_windows.hpp b/ust/ust_windows.hpp index 73ca41a..ebfb4b2 100644 --- a/ust/ust_windows.hpp +++ b/ust/ust_windows.hpp @@ -237,16 +237,45 @@ UST_NOINLINE inline StackTrace generate() { // Fetch source file & line numbers via addr2line (same as Unix) std::map> fileAddresses; std::map> fileData; - for (const auto& it : stackTrace) { - if (it.binaryFileName.length()) { - if (fileAddresses.find(it.binaryFileName) == fileAddresses.end()) { - fileAddresses[it.binaryFileName] = {}; + for (int a = 0; a < numFrames; a++) { + HMODULE moduleHandle; + GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, + (const char*)stack[a], &moduleHandle); + std::string fileName(4096, '\0'); + auto fileNameSize = + GetModuleFileNameA(moduleHandle, &fileName[0], fileName.size()); + if (fileNameSize == 0 || fileNameSize == (ssize_t)fileName.size()) { + fileName = ""; + } else { + fileName = fileName.substr(0, fileNameSize); + std::replace(fileName.begin(), fileName.end(), '\\', '/'); + } + // Compute relative address for addr2line (needs module-relative offset) + uint64_t absoluteAddr = uint64_t(stack[a]); + uint64_t relativeAddr = absoluteAddr; + if (!fileName.empty() && moduleHandle) { + MODULEINFO moduleInfo; + memset(&moduleInfo, 0, sizeof(moduleInfo)); + moduleInfo.SizeOfStruct = sizeof(moduleInfo); + if (GetModuleInformation(GetCurrentProcess(), moduleHandle, + &moduleInfo, sizeof(moduleInfo))) { + uint64_t moduleBase = (uint64_t)moduleInfo.lpBaseOfDll; + if (absoluteAddr >= moduleBase) { + relativeAddr = absoluteAddr - moduleBase; + } + } + } + std::string addrStr = addressToString(relativeAddr); + // Update the stackTrace entry with relative address for addr2line lookup + stackTrace[a].address = addrStr; + if (fileName.length()) { + if (fileAddresses.find(fileName) == fileAddresses.end()) { + fileAddresses[fileName] = {}; } - fileAddresses.at(it.binaryFileName).push_back(it.address); + fileAddresses.at(fileName).push_back(addrStr); } } for (const auto& it : fileAddresses) { - std::string fileName = it.first; std::ostringstream ss; ss << "addr2line -C -f -p -e " << fileName << " "; for (const auto& it2 : it.second) { From 4575b3082d1c32c1b849e3fefa3d0706a4d9f70b Mon Sep 17 00:00:00 2001 From: Jason Gauci Date: Fri, 18 Sep 2026 01:43:35 -0500 Subject: [PATCH 05/12] Fix MinGW generate(): fix fileName reference in addr2line loop --- ust/ust_windows.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/ust/ust_windows.hpp b/ust/ust_windows.hpp index ebfb4b2..7fb6f12 100644 --- a/ust/ust_windows.hpp +++ b/ust/ust_windows.hpp @@ -276,6 +276,7 @@ UST_NOINLINE inline StackTrace generate() { } } for (const auto& it : fileAddresses) { + std::string fileName = it.first; std::ostringstream ss; ss << "addr2line -C -f -p -e " << fileName << " "; for (const auto& it2 : it.second) { From fea14647f340ed0e88e125fef2448bfebd888166 Mon Sep 17 00:00:00 2001 From: Jason Gauci Date: Fri, 18 Sep 2026 02:04:23 -0500 Subject: [PATCH 06/12] Simplify CI: only run on pull_request --- .github/workflows/ci.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index d62bdb7..5221176 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1,7 +1,6 @@ name: CI on: - push: pull_request: jobs: From e6073e981eb060db5e18c80555a180e129cfb79f Mon Sep 17 00:00:00 2001 From: Jason Gauci Date: Fri, 18 Sep 2026 02:08:21 -0500 Subject: [PATCH 07/12] Fix MinGW build: declare MODULEINFO/GetModuleInformation, simplify CI trigger --- ust/ust_windows.hpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/ust/ust_windows.hpp b/ust/ust_windows.hpp index 7fb6f12..e39a55e 100644 --- a/ust/ust_windows.hpp +++ b/ust/ust_windows.hpp @@ -24,6 +24,20 @@ #include #include #include +// Declare MODULEINFO and GetModuleInformation for MinGW (from Psapi.h) +#ifndef MODULEINFO_DEFINED +#define MODULEINFO_DEFINED +typedef struct _MODULEINFO { + LPVOID lpBaseOfDll; + DWORD SizeOfImage; + LPVOID EntryPoint; +} MODULEINFO, *LPMODULEINFO; +#endif +#ifndef UST_MODULEINFO_PROTOTYPE +#define UST_MODULEINFO_PROTOTYPE +BOOL WINAPI GetModuleInformation(HANDLE hProcess, HMODULE hModule, + LPMODULEINFO lpmodinfo, DWORD cb); +#endif #if defined(__MINGW32__) || defined(__MINGW64__) #define WEXITSTATUS(w) (((w) >> 8) & 0xff) #else From a894f5c4cca643060b155e73829d6dd78ae1b4a3 Mon Sep 17 00:00:00 2001 From: Jason Gauci Date: Fri, 18 Sep 2026 02:12:13 -0500 Subject: [PATCH 08/12] Fix CMake MINGW detection for cross-compiler --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4820e01..aa924ea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,7 +42,7 @@ include_directories( ust ) -IF(MINGW OR (CMAKE_CXX_COMPILER_ID MATCHES "GNU" AND CMAKE_CXX_COMPILER MATCHES "x86_64-w64-mingw32")) +IF(MINGW OR CMAKE_CXX_COMPILER MATCHES "mingw32" OR CMAKE_CXX_COMPILER MATCHES "x86_64-w64-mingw32" OR (CMAKE_CXX_COMPILER_ID MATCHES "GNU" AND CMAKE_CXX_COMPILER MATCHES "mingw")) SET( CORE_LIBRARIES From 09f13a4ee6200f261d3a3d1a97107d9fb2ad3bd9 Mon Sep 17 00:00:00 2001 From: Jason Gauci Date: Fri, 18 Sep 2026 02:30:47 -0500 Subject: [PATCH 09/12] Fix MinGW build: include psapi.h instead of manual declarations --- ust/ust_windows.hpp | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/ust/ust_windows.hpp b/ust/ust_windows.hpp index e39a55e..2e3bd28 100644 --- a/ust/ust_windows.hpp +++ b/ust/ust_windows.hpp @@ -24,19 +24,8 @@ #include #include #include -// Declare MODULEINFO and GetModuleInformation for MinGW (from Psapi.h) -#ifndef MODULEINFO_DEFINED -#define MODULEINFO_DEFINED -typedef struct _MODULEINFO { - LPVOID lpBaseOfDll; - DWORD SizeOfImage; - LPVOID EntryPoint; -} MODULEINFO, *LPMODULEINFO; -#endif -#ifndef UST_MODULEINFO_PROTOTYPE -#define UST_MODULEINFO_PROTOTYPE -BOOL WINAPI GetModuleInformation(HANDLE hProcess, HMODULE hModule, - LPMODULEINFO lpmodinfo, DWORD cb); +#if !defined(_MSC_VER) && (defined(__MINGW32__) || defined(__MINGW64__)) +#include #endif #if defined(__MINGW32__) || defined(__MINGW64__) #define WEXITSTATUS(w) (((w) >> 8) & 0xff) From dd703ba3a125a8de4d155f9ebab64967d648734c Mon Sep 17 00:00:00 2001 From: Jason Gauci Date: Fri, 18 Sep 2026 03:01:05 -0500 Subject: [PATCH 10/12] Fix MinGW build: remove SizeOfStruct from MODULEINFO --- ust/ust_windows.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/ust/ust_windows.hpp b/ust/ust_windows.hpp index 2e3bd28..fb284d2 100644 --- a/ust/ust_windows.hpp +++ b/ust/ust_windows.hpp @@ -259,7 +259,6 @@ UST_NOINLINE inline StackTrace generate() { if (!fileName.empty() && moduleHandle) { MODULEINFO moduleInfo; memset(&moduleInfo, 0, sizeof(moduleInfo)); - moduleInfo.SizeOfStruct = sizeof(moduleInfo); if (GetModuleInformation(GetCurrentProcess(), moduleHandle, &moduleInfo, sizeof(moduleInfo))) { uint64_t moduleBase = (uint64_t)moduleInfo.lpBaseOfDll; From fac3ced051c0d953cde6273e623523ee27230086 Mon Sep 17 00:00:00 2001 From: Jason Gauci Date: Fri, 18 Sep 2026 07:29:53 -0500 Subject: [PATCH 11/12] Fix MinGW addr2line addresses under ASLR --- ust/ust_windows.hpp | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/ust/ust_windows.hpp b/ust/ust_windows.hpp index fb284d2..a9fff52 100644 --- a/ust/ust_windows.hpp +++ b/ust/ust_windows.hpp @@ -173,6 +173,27 @@ inline std::string SystemToStr(const char* cmd) { return result; } +// PE debug information records addresses relative to the image's preferred +// link-time base, while CaptureStackBackTrace returns addresses relative to +// the image's (possibly ASLR-relocated) load base. Translate between the two +// before invoking GNU addr2line. +inline uint64_t addr2lineAddress(void* address, HMODULE moduleHandle) { + if (!address || !moduleHandle) return uint64_t(address); + + const auto* dosHeader = reinterpret_cast(moduleHandle); + if (dosHeader->e_magic != IMAGE_DOS_SIGNATURE) return uint64_t(address); + + const auto* ntHeaders = reinterpret_cast( + reinterpret_cast(moduleHandle) + dosHeader->e_lfanew); + if (ntHeaders->Signature != IMAGE_NT_SIGNATURE) return uint64_t(address); + + const uint64_t loadedBase = reinterpret_cast(moduleHandle); + const uint64_t absoluteAddress = reinterpret_cast(address); + if (absoluteAddress < loadedBase) return absoluteAddress; + return uint64_t(ntHeaders->OptionalHeader.ImageBase) + + (absoluteAddress - loadedBase); +} + UST_NOINLINE inline StackTrace generate_raw() { std::vector stackTrace; void* stack[MAX_STACK_FRAMES]; @@ -253,23 +274,7 @@ UST_NOINLINE inline StackTrace generate() { fileName = fileName.substr(0, fileNameSize); std::replace(fileName.begin(), fileName.end(), '\\', '/'); } - // Compute relative address for addr2line (needs module-relative offset) - uint64_t absoluteAddr = uint64_t(stack[a]); - uint64_t relativeAddr = absoluteAddr; - if (!fileName.empty() && moduleHandle) { - MODULEINFO moduleInfo; - memset(&moduleInfo, 0, sizeof(moduleInfo)); - if (GetModuleInformation(GetCurrentProcess(), moduleHandle, - &moduleInfo, sizeof(moduleInfo))) { - uint64_t moduleBase = (uint64_t)moduleInfo.lpBaseOfDll; - if (absoluteAddr >= moduleBase) { - relativeAddr = absoluteAddr - moduleBase; - } - } - } - std::string addrStr = addressToString(relativeAddr); - // Update the stackTrace entry with relative address for addr2line lookup - stackTrace[a].address = addrStr; + std::string addrStr = addressToString(addr2lineAddress(stack[a], moduleHandle)); if (fileName.length()) { if (fileAddresses.find(fileName) == fileAddresses.end()) { fileAddresses[fileName] = {}; From 02b0c7d04563a5adb67027e1d08e91065ede0c2f Mon Sep 17 00:00:00 2001 From: Jason Gauci Date: Fri, 18 Sep 2026 07:35:35 -0500 Subject: [PATCH 12/12] Read MinGW preferred image base from PE file --- ust/ust_windows.hpp | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/ust/ust_windows.hpp b/ust/ust_windows.hpp index a9fff52..b443b31 100644 --- a/ust/ust_windows.hpp +++ b/ust/ust_windows.hpp @@ -177,20 +177,29 @@ inline std::string SystemToStr(const char* cmd) { // link-time base, while CaptureStackBackTrace returns addresses relative to // the image's (possibly ASLR-relocated) load base. Translate between the two // before invoking GNU addr2line. -inline uint64_t addr2lineAddress(void* address, HMODULE moduleHandle) { +inline uint64_t addr2lineAddress(void* address, HMODULE moduleHandle, + const std::string& fileName) { if (!address || !moduleHandle) return uint64_t(address); - const auto* dosHeader = reinterpret_cast(moduleHandle); - if (dosHeader->e_magic != IMAGE_DOS_SIGNATURE) return uint64_t(address); - - const auto* ntHeaders = reinterpret_cast( - reinterpret_cast(moduleHandle) + dosHeader->e_lfanew); - if (ntHeaders->Signature != IMAGE_NT_SIGNATURE) return uint64_t(address); + // The Windows loader rewrites OptionalHeader.ImageBase in the in-memory PE + // header after applying ASLR, so read the preferred base from the file. + IMAGE_DOS_HEADER dosHeader{}; + IMAGE_NT_HEADERS ntHeaders{}; + std::ifstream image(fileName, std::ios::binary); + image.read(reinterpret_cast(&dosHeader), sizeof(dosHeader)); + if (!image || dosHeader.e_magic != IMAGE_DOS_SIGNATURE) { + return uint64_t(address); + } + image.seekg(dosHeader.e_lfanew); + image.read(reinterpret_cast(&ntHeaders), sizeof(ntHeaders)); + if (!image || ntHeaders.Signature != IMAGE_NT_SIGNATURE) { + return uint64_t(address); + } const uint64_t loadedBase = reinterpret_cast(moduleHandle); const uint64_t absoluteAddress = reinterpret_cast(address); if (absoluteAddress < loadedBase) return absoluteAddress; - return uint64_t(ntHeaders->OptionalHeader.ImageBase) + + return uint64_t(ntHeaders.OptionalHeader.ImageBase) + (absoluteAddress - loadedBase); } @@ -274,7 +283,8 @@ UST_NOINLINE inline StackTrace generate() { fileName = fileName.substr(0, fileNameSize); std::replace(fileName.begin(), fileName.end(), '\\', '/'); } - std::string addrStr = addressToString(addr2lineAddress(stack[a], moduleHandle)); + std::string addrStr = + addressToString(addr2lineAddress(stack[a], moduleHandle, fileName)); if (fileName.length()) { if (fileAddresses.find(fileName) == fileAddresses.end()) { fileAddresses[fileName] = {};