Skip to content

Commit 044805c

Browse files
authored
Enhance createdump to detect alt stack execution (#127068)
createdump's UnwindNativeFrames fails to capture the original thread stack when a crash occurs on a thread using an alternate signal stack. The native unwinder's monotonic-SP guard breaks we cross crosses the signal trampoline back to the original stack, because the SP legitimately decreases. This causes the unwinder to stop early, omitting the original stack memory from the dump. Use `unw_is_signal_frame` in `PAL_VirtualUnwindOutOfProc` to detect signal trampoline frames. When a signal frame is detected, `UnwindNativeFrames` allows a single SP decrease, enabling the unwinder to cross back to the original stack and capture its memory.
1 parent 4f0a034 commit 044805c

4 files changed

Lines changed: 36 additions & 8 deletions

File tree

src/coreclr/debug/createdump/createdumppal.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ typedef BOOL (*PFN_PAL_VirtualUnwindOutOfProc)(
2323
CONTEXT *context,
2424
PULONG64 functionStart,
2525
SIZE_T baseAddress,
26-
UnwindReadMemoryCallback readMemoryCallback);
26+
UnwindReadMemoryCallback readMemoryCallback,
27+
bool *isSignalFrame);
2728

2829
typedef BOOL (*PFN_PAL_GetUnwindInfoSize)(
2930
SIZE_T baseAddress,
@@ -129,13 +130,14 @@ PAL_VirtualUnwindOutOfProc(
129130
CONTEXT *context,
130131
PULONG64 functionStart,
131132
SIZE_T baseAddress,
132-
UnwindReadMemoryCallback readMemoryCallback)
133+
UnwindReadMemoryCallback readMemoryCallback,
134+
bool *isSignalFrame)
133135
{
134136
if (!InitializePAL() || g_PAL_VirtualUnwindOutOfProc == nullptr)
135137
{
136138
return FALSE;
137139
}
138-
return g_PAL_VirtualUnwindOutOfProc(context, functionStart, baseAddress, readMemoryCallback);
140+
return g_PAL_VirtualUnwindOutOfProc(context, functionStart, baseAddress, readMemoryCallback, isSignalFrame);
139141
}
140142

141143
BOOL

src/coreclr/debug/createdump/threadinfo.cpp

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ ThreadInfo::UnwindNativeFrames(CONTEXT* pContext)
5151
uint64_t previousSp = 0;
5252
uint64_t previousIp = 0;
5353
int ipMatchCount = 0;
54+
bool isSignalFrame = false;
55+
bool crossedSignalTrampoline = false;
5456

