From 798866a250bf8643757e6a57beed40f286484f77 Mon Sep 17 00:00:00 2001 From: Bryan Green Date: Mon, 6 Oct 2025 16:29:55 -0500 Subject: [PATCH] Add Windows support for backtrace_functions (MSVC only) Implement backtrace generation on Windows using the DbgHelp API. This provides similar functionality to the existing Unix/Linux backtrace support, but only for MSVC builds. MinGW builds are not supported due to differences in the debugging infrastructure. When PDB debug symbols are available, backtraces will include function names and source file locations. Without PDB files, only raw addresses are displayed. The implementation uses the Unicode (wide character) variants of DbgHelp functions and converts paths to UTF-8 to properly handle international characters in file paths. This adds a dependency on dbghelp.lib for Windows MSVC builds. Author: Bryan Green --- src/backend/meson.build | 6 ++ src/backend/utils/error/elog.c | 167 ++++++++++++++++++++++++++++++++- 2 files changed, 172 insertions(+), 1 deletion(-) diff --git a/src/backend/meson.build b/src/backend/meson.build index b831a54165..849e457da3 100644 --- a/src/backend/meson.build +++ b/src/backend/meson.build @@ -1,6 +1,12 @@ # Copyright (c) 2022-2025, PostgreSQL Global Development Group backend_build_deps = [backend_code] + +# Add dbghelp for backtrace capability +if host_system == 'windows' and cc.get_id() == 'msvc' + backend_build_deps += cc.find_library('dbghelp') +endif + backend_sources = [] backend_link_with = [pgport_srv, common_srv] diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c index b7b9692f8c..d57ca54274 100644 --- a/src/backend/utils/error/elog.c +++ b/src/backend/utils/error/elog.c @@ -140,6 +140,13 @@ static void write_syslog(int level, const char *line); static void write_eventlog(int level, const char *line, int len); #endif +#ifdef _MSC_VER +#include +#include +static bool win32_backtrace_symbols_initialized = false; +static HANDLE win32_backtrace_process = NULL; +#endif + /* We provide a small stack of ErrorData records for re-entrant cases */ #define ERRORDATA_STACK_SIZE 5 @@ -1112,6 +1119,12 @@ errbacktrace(void) * specifies how many inner frames to skip. Use this to avoid showing the * internal backtrace support functions in the backtrace. This requires that * this and related functions are not inlined. + * + * Platform-specific implementations: + * - Unix/Linux/: Uses backtrace() and backtrace_symbols() + * - Windows: Uses CaptureStackBackTrace() with DbgHelp for symbol resolution + * (requires PDB files; falls back to raw addresses if unavailable) + * - Other: Returns unsupported message */ static void set_backtrace(ErrorData *edata, int num_skip) @@ -1138,6 +1151,159 @@ set_backtrace(ErrorData *edata, int num_skip) appendStringInfoString(&errtrace, "insufficient memory for backtrace generation"); } +#elif defined(_MSC_VER) + { + void *stack[100]; + DWORD frames; + DWORD i; + wchar_t buffer[sizeof(SYMBOL_INFOW) + MAX_SYM_NAME * sizeof(wchar_t)]; + PSYMBOL_INFOW symbol; + char *utf8_buffer; + int utf8_len; + + if (!win32_backtrace_symbols_initialized) + { + win32_backtrace_process = GetCurrentProcess(); + + SymSetOptions(SYMOPT_UNDNAME | + SYMOPT_DEFERRED_LOADS | + SYMOPT_LOAD_LINES | + SYMOPT_FAIL_CRITICAL_ERRORS); + + if (SymInitialize(win32_backtrace_process, NULL, TRUE)) + { + win32_backtrace_symbols_initialized = true; + } + else + { + DWORD error = GetLastError(); + elog(WARNING, "SymInitialize failed with error %lu", error); + } + } + + frames = CaptureStackBackTrace(num_skip, lengthof(stack), stack, NULL); + + if (frames == 0) + { + appendStringInfoString(&errtrace, "\nNo stack frames captured"); + edata->backtrace = errtrace.data; + return; + } + + symbol = (PSYMBOL_INFOW) buffer; + symbol->MaxNameLen = MAX_SYM_NAME; + symbol->SizeOfStruct = sizeof(SYMBOL_INFOW); + + for (i = 0; i < frames; i++) + { + DWORD64 address = (DWORD64) (stack[i]); + DWORD64 displacement = 0; + BOOL sym_result; + + sym_result = SymFromAddrW(win32_backtrace_process, + address, + &displacement, + symbol); + + if (sym_result) + { + IMAGEHLP_LINEW64 line; + DWORD line_displacement = 0; + + line.SizeOfStruct = sizeof(IMAGEHLP_LINEW64); + + if (SymGetLineFromAddrW64(win32_backtrace_process, + address, + &line_displacement, + &line)) + { + /* Convert symbol name to UTF-8 */ + utf8_len = WideCharToMultiByte(CP_UTF8, 0, symbol->Name, -1, + NULL, 0, NULL, NULL); + if (utf8_len > 0) + { + char *filename_utf8; + int filename_len; + + utf8_buffer = palloc(utf8_len); + WideCharToMultiByte(CP_UTF8, 0, symbol->Name, -1, + utf8_buffer, utf8_len, NULL, NULL); + + /* Convert file name to UTF-8 */ + filename_len = WideCharToMultiByte(CP_UTF8, 0, + line.FileName, -1, + NULL, 0, NULL, NULL); + if (filename_len > 0) + { + filename_utf8 = palloc(filename_len); + WideCharToMultiByte(CP_UTF8, 0, line.FileName, -1, + filename_utf8, filename_len, + NULL, NULL); + + appendStringInfo(&errtrace, + "\n%s+0x%llx [%s:%lu]", + utf8_buffer, + (unsigned long long) displacement, + filename_utf8, + (unsigned long) line.LineNumber); + + pfree(filename_utf8); + } + else + { + appendStringInfo(&errtrace, + "\n%s+0x%llx [0x%llx]", + utf8_buffer, + (unsigned long long) displacement, + (unsigned long long) address); + } + + pfree(utf8_buffer); + } + else + { + /* Conversion failed, use address only */ + appendStringInfo(&errtrace, + "\n[0x%llx]", + (unsigned long long) address); + } + } + else + { + /* No line info, convert symbol name only */ + utf8_len = WideCharToMultiByte(CP_UTF8, 0, symbol->Name, -1, + NULL, 0, NULL, NULL); + if (utf8_len > 0) + { + utf8_buffer = palloc(utf8_len); + WideCharToMultiByte(CP_UTF8, 0, symbol->Name, -1, + utf8_buffer, utf8_len, NULL, NULL); + + appendStringInfo(&errtrace, + "\n%s+0x%llx [0x%llx]", + utf8_buffer, + (unsigned long long) displacement, + (unsigned long long) address); + + pfree(utf8_buffer); + } + else + { + /* Conversion failed, use address only */ + appendStringInfo(&errtrace, + "\n[0x%llx]", + (unsigned long long) address); + } + } + } + else + { + appendStringInfo(&errtrace, + "\n[0x%llx]", + (unsigned long long) address); + } + } + } #else appendStringInfoString(&errtrace, "backtrace generation is not supported by this installation"); @@ -1145,7 +1311,6 @@ set_backtrace(ErrorData *edata, int num_skip) edata->backtrace = errtrace.data; } - /* * errmsg_internal --- add a primary error message text to the current error * -- 2.46.0.windows.1