On 2/23/2026 4:07 PM, Álvaro Herrera wrote:
> On 2026-Feb-23, Bryan Green wrote:
> 
>> I have implemented DuplicateHandle and closed the handle in the
>> appropriate places.  I also reset backtrace_process to NULL if
>> SymInitialize() fails.  Patch is attached.
> 
> Hmm, should then backtrace_cleanup() cope with the case where it's NULL?
> 
> Also, I wonder what happens if one "backtraceable" error occurs, and we
> fail to SymInitialize(), then another backtraceable error occurs.
> Should we do the DuplicateHandle()+SymInitialize() dance again, or
> should we just give up?  The current implementation does the former, I
> think; but the latter is also easily achievable by setting
> backtrace_symbols_initialized to true and leaving backtrace_process as
> NULL; then this case can be detected specifically in set_backtrace() and
> treated as a case where we just return NULL before attempting anything
> else.
> 
I have removed all but the SymCleanup form the backtrace_cleanup since
it happens at process exit.  I have implemented not retrying as you
described above.

Let me know if this was what you were thinking.

Patch attached.

-- 
Bryan Green
EDB: https://www.enterprisedb.com
From 8b0c21111d48eb3cf85338b916f547715b91510e Mon Sep 17 00:00:00 2001
From: Bryan Green <[email protected]>
Date: Mon, 23 Feb 2026 22:20:23 -0600
Subject: [PATCH v10] Add backtrace support for Windows using DbgHelp API

Previously, backtrace generation on Windows would return an "unsupported"
message.  With this commit, we rely on CaptureStackBackTrace() to capture
the call stack and the DbgHelp API (SymFromAddrW, SymGetLineFromAddrW64)
for symbol resolution.

Symbol handler initialization (SymInitialize) is performed once per
process and cached.  If initialization fails, the report for it is
returned as the backtrace output, and the failure is recorded so that
initialization is not re-attempted on subsequent calls.  The symbol
handler is cleaned up via on_proc_exit() to release DbgHelp resources.

The implementation provides symbol names, offsets, and addresses.  When
PDB files are available, it also includes 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, IMAGEHLP_LINEW64,
SymGetLineFromAddrW64) rather than the generic versions.  This allows us
to rely on predictable encoding conversion from Unicode, rather than using
the haphazard ANSI codepage that we'd get otherwise.

DbgHelp is apparently available on all Windows platforms we support, so
there are no version number checks.

Author: Bryan Green <[email protected]>
Reviewed-by: Euler Taveira <[email protected]>
Reviewed-by: Jakub Wartak <[email protected]>
Reviewed-by: Greg Burd <[email protected]>
Discussion: https://postgr.es/m/[email protected]
---
 src/backend/meson.build        |   6 ++
 src/backend/utils/error/elog.c | 171 ++++++++++++++++++++++++++++++++-
 2 files changed, 174 insertions(+), 3 deletions(-)

diff --git a/src/backend/meson.build b/src/backend/meson.build
index fec9f1d03b..4f5292d8f8 100644
--- a/src/backend/meson.build
+++ b/src/backend/meson.build
@@ -41,6 +41,12 @@ backend_link_args = []
 backend_link_depends = []
 
 
+# On Windows also make the backend depend on dbghelp, for backtrace support
+if host_system == 'windows' and cc.get_id() == 'msvc'
+  backend_build_deps += cc.find_library('dbghelp')
+endif
+
+
 # On windows when compiling with msvc we need to make postgres export all its
 # symbols so that extension libraries can use them. For that we need to scan
 # the constituting objects and generate a file specifying all the functions as
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 24e8f37f63..3ef39acc4d 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -66,6 +66,10 @@
 #include <execinfo.h>
 #endif
 
+#ifdef _MSC_VER
+#include <dbghelp.h>
+#endif
+
 #include "access/xact.h"
 #include "common/ip.h"
 #include "libpq/libpq.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
 
@@ -180,6 +189,7 @@ static void set_stack_entry_location(ErrorData *edata,
                                                                         const 
char *funcname);
 static bool matches_backtrace_functions(const char *funcname);
 static pg_noinline void set_backtrace(ErrorData *edata, int num_skip);
+static void backtrace_cleanup(int code, Datum arg);
 static void set_errdata_field(MemoryContextData *cxt, char **ptr, const char 
*str);
 static void FreeErrorDataContents(ErrorData *edata);
 static int     log_min_messages_cmp(const ListCell *a, const ListCell *b);
@@ -1124,6 +1134,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.
+ *
+ * The implementation is, unsurprisingly, platform-specific:
+ * - GNU libc and copycats: 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)
+ * - Others (musl libc): unsupported
  */
 static void
 set_backtrace(ErrorData *edata, int num_skip)
