v5-0001-Add-Windows-support-for-backtrace_functions.patch
text/plain
Filename: v5-0001-Add-Windows-support-for-backtrace_functions.patch
Type: text/plain
Part: 0
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch v5-0001
Subject: Add backtrace support for Windows using DbgHelp API
| File | + | − |
|---|---|---|
| src/backend/meson.build | 5 | 0 |
| src/backend/utils/error/elog.c | 150 | 1 |
From ba3bf14d523e4c656be1cf3d8bdb80afaa3355ed Mon Sep 17 00:00:00 2001
From: Bryan Green <dbryan.green@gmail.com>
Date: Fri, 31 Oct 2025 20:27:38 -0600
Subject: [PATCH v5] Add backtrace support for Windows using DbgHelp API
Previously, backtrace generation on Windows would return an "unsupported"
message. This patch implements Windows backtrace support using
CaptureStackBackTrace() for capturing the call stack and the DbgHelp API
(SymFromAddrW, SymGetLineFromAddr64) for symbol resolution.
The implementation provides symbol names, offsets, addresses, and when PDB
files are available, source file names and line numbers. Symbol names and
file paths are converted from UTF-16 to the database encoding using
wchar2char(), which properly handles both UTF-8 and non-UTF-8 databases on
Windows. When symbol information is unavailable or encoding conversion
fails, it falls back to displaying raw addresses.
The implementation uses the explicit Unicode versions of the DbgHelp
functions (SYMBOL_INFOW, SymFromAddrW) rather than the generic versions.
This is necessary because the generic SYMBOL_INFO becomes SYMBOL_INFOA on
PostgreSQL's Windows builds (which don't define UNICODE), providing strings
in the Windows ANSI codepage rather than a predictable encoding that can be
converted to the database encoding.
Symbol handler initialization (SymInitialize) is performed once per process
and cached. If initialization fails, a warning is logged and no backtrace
is generated.
This patch also adds a check for zero frames returned by backtrace() on
Unix/Linux platforms, which can occur in certain circumstances such as ARM
builds without unwind tables.
Author: Bryan Green <dbryan.green@gmail.com>
Reviewed-by: Euler Taveira <euler@eulerto.com>
Reviewed-by: Jakub Wartak <jakub.wartak@enterprisedb.com>
---
src/backend/meson.build | 5 ++
src/backend/utils/error/elog.c | 151 ++++++++++++++++++++++++++++++++-
2 files changed, 155 insertions(+), 1 deletion(-)
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..17a3ba256a 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -66,11 +66,15 @@
#include <execinfo.h>
#endif
+#ifdef _MSC_VER
+#include <dbghelp.h>
+#include <windows.h>
+#endif
+
#include "access/xact.h"
#include "common/ip.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
-#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "nodes/miscnodes.h"
#include "pgstat.h"
@@ -140,6 +144,11 @@ static void write_syslog(int level, const char *line);
static void write_eventlog(int level, const char *line, int len);
#endif
+#ifdef _MSC_VER
+static bool backtrace_symbols_initialized = false;
+static HANDLE backtrace_process = NULL;
+#endif
+
/* We provide a small stack of ErrorData records for re-entrant cases */
#define ERRORDATA_STACK_SIZE 5
@@ -1121,6 +1130,13 @@ 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 exported functions/raw addresses if
+ * unavailable)
+ * - Other: Returns unsupported message
*/
static void
set_backtrace(ErrorData *edata, int num_skip)
@@ -1136,6 +1152,14 @@ set_backtrace(ErrorData *edata, int num_skip)
char **strfrms;
nframes = backtrace(buf, lengthof(buf));
+
+ if (nframes == 0)
+ {
+ appendStringInfoString(&errtrace, "\nNo stack frames captured");
+ edata->backtrace = errtrace.data;
+ return;
+ }
+
strfrms = backtrace_symbols(buf, nframes);
if (strfrms != NULL)
{
@@ -1147,6 +1171,131 @@ set_backtrace(ErrorData *edata, int num_skip)
appendStringInfoString(&errtrace,
"insufficient memory for backtrace generation");
}
+#elif defined(_MSC_VER)
+ {
+ void *buf[100];
+ int nframes;
+ char buffer[sizeof(SYMBOL_INFOW) + MAX_SYM_NAME * sizeof(wchar_t)];
+ PSYMBOL_INFOW symbol;
+
+ if (!backtrace_symbols_initialized)
+ {
+ backtrace_process = GetCurrentProcess();
+
+ SymSetOptions(SYMOPT_UNDNAME |
+ SYMOPT_DEFERRED_LOADS |
+ SYMOPT_LOAD_LINES |
+ SYMOPT_FAIL_CRITICAL_ERRORS);
+
+ if (!SymInitialize(backtrace_process, NULL, TRUE))
+ {
+ elog(WARNING, "could not initialize the symbol handler: error code %lu",
+ GetLastError());
+ edata->backtrace = errtrace.data;
+ return;
+ }
+ backtrace_symbols_initialized = true;
+ }
+
+ nframes = CaptureStackBackTrace(num_skip, lengthof(buf), buf, NULL);
+
+ if (nframes == 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 (int i = 0; i < nframes; i++)
+ {
+ DWORD64 address = (DWORD64)buf[i];
+ DWORD64 displacement = 0;
+ BOOL sym_result;
+
+ sym_result = SymFromAddrW(backtrace_process,
+ address,
+ &displacement,
+ symbol);
+
+ if (sym_result)
+ {
+ IMAGEHLP_LINEW64 line;
+ DWORD line_displacement = 0;
+ char symbol_name[MAX_SYM_NAME];
+ size_t result;
+
+ line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
+
+ /*
+ * Convert symbol name from UTF-16 to database encoding using
+ * wchar2char(), which handles both UTF-8 and non-UTF-8 databases
+ * correctly on Windows.
+ */
+ result = wchar2char(symbol_name, (const wchar_t *) symbol->Name,
+ sizeof(symbol_name), NULL);
+
+ if (result == (size_t) -1)
+ {
+ /* Conversion failed, use address only */
+ appendStringInfo(&errtrace,
+ "\n[0x%llx]",
+ (unsigned long long) address);
+ continue;
+ }
+
+ if (SymGetLineFromAddrW64(backtrace_process,
+ address,
+ &line_displacement,
+ &line))
+ {
+ char filename[MAX_PATH];
+
+ /* Convert filename from UTF-16 to database encoding */
+ result = wchar2char(filename, (const wchar_t *) line.FileName,
+ sizeof(filename), NULL);
+
+ if (result != (size_t) -1)
+ {
+ appendStringInfo(&errtrace,
+ "\n%s+0x%llx [0x%llx] [%s:%lu]",
+ symbol_name,
+ (unsigned long long) displacement,
+ (unsigned long long) address,
+ filename,
+ (unsigned long) line.LineNumber);
+ }
+ else
+ {
+ /* Filename conversion failed, omit it */
+ appendStringInfo(&errtrace,
+ "\n%s+0x%llx [0x%llx]",
+ symbol_name,
+ (unsigned long long) displacement,
+ (unsigned long long) address);
+ }
+ }
+ else
+ {
+ /* No line info available */
+ appendStringInfo(&errtrace,
+ "\n%s+0x%llx [0x%llx]",
+ symbol_name,
+ (unsigned long long) displacement,
+ (unsigned long long) address);
+ }
+ }
+ else
+ {
+ elog(WARNING, "symbol lookup failed: error code %lu",
+ GetLastError());
+ edata->backtrace = errtrace.data;
+ }
+ }
+ }
#else
appendStringInfoString(&errtrace,
"backtrace generation is not supported by this installation");
--
2.46.0.windows.1