Hi,
I've implemented Windows support for the backtrace_functions configuration
parameter using the DbgHelp API. This brings Windows debugging capabilities
closer to parity with Unix/Linux platforms.
Currently, backtrace_functions only works on Unix-like systems. This patch
adds equivalent functionality for Windows MSVC builds using:
- CaptureStackBackTrace() for capturing the call stack
- SymFromAddrW() and SymGetLineFromAddrW64() for symbol resolution
- UTF-8 conversion to handle international file paths correctly
When PDB symbol files are available, backtraces show function names,
offsets,
file paths, and line numbers. Without PDB files, raw memory addresses
are shown.
Testing performed:
- MSVC build with PDB files: Backtraces include function names, offsets,
source files, and line numbers
- MSVC build without PDB files: Backtraces show memory addresses only
- Verified backtraces appear in server logs when backtrace_functions is set
- Confirmed no crashes or memory leaks
- Passed all tests with Cirrus CI for all defined platforms.
The implementation is MSVC-specific. MinGW builds will use the existing
fallback message ("backtrace generation is not supported by this
installation").
The patch is attached. I'm happy to make revisions based on feedback.
Best regards,
Bryan Green
From 798866a250bf8643757e6a57beed40f286484f77 Mon Sep 17 00:00:00 2001
From: Bryan Green <[email protected]>
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 <[email protected]>
---
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 <windows.h>
+#include <dbghelp.h>
+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