5557
// For each native frame, add a page around the IP and any unwind info not already
5658
// added in VisitProgramHeader (Linux) and VisitSection (MacOS) to the dump.
@@ -69,10 +71,19 @@ ThreadInfo::UnwindNativeFrames(CONTEXT* pContext)
6971
sp++;
7072
}
7173
#endif
72-
if (ip == 0 || sp <= previousSp) {
74+
// When a signal handler uses SA_ONSTACK (alternate signal stack), the SP can legitimately
75+
// decrease when unwinding crosses the signal trampoline back to the original thread stack.
76+
// Allow the SP decrease if the current frame is a signal trampoline (detected by the
77+
// previous unwind call) and we haven't already crossed one (limit to one crossing to
78+
// bound corruption damage).
79+
if (ip == 0 || sp == 0 || (sp <= previousSp && (!isSignalFrame || crossedSignalTrampoline))) {
7380
TRACE_VERBOSE("Unwind: sp not increasing or ip == 0 sp %p ip %p\n", (void*)sp, (void*)ip);
7481
break;
7582
}
83+
if (sp < previousSp)
84+
{
85+
crossedSignalTrampoline = true;
86+
}
7687
// Break out of the endless loop if the IP matches over a 1000 times. This is a fallback
7788
// behavior of libunwind when the module the IP is in doesn't have unwind info and for
7889
// simple stack overflows. The stack memory is added to the dump in GetThreadStack and
@@ -104,7 +115,8 @@ ThreadInfo::UnwindNativeFrames(CONTEXT* pContext)
104115

105116
// Unwind the native frame adding all the memory accessed to the core dump via the read memory adapter.
106117
ULONG64 functionStart;
107-
if (!PAL_VirtualUnwindOutOfProc(pContext, &functionStart, baseAddress, ReadMemoryAdapter)) {
118+
isSignalFrame = false;
119+
if (!PAL_VirtualUnwindOutOfProc(pContext, &functionStart, baseAddress, ReadMemoryAdapter, &isSignalFrame)) {
108120
TRACE("Unwind: PAL_VirtualUnwindOutOfProc returned false\n");
109121
break;
110122
}

src/coreclr/pal/inc/pal.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2335,7 +2335,7 @@ typedef BOOL(*UnwindReadMemoryCallback)(PVOID address, PVOID buffer, SIZE_T size
23352335

23362336
PALIMPORT BOOL PALAPI PAL_VirtualUnwind(CONTEXT *context);
23372337

2338-
PALIMPORT BOOL PALAPI PAL_VirtualUnwindOutOfProc(CONTEXT *context, PULONG64 functionStart, SIZE_T baseAddress, UnwindReadMemoryCallback readMemoryCallback);
2338+
PALIMPORT BOOL PALAPI PAL_VirtualUnwindOutOfProc(CONTEXT *context, PULONG64 functionStart, SIZE_T baseAddress, UnwindReadMemoryCallback readMemoryCallback, bool *isSignalFrame);
23392339

23402340
PALIMPORT BOOL PALAPI PAL_GetUnwindInfoSize(SIZE_T baseAddress, ULONG64 ehFrameHdrAddr, UnwindReadMemoryCallback readMemoryCallback, PULONG64 ehFrameStart, PULONG64 ehFrameSize);
23412341

src/coreclr/pal/src/exception/remote-unwind.cpp

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2328,17 +2328,23 @@ static unw_accessors_t unwind_accessors = init_unwind_accessors();
23282328
functionStart - the pointer to return the starting address of the function or nullptr
23292329
baseAddress - base address of the module to find the unwind info
23302330
readMemoryCallback - reads memory from the target
2331+
isSignalFrame - output parameter: set to true if the unwound-to frame is a signal trampoline
23312332
--*/
23322333
BOOL
23332334
PALAPI
2334-
PAL_VirtualUnwindOutOfProc(CONTEXT *context, PULONG64 functionStart, SIZE_T baseAddress, UnwindReadMemoryCallback readMemoryCallback)
2335+
PAL_VirtualUnwindOutOfProc(CONTEXT *context, PULONG64 functionStart, SIZE_T baseAddress, UnwindReadMemoryCallback readMemoryCallback, bool *isSignalFrame)
23352336
{
23362337
unw_addr_space_t addrSpace = 0;
23372338
unw_cursor_t cursor;
23382339
libunwindInfo info;
23392340
BOOL result = FALSE;
23402341
int st;
23412342

2343+
if (isSignalFrame)
2344+
{
2345+
*isSignalFrame = false;
2346+
}
2347+
23422348
info.BaseAddress = baseAddress;
23432349
info.Context = context;
23442350
info.FunctionStart = 0;
@@ -2410,6 +2416,14 @@ PAL_VirtualUnwindOutOfProc(CONTEXT *context, PULONG64 functionStart, SIZE_T base
24102416
goto exit;
24112417
}
24122418

2419+
// Check if the frame we landed on is a signal trampoline. When a signal handler uses
2420+
// SA_ONSTACK, stepping from a signal frame crosses from the alternate signal stack to the
2421+
// original thread stack, which can cause the SP to decrease.
2422+
if (isSignalFrame && unw_is_signal_frame(&cursor) > 0)
2423+
{
2424+
*isSignalFrame = true;
2425+
}
2426+
24132427
UnwindContextToContext(&cursor, context);
24142428

24152429
result = TRUE;
@@ -2572,7 +2586,7 @@ PAL_GetUnwindInfoSize(SIZE_T baseAddress, ULONG64 ehFrameHdrAddr, UnwindReadMemo
25722586

25732587
BOOL
25742588
PALAPI
2575-
PAL_VirtualUnwindOutOfProc(CONTEXT *context, PULONG64 functionStart, SIZE_T baseAddress, UnwindReadMemoryCallback readMemoryCallback)
2589+
PAL_VirtualUnwindOutOfProc(CONTEXT *context, PULONG64 functionStart, SIZE_T baseAddress, UnwindReadMemoryCallback readMemoryCallback, bool *isSignalFrame)
25762590
{
25772591
return FALSE;
25782592
}

0 commit comments

Comments
 (0)