@@ -1134,12 +1151,12 @@ set_backtrace(ErrorData *edata, int num_skip)
 
 #ifdef HAVE_BACKTRACE_SYMBOLS
        {
-               void       *buf[100];
+               void       *frames[100];
                int                     nframes;
                char      **strfrms;
 
-               nframes = backtrace(buf, lengthof(buf));
-               strfrms = backtrace_symbols(buf, nframes);
+               nframes = backtrace(frames, lengthof(frames));
+               strfrms = backtrace_symbols(frames, nframes);
                if (strfrms != NULL)
                {
                        for (int i = num_skip; i < nframes; i++)
@@ -1150,6 +1167,141 @@ set_backtrace(ErrorData *edata, int num_skip)
                        appendStringInfoString(&errtrace,
                                                                   
"insufficient memory for backtrace generation");
        }
+#elif defined(_MSC_VER)
+       {
+               void       *frames[100];
+               int                     nframes;
+               char            buffer[sizeof(SYMBOL_INFOW) + MAX_SYM_NAME * 
sizeof(wchar_t)];
+               PSYMBOL_INFOW psymbol;
+
+               if (!backtrace_symbols_initialized)
+               {
+                       /* This will keep us from retrying */
+                       backtrace_symbols_initialized = true;
+
+                       if(!DuplicateHandle(GetCurrentProcess(),
+                                                               
GetCurrentProcess(),
+                                                               
GetCurrentProcess(),
+                                                               
&backtrace_process,
+                                                               0,
+                                                               FALSE,
+                                                               
DUPLICATE_SAME_ACCESS))
+                       {
+                               appendStringInfo(&errtrace,
+                                                                "could not get 
process handle for backtrace: error code %lu",
+                                                                
GetLastError());
+                               edata->backtrace = errtrace.data;
+                               return;
+                       }
+
+                       SymSetOptions(SYMOPT_UNDNAME |
+                                                 SYMOPT_DEFERRED_LOADS |
+                                                 SYMOPT_LOAD_LINES |
+                                                 SYMOPT_FAIL_CRITICAL_ERRORS);
+
+                       if (!SymInitialize(backtrace_process, NULL, TRUE))
+                       {
+                               CloseHandle(backtrace_process);
+                               backtrace_process = NULL;
+                               appendStringInfo(&errtrace,
+                                                                "could not 
initialize symbol handler: error code %lu",
+                                                                
GetLastError());
+                               edata->backtrace = errtrace.data;
+                               return;
+                       }
+
+                       on_proc_exit(backtrace_cleanup, 0);
+               }
+
+               if (backtrace_process == NULL)
+                       return;
+
+               nframes = CaptureStackBackTrace(num_skip, lengthof(frames), 
frames, NULL);
+
+               if (nframes == 0)
+               {
+                       appendStringInfoString(&errtrace, "zero stack frames 
captured");
+                       edata->backtrace = errtrace.data;
+                       return;
+               }
+
+               psymbol = (PSYMBOL_INFOW) buffer;
+               psymbol->MaxNameLen = MAX_SYM_NAME;
+               psymbol->SizeOfStruct = sizeof(SYMBOL_INFOW);
+
+               for (int i = 0; i < nframes; i++)
+               {
+                       DWORD64         address = (DWORD64) frames[i];
+                       DWORD64         displacement = 0;
+                       BOOL            sym_result;
+
+                       sym_result = SymFromAddrW(backtrace_process,
+                                                                         
address,
+                                                                         
&displacement,
+                                                                         
psymbol);
+                       if (sym_result)
+                       {
+                               char            symbol_name[MAX_SYM_NAME];
+                               size_t          result;
+
+                               /*
+                                * 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 
*) psymbol->Name,
+                                                                       
sizeof(symbol_name), NULL);
+
+                               if (result == (size_t) -1)
+                               {
+                                       /* Conversion failed, use address only 
*/
+                                       appendStringInfo(&errtrace,
+                                                                        
"\n[0x%llx]",
+                                                                        
(unsigned long long) address);
+                               }
+                               else
+                               {
+                                       IMAGEHLP_LINEW64 line;
+                                       DWORD           line_displacement = 0;
+                                       char            filename[MAX_PATH];
+
+                                       line.SizeOfStruct = 
sizeof(IMAGEHLP_LINEW64);
+
+                                       /* Start with the common part: 
symbol+offset [address] */
+                                       appendStringInfo(&errtrace,
+                                                                        
"\n%s+0x%llx [0x%llx]",
+                                                                        
symbol_name,
+                                                                        
(unsigned long long) displacement,
+                                                                        
(unsigned long long) address);
+
+                                       /* Try to append line info if available 
*/
+                                       if 
(SymGetLineFromAddrW64(backtrace_process,
+                                                                               
          address,
+                                                                               
          &line_displacement,
+                                                                               
          &line))
+                                       {
+                                               result = wchar2char(filename, 
(const wchar_t *) line.FileName,
+                                                                               
        sizeof(filename), NULL);
+
+                                               if (result != (size_t) -1)
+                                               {
+                                                       
appendStringInfo(&errtrace,
+                                                                               
         " [%s:%lu]",
+                                                                               
         filename,
+                                                                               
         (unsigned long) line.LineNumber);
+                                               }
+                                       }
+                               }
+                       }
+                       else
+                       {
+                               appendStringInfo(&errtrace,
+                                                                "\n[0x%llx] 
(symbol lookup failed: error code %lu)",
+                                                                (unsigned long 
long) address,
+                                                                
GetLastError());
+                       }
+               }
+       }
 #else
        appendStringInfoString(&errtrace,
                                                   "backtrace generation is not 
supported by this installation");
@@ -1158,6 +1310,19 @@ set_backtrace(ErrorData *edata, int num_skip)
        edata->backtrace = errtrace.data;
 }
 
+/*
+ * Cleanup function for DbgHelp resources.
+ * Called via on_proc_exit() to release resources allocated by SymInitialize().
+ */
+pg_attribute_unused()
+static void
+backtrace_cleanup(int code, Datum arg)
+{
+#ifdef _MSC_VER
+       SymCleanup(backtrace_process);
+#endif
+}
+
 /*
  * errmsg_internal --- add a primary error message text to the current error
  *
-- 
2.52.0.windows.1

Reply via email to