From 81ee688f490fa37b5eb30d1d88123e3d0a8423f2 Mon Sep 17 00:00:00 2001 From: Bryan Green Date: Tue, 21 Oct 2025 19:31:29 -0500 Subject: [PATCH] Add Windows support for backtrace_functions Implement backtrace generation on Windows using the DbgHelp API, providing similar functionality to the existing Unix/Linux support. When PDB files are available, backtraces include function names and source locations. Without PDB files, raw addresses are shown. DbgHelp is initialized once per backend and cleaned up via on_proc_exit(). This adds a dependency on dbghelp.lib for MSVC builds. --- src/backend/meson.build | 5 + src/backend/utils/error/elog.c | 174 +++++++++++++++++++++++++++++++++ 2 files changed, 179 insertions(+) diff --git a/src/backend/meson.build b/src/backend/meson.build index b831a54165..eeb69c4079 100644 --- a/src/backend/meson.build +++ b/src/backend/meson.build @@ -1,6 +1,11 @@ # Copyright (c) 2022-2025, PostgreSQL Global Development Group backend_build_deps = [backend_code] + +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 29643c5143..fc421ce444 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 @@ -1116,6 +1123,18 @@ errbacktrace(void) return 0; } +#ifdef _MSC_VER +/* + * Cleanup function for DbgHelp resources. + * Called via on_proc_exit() to release resources allocated by SymInitialize(). + */ +static void +win32_backtrace_cleanup(int code, Datum arg) +{ + SymCleanup(win32_backtrace_process); +} +#endif + /* * Compute backtrace data and add it to the supplied ErrorData. num_skip * specifies how many inner frames to skip. Use this to avoid showing the @@ -1147,6 +1166,161 @@ 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; + on_proc_exit(win32_backtrace_cleanup, 0); + } + 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"); -- 2.46.0.windows.1