Skip to content

Commit 909dce3

Browse files
Chandhana Solainathanmeta-codesync[bot]
authored andcommitted
telemetry: Add Windows throw-site stack trace capture
Summary: Adds Windows throw-site stack trace capture. On Windows, C++ exceptions are implemented as Structured Exception Handling (SEH) events with the [magic code 0xE06D7363](https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/specific-exceptions). A [Vectored Exception Handler](https://learn.microsoft.com/en-us/windows/win32/debug/vectored-exception-handling) filters for this code to detect C++ throws and captures the stack trace via the shared onThrow() infrastructure. Completes cross-platform support — Linux, macOS, and Windows all now capture throw-site stack traces. Reviewed By: vilatto Differential Revision: D102222780 fbshipit-source-id: a3ac9b3c54acad5ec3c778d6a7030a1532591e46
1 parent 59aa7de commit 909dce3

4 files changed

Lines changed: 58 additions & 15 deletions

File tree

eden/fs/telemetry/ThrowTraceCapture.cpp

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,13 @@
1212
#ifdef __APPLE__
1313
#include <dlfcn.h>
1414
#endif
15+
#ifdef _WIN32
16+
#include <windows.h>
17+
#endif
1518

1619
#include "eden/fs/rust/backtrace_ffi/src/lib.rs.h"
1720

18-
#if defined(__linux__) || defined(__APPLE__)
19-
2021
namespace {
21-
// Common: onThrow() captures a raw backtrace via Rust FFI.
22-
// Re-entrancy is handled by the Rust CapturingGuard in capture_backtrace().
2322
void onThrow() {
2423
constexpr size_t kMaxStackDepth = 64;
2524
facebook::eden::capture_backtrace(kMaxStackDepth);
@@ -68,9 +67,51 @@ __cxa_throw(void* thrownException, void* type, void (*destructor)(void*)) {
6867
__builtin_unreachable();
6968
}
7069

71-
#endif // platform hooks
70+
#elif defined(_WIN32)
71+
// Windows: Vectored Exception Handler catches C++ exceptions (SEH 0xE06D7363).
72+
73+
constexpr DWORD kCppExceptionCode = 0xE06D7363;
74+
75+
LONG WINAPI cppExceptionHandler(PEXCEPTION_POINTERS info) {
76+
if (info && info->ExceptionRecord &&
77+
info->ExceptionRecord->ExceptionCode == kCppExceptionCode) {
78+
onThrow();
79+
}
80+
return EXCEPTION_CONTINUE_SEARCH;
81+
}
82+
83+
// AddVectoredExceptionHandler first parameter: CALL_FIRST (1) registers our
84+
// handler at the front of the VEH list so it runs before other handlers,
85+
// ensuring we capture the stack trace before the exception is modified.
86+
// See:
87+
// https://learn.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-addvectoredexceptionhandler
88+
#define CALL_FIRST 1
89+
90+
struct VehRegistrar {
91+
PVOID handle_ = nullptr;
92+
VehRegistrar(const VehRegistrar&) = delete;
93+
VehRegistrar& operator=(const VehRegistrar&) = delete;
94+
VehRegistrar(VehRegistrar&&) = delete;
95+
VehRegistrar& operator=(VehRegistrar&&) = delete;
96+
97+
VehRegistrar() {
98+
handle_ = AddVectoredExceptionHandler(CALL_FIRST, cppExceptionHandler);
99+
if (!handle_) {
100+
fprintf(
101+
stderr,
102+
"Failed to register VEH for throw-site stack trace capture\n");
103+
}
104+
}
105+
~VehRegistrar() {
106+
if (handle_) {
107+
RemoveVectoredExceptionHandler(handle_);
108+
}
109+
}
110+
};
111+
112+
static VehRegistrar vehRegistrar;
72113

73-
#endif // defined(__linux__) || defined(__APPLE__)
114+
#endif // platform hooks
74115

75116
namespace facebook::eden {
76117

eden/fs/telemetry/ThrowTraceCapture.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ namespace facebook::eden {
2323
* Platform-specific hooks:
2424
* Linux: __wrap___cxa_throw (via --wrap linker flag)
2525
* macOS: __cxa_throw override (via dlsym RTLD_NEXT)
26+
* Windows: Vectored Exception Handler (SEH code 0xE06D7363)
2627
*/
2728
std::optional<std::string> getThrowSiteStackTrace();
2829

eden/fs/telemetry/test/EdenErrorInfoBuilderTest.cpp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,17 @@ TEST(EdenErrorInfoTest, InitializeFuseEdenErrorInfoWithException) {
3737
EXPECT_NE(
3838
info.exceptionType.value().find("runtime_error"), std::string::npos);
3939
ASSERT_TRUE(info.stackTrace.has_value());
40+
EXPECT_NE(info.stackTrace->find("Stack trace:"), std::string::npos)
41+
<< "Stack trace should contain raw trace section, got: "
42+
<< *info.stackTrace;
43+
#ifndef _WIN32
44+
// Windows without PDB debug info can't resolve file paths or function names
4045
EXPECT_NE(
4146
info.stackTrace->find("EdenErrorInfoBuilderTest.cpp"),
4247
std::string::npos)
4348
<< "Stack trace should contain source file, got: " << *info.stackTrace;
44-
#ifdef __linux__
45-
EXPECT_NE(info.stackTrace->find("Stack trace:"), std::string::npos)
46-
<< "Stack trace should contain raw trace section, got: "
49+
EXPECT_NE(info.stackTrace->find("throwRuntimeError"), std::string::npos)
50+
<< "Stack trace should contain the throwing function, got: "
4751
<< *info.stackTrace;
4852
#endif
4953
}

eden/fs/telemetry/test/ThrowTraceCaptureTest.cpp

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414

1515
using namespace facebook::eden;
1616

17-
#ifdef __linux__
1817
namespace {
1918

2019
[[noreturn]] FOLLY_NOINLINE void innerThrowingFunc() {
@@ -26,23 +25,21 @@ namespace {
2625
}
2726

2827
} // namespace
29-
#endif
3028

3129
TEST(ThrowTraceCaptureTest, CapturesThrowSiteStackTrace) {
32-
#ifndef __linux__
33-
GTEST_SKIP() << "Stack trace capture not yet implemented on this platform";
34-
#else
3530
try {
3631
outerThrowingFunc();
3732
} catch (const std::exception&) {
3833
auto trace = getThrowSiteStackTrace();
3934
ASSERT_TRUE(trace.has_value()) << "Stack trace capture is broken";
35+
#ifndef _WIN32
36+
// Windows without PDB debug info can't resolve function names
4037
EXPECT_NE(trace->find("innerThrowingFunc"), std::string::npos)
4138
<< "Expected throw-site function in trace, got: " << *trace;
4239
EXPECT_NE(trace->find("outerThrowingFunc"), std::string::npos)
4340
<< "Expected caller function in trace, got: " << *trace;
44-
}
4541
#endif
42+
}
4643
}
4744

4845
TEST(ThrowTraceCaptureTest, ReturnsNulloptOutsideCatchBlock) {

0 commit comments

Comments
 (0)