Skip to content

Commit 8c02d27

Browse files
MichaelCuevasmeta-codesync[bot]
authored andcommitted
Add death test for stack-overflow exception filter path
Summary: D116557659 made `windowsExceptionFilter` stack-overflow-safe: for EXCEPTION_STACK_OVERFLOW it skips DbgHelp symbolization (which double-faults on the exhausted stack), writes one static message to stderr, and returns EXCEPTION_CONTINUE_SEARCH so the OS default handling reaches WER and writes the LocalDumps minidump. That is what gives us crash dumps for the ProjFS-drop SEV (S697276, T284863536). This adds a gtest death test that locks in the guarantee that stack overflows reach WER. The death-test child installs the real exception filter and drives a real stack overflow via noinline recursion with a 4KB volatile frame (so the optimizer cannot inline, collapse, or tail-call it). It asserts both discriminating behaviors of the fix: - the child's exit status is STATUS_STACK_OVERFLOW (0xC00000FD), i.e. the exception code propagated through EXCEPTION_CONTINUE_SEARCH to the OS default handling, and - stderr contains "stack overflow detected, deferring to WER for crash dump", the filter's no-stack static message. Against the pre-fix filter the child instead dies of the filter's own access violation (exit 0xC0000005) without writing the message, so this test fails against the old code (verified below). The gtest exit-code predicate compares the unsigned representation because gtest reports the raw Windows exit status as a signed int, making 0xC00000FD appear negative. The BUCK target is gated to Windows with `compatible_with`. For `network_access` (required by BUCKLINT's network_access_required), the network_access_utils helpers cannot be used directly: they all resolve to "all" on non-Linux, and the Windows RE test platform rejects unisolated tests (NetworkIsolationType::NONE is not in its allow-list). Instead the target uses a select that isolates on Linux and leaves network_access unset on Windows so the platform default isolation applies - the same pattern as buck2/tests/meta_only/e2e/targets/cross_os_tests. This test needs no network. Differential Revision: D116566143 fbshipit-source-id: aca3415eb61bda498a3af02a7683189ba2e6c3bb
1 parent db5860c commit 8c02d27

2 files changed

Lines changed: 91 additions & 0 deletions

File tree

eden/fs/utils/test/BUCK

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,29 @@ cpp_unittest(
189189
],
190190
)
191191

192+
cpp_unittest(
193+
name = "win_stacktrace_test",
194+
srcs = ["WinStackTraceTest.cpp"],
195+
compatible_with = [
196+
"ovr_config//os:windows",
197+
],
198+
# The network_access_utils helpers resolve to "all" on non-Linux, which
199+
# the Windows RE test platform rejects (only restricted isolation modes
200+
# and the platform default are allowed). Leave network_access unset on
201+
# Windows so the platform default isolation applies; this test needs no
202+
# network. Same pattern as buck2/tests/meta_only/e2e/targets/cross_os_tests.
203+
network_access = select({
204+
"DEFAULT": None,
205+
"ovr_config//os:linux": network_access_utils.NETWORK_ACCESS_NONE,
206+
}),
207+
supports_static_listing = False,
208+
deps = [
209+
"//eden/fs/utils:win_stacktrace",
210+
"//folly:c_portability",
211+
"//folly/portability:gtest",
212+
],
213+
)
214+
192215
cpp_benchmark(
193216
name = "bench",
194217
srcs = [
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This software may be used and distributed according to the terms of the
5+
* GNU General Public License version 2.
6+
*/
7+
8+
#include "eden/fs/utils/WinStackTrace.h"
9+
10+
#ifdef _WIN32
11+
#include <cstdint>
12+
#include <cstdio>
13+
14+
#include <folly/CPortability.h>
15+
#include <folly/portability/GTest.h>
16+
17+
namespace {
18+
19+
// Recurse with a sizable frame that the optimizer cannot collapse:
20+
// FOLLY_NOINLINE prevents inlining, the volatile buffer forces a real stack
21+
// allocation in every frame, and using the recursive result after the call
22+
// prevents tail-call optimization. The depth bound keeps this from being
23+
// recursion on all control paths (which MSVC rejects with C4717) while still
24+
// being effectively infinite: the stack overflows long before it is reached.
25+
FOLLY_NOINLINE int overflowTheStack(int depth) {
26+
volatile char buffer[4096];
27+
buffer[0] = static_cast<char>(depth);
28+
buffer[sizeof(buffer) - 1] = buffer[0];
29+
if (depth < 100000000) {
30+
return overflowTheStack(depth + 1) + buffer[sizeof(buffer) - 1];
31+
}
32+
return buffer[0];
33+
}
34+
35+
bool exitedWithStackOverflow(int exitCode) {
36+
// gtest reports the raw Windows exit status as an int, so
37+
// STATUS_STACK_OVERFLOW (0xC00000FD) appears as a negative value; compare
38+
// the unsigned representation.
39+
if (static_cast<uint32_t>(exitCode) != 0xC00000FDu) {
40+
fprintf(
41+
stderr,
42+
"unexpected exit code: 0x%X\n",
43+
static_cast<unsigned int>(exitCode));
44+
return false;
45+
}
46+
return true;
47+
}
48+
49+
} // namespace
50+
51+
TEST(WinStackTraceTest, stackOverflowDefersToWer) {
52+
// The death-test child installs the real exception filter and drives a
53+
// real stack overflow. The filter must not symbolize on the exhausted
54+
// stack: it writes one static message to stderr and returns
55+
// EXCEPTION_CONTINUE_SEARCH, so the OS default handling (WER) terminates
56+
// the child with STATUS_STACK_OVERFLOW as the exit status. If the filter
57+
// regresses to symbolizing (or holding large locals in its own frame), it
58+
// double-faults before writing anything and this test fails on the missing
59+
// stderr message.
60+
EXPECT_EXIT(
61+
{
62+
facebook::eden::installWindowsExceptionFilter();
63+
overflowTheStack(0);
64+
},
65+
exitedWithStackOverflow,
66+
"stack overflow detected, deferring to WER for crash dump");
67+
}
68+
#endif

0 commit comments

Comments
 (0)