This is an automated email from the ASF dual-hosted git repository.

tqchen pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tvm-ffi.git


The following commit(s) were added to refs/heads/main by this push:
     new 9d784c4d [WIN] Enhance threadsafety of DbgHelp (#754)
9d784c4d is described below

commit 9d784c4da74ff7360d76c79a89fe60a63516f880
Author: Tianqi Chen <[email protected]>
AuthorDate: Sun Sep 6 10:45:09 2026 -0400

    [WIN] Enhance threadsafety of DbgHelp (#754)
    
    `src/ffi/backtrace_win.cc` called DbgHelp unguarded, though DbgHelp is
    single-threaded and requires the caller to serialize it. The process
    died while
    formatting a backtrace for another thread's exception, so the original
    error was
    never printed.
    
    - One mutex around every DbgHelp call made by tvm_ffi.
    - A duplicated process handle as the session key. `GetCurrentProcess()`
    has
    the same value in every component, so another DbgHelp user in the
    process could
      close our symbols. A fresh key also makes `SymInitialize`'s failure
      unambiguous, so its result can be checked.
    - Initialize once and keep the session, dropping the per-call
    `SymInitialize`/`SymCleanup` and with it module enumeration inside the
    lock.
    
    There is deliberately no destructor: `SymCleanup` at static-destruction
    time
    would reintroduce teardown at an unpredictable point relative to DLL
    unload.
    
    A failed initialization now returns a backtrace with only the caller's
    own frame.
    `StackWalk64` resolves through the session, so without one there is no
    unwinding
    at all.
---
 src/ffi/backtrace_win.cc | 56 ++++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 52 insertions(+), 4 deletions(-)

diff --git a/src/ffi/backtrace_win.cc b/src/ffi/backtrace_win.cc
index ca5cf06d..6f782b90 100644
--- a/src/ffi/backtrace_win.cc
+++ b/src/ffi/backtrace_win.cc
@@ -32,10 +32,51 @@
 #include <tvm/ffi/error.h>
 
 #include <iostream>
+#include <mutex>
 #include <vector>
 
 #include "./backtrace_utils.h"
 
+namespace {
+
+/*! \brief Global singleton holding tvm_ffi's DbgHelp symbol session. */
+struct DbgHelpSession {
+  /*! \brief Serializes every DbgHelp call made by tvm_ffi. */
+  std::mutex mutex;
+  /*! \brief Session handle, or nullptr when no session could be established. 
*/
+  HANDLE handle = nullptr;
+
+  static DbgHelpSession* Global() {
+    static DbgHelpSession inst;
+    return &inst;
+  }
+
+  DbgHelpSession() {
+    HANDLE current_process_handle = GetCurrentProcess();
+    // Duplicate rather than key the session on GetCurrentProcess() directly: 
that
+    // value is the same in every component, so another DbgHelp user in this 
process
+    // could close our symbols. A duplicated handle is a key only we hold.
+    if (!DuplicateHandle(current_process_handle, current_process_handle, 
current_process_handle,
+                         &handle, 0, FALSE, DUPLICATE_SAME_ACCESS)) {
+      handle = nullptr;
+      return;
+    }
+    // LOAD_LINES keeps file and line information; UNDNAME demangles C++ names.
+    SymSetOptions(SYMOPT_LOAD_LINES | SYMOPT_UNDNAME);
+    // TRUE enumerates loaded modules now, so no module is discovered later 
while
+    // the mutex is held. Unlike the previous code the result is checked: a 
session
+    // that failed to initialize must not be cached as usable.
+    if (!SymInitialize(handle, NULL, TRUE)) {
+      // Nothing was filed under this key, so there is no session to 
SymCleanup;
+      // release the handle and leave it null to report the failure.
+      CloseHandle(handle);
+      handle = nullptr;
+    }
+  }
+};
+
+}  // namespace
+
 const TVMFFIByteArray* TVMFFIBacktrace(const char* filename, int lineno, const 
char* func,
                                        int cross_ffi_boundary) {
   static thread_local std::string backtrace_str;
@@ -51,11 +92,19 @@ const TVMFFIByteArray* TVMFFIBacktrace(const char* 
filename, int lineno, const c
     backtrace.Append(filename, func, lineno);
   }
 
-  HANDLE process = GetCurrentProcess();
   HANDLE thread = GetCurrentThread();
 
-  SymSetOptions(SYMOPT_LOAD_LINES | SYMOPT_UNDNAME);
-  SymInitialize(process, NULL, TRUE);
+  DbgHelpSession* session = DbgHelpSession::Global();
+  std::lock_guard<std::mutex> lock(session->mutex);
+  HANDLE process = session->handle;
+  if (process == nullptr) {
+    // StackWalk64 resolves through the session, so without one there is 
nothing
+    // to walk. Report the caller's own frame instead of using a dead session.
+    backtrace_str = backtrace.GetBacktrace();
+    backtrace_array.data = backtrace_str.data();
+    backtrace_array.size = backtrace_str.size();
+    return &backtrace_array;
+  }
   CONTEXT context = {};
   RtlCaptureContext(&context);
 
@@ -138,7 +187,6 @@ const TVMFFIByteArray* TVMFFIBacktrace(const char* 
filename, int lineno, const c
     }
     backtrace.Append(filename, symbol, lineno);
   }
-  SymCleanup(process);
   backtrace_str = backtrace.GetBacktrace();
   backtrace_array.data = backtrace_str.data();
   backtrace_array.size = backtrace_str.size();

Reply via email to