Skip to content

Commit 133396c

Browse files
committed
Bug 1515229 - Make MozStackWalk/MozWalkTheStack frame skipping more reliable. r=gerald,nika,bobowen,jld
Differential Revision: https://phabricator.services.mozilla.com/D110899
1 parent c592078 commit 133396c

File tree

20 files changed

+496
-157
lines changed

20 files changed

+496
-157
lines changed

memory/replace/dmd/DMD.cpp

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -663,12 +663,7 @@ static uint32_t gGCStackTraceTableWhenSizeExceeds = 4 * 1024;
663663
void* stackEnd = pthread_get_stackaddr_np(pthread_self());
664664
FramePointerStackWalk(StackWalkCallback, MaxFrames, &tmp, fp, stackEnd);
665665
#else
666-
# if defined(XP_WIN) && defined(_M_X64)
667-
int skipFrames = 1;
668-
# else
669-
int skipFrames = 2;
670-
# endif
671-
MozStackWalk(StackWalkCallback, skipFrames, MaxFrames, &tmp);
666+
MozStackWalk(StackWalkCallback, nullptr, MaxFrames, &tmp);
672667
#endif
673668
}
674669

memory/replace/phc/PHC.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ void StackTrace::Fill() {
232232
void* stackEnd = pthread_get_stackaddr_np(pthread_self());
233233
FramePointerStackWalk(StackWalkCallback, kMaxFrames, this, fp, stackEnd);
234234
#else
235-
MozStackWalk(StackWalkCallback, /* aSkipFrames = */ 0, kMaxFrames, this);
235+
MozStackWalk(StackWalkCallback, nullptr, kMaxFrames, this);
236236
#endif
237237
}
238238

mfbt/Assertions.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,13 +91,13 @@ MOZ_ReportAssertionFailure(const char* aStr, const char* aFilename,
9191
"Assertion failure: %s, at %s:%d\n", aStr, aFilename,
9292
aLine);
9393
# if defined(MOZ_DUMP_ASSERTION_STACK)
94-
MozWalkTheStackWithWriter(MOZ_ReportAssertionFailurePrintFrame,
95-
/* aSkipFrames */ 1, /* aMaxFrames */ 0);
94+
MozWalkTheStackWithWriter(MOZ_ReportAssertionFailurePrintFrame, CallerPC(),
95+
/* aMaxFrames */ 0);
9696
# endif
9797
#else
9898
fprintf(stderr, "Assertion failure: %s, at %s:%d\n", aStr, aFilename, aLine);
9999
# if defined(MOZ_DUMP_ASSERTION_STACK)
100-
MozWalkTheStack(stderr, /* aSkipFrames */ 1, /* aMaxFrames */ 0);
100+
MozWalkTheStack(stderr, CallerPC(), /* aMaxFrames */ 0);
101101
# endif
102102
fflush(stderr);
103103
#endif
@@ -112,7 +112,7 @@ MOZ_MAYBE_UNUSED static MOZ_COLD MOZ_NEVER_INLINE void MOZ_ReportCrash(
112112
#else
113113
fprintf(stderr, "Hit MOZ_CRASH(%s) at %s:%d\n", aStr, aFilename, aLine);
114114
# if defined(MOZ_DUMP_ASSERTION_STACK)
115-
MozWalkTheStack(stderr, /* aSkipFrames */ 1, /* aMaxFrames */ 0);
115+
MozWalkTheStack(stderr, CallerPC(), /* aMaxFrames */ 0);
116116
# endif
117117
fflush(stderr);
118118
#endif

mozglue/misc/StackWalk.cpp

Lines changed: 65 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,26 @@ extern MOZ_EXPORT void* __libc_stack_end; // from ld-linux.so
6969
# include <pthread.h>
7070
#endif
7171

72+
class FrameSkipper {
73+
public:
74+
constexpr FrameSkipper() : mPc(0) {}
75+
bool ShouldSkipPC(void* aPC) {
76+
// Skip frames until we encounter the one we were initialized with,
77+
// and then never skip again.
78+
if (mPc != 0) {
79+
if (mPc != uintptr_t(aPC)) {
80+
return true;
81+
}
82+
mPc = 0;
83+
}
84+
return false;
85+
}
86+
explicit FrameSkipper(const void* aPc) : mPc(uintptr_t(aPc)) {}
87+
88+
private:
89+
uintptr_t mPc;
90+
};
91+
7292
#ifdef XP_WIN
7393

7494
# include <windows.h>
@@ -92,7 +112,7 @@ struct WalkStackData {
92112
// Are we walking the stack of the calling thread? Note that we need to avoid
93113
// calling fprintf and friends if this is false, in order to avoid deadlocks.
94114
bool walkCallingThread;
95-
uint32_t skipFrames;
115+
const void* firstFramePC;
96116
HANDLE thread;
97117
HANDLE process;
98118
HANDLE eventStart;
@@ -243,8 +263,7 @@ static void WalkStackMain64(struct WalkStackData* aData) {
243263
bool firstFrame = true;
244264
# endif
245265

246-
// Skip our own stack walking frames.
247-
int skip = (aData->walkCallingThread ? 3 : 0) + aData->skipFrames;
266+
FrameSkipper skipper(aData->firstFramePC);
248267

249268
// Now walk the stack.
250269
while (true) {
@@ -349,7 +368,7 @@ static void WalkStackMain64(struct WalkStackData* aData) {
349368
break;
350369
}
351370

352-
if (skip-- > 0) {
371+
if (skipper.ShouldSkipPC((void*)addr)) {
353372
continue;
354373
}
355374

@@ -384,7 +403,7 @@ static void WalkStackMain64(struct WalkStackData* aData) {
384403
*/
385404

386405
static void DoMozStackWalkThread(MozWalkStackCallback aCallback,
387-
uint32_t aSkipFrames, uint32_t aMaxFrames,
406+
const void* aFirstFramePC, uint32_t aMaxFrames,
388407
void* aClosure, HANDLE aThread,
389408
CONTEXT* aContext) {
390409
struct WalkStackData data;
@@ -401,7 +420,7 @@ static void DoMozStackWalkThread(MozWalkStackCallback aCallback,
401420
data.walkCallingThread = (threadId == currentThreadId);
402421
}
403422

404-
data.skipFrames = aSkipFrames;
423+
data.firstFramePC = aFirstFramePC;
405424
data.thread = targetThread;
406425
data.process = ::GetCurrentProcess();
407426
void* local_pcs[1024];
@@ -435,14 +454,15 @@ static void DoMozStackWalkThread(MozWalkStackCallback aCallback,
435454
MFBT_API void MozStackWalkThread(MozWalkStackCallback aCallback,
436455
uint32_t aMaxFrames, void* aClosure,
437456
HANDLE aThread, CONTEXT* aContext) {
438-
DoMozStackWalkThread(aCallback, /* aSkipFrames = */ 0, aMaxFrames, aClosure,
439-
aThread, aContext);
457+
DoMozStackWalkThread(aCallback, CallerPC(), aMaxFrames, aClosure, aThread,
458+
aContext);
440459
}
441460

442-
MFBT_API void MozStackWalk(MozWalkStackCallback aCallback, uint32_t aSkipFrames,
443-
uint32_t aMaxFrames, void* aClosure) {
444-
DoMozStackWalkThread(aCallback, aSkipFrames, aMaxFrames, aClosure, nullptr,
445-
nullptr);
461+
MFBT_API void MozStackWalk(MozWalkStackCallback aCallback,
462+
const void* aFirstFramePC, uint32_t aMaxFrames,
463+
void* aClosure) {
464+
DoMozStackWalkThread(aCallback, aFirstFramePC ? aFirstFramePC : CallerPC(),
465+
aMaxFrames, aClosure, nullptr, nullptr);
446466
}
447467

448468
static BOOL CALLBACK callbackEspecial64(PCSTR aModuleName, DWORD64 aModuleBase,
@@ -691,12 +711,13 @@ void DemangleSymbol(const char* aSymbol, char* aBuffer, int aBufLen) {
691711
(MOZ_STACKWALK_SUPPORTS_MACOSX || MOZ_STACKWALK_SUPPORTS_LINUX))
692712

693713
static void DoFramePointerStackWalk(MozWalkStackCallback aCallback,
694-
uint32_t aSkipFrames, uint32_t aMaxFrames,
695-
void* aClosure, void** aBp,
696-
void* aStackEnd);
714+
const void* aFirstFramePC,
715+
uint32_t aMaxFrames, void* aClosure,
716+
void** aBp, void* aStackEnd);
697717

698-
MFBT_API void MozStackWalk(MozWalkStackCallback aCallback, uint32_t aSkipFrames,
699-
uint32_t aMaxFrames, void* aClosure) {
718+
MFBT_API void MozStackWalk(MozWalkStackCallback aCallback,
719+
const void* aFirstFramePC, uint32_t aMaxFrames,
720+
void* aClosure) {
700721
// Get the frame pointer
701722
void** bp = (void**)__builtin_frame_address(0);
702723

@@ -733,7 +754,7 @@ MFBT_API void MozStackWalk(MozWalkStackCallback aCallback, uint32_t aSkipFrames,
733754
# else
734755
# error Unsupported configuration
735756
# endif
736-
DoFramePointerStackWalk(aCallback, aSkipFrames, aMaxFrames, aClosure, bp,
757+
DoFramePointerStackWalk(aCallback, aFirstFramePC, aMaxFrames, aClosure, bp,
737758
stackEnd);
738759
}
739760

@@ -744,7 +765,7 @@ MFBT_API void MozStackWalk(MozWalkStackCallback aCallback, uint32_t aSkipFrames,
744765

745766
struct unwind_info {
746767
MozWalkStackCallback callback;
747-
int skip;
768+
FrameSkipper skipper;
748769
int maxFrames;
749770
int numFrames;
750771
void* closure;
@@ -755,7 +776,7 @@ static _Unwind_Reason_Code unwind_callback(struct _Unwind_Context* context,
755776
unwind_info* info = static_cast<unwind_info*>(closure);
756777
void* pc = reinterpret_cast<void*>(_Unwind_GetIP(context));
757778
// TODO Use something like '_Unwind_GetGR()' to get the stack pointer.
758-
if (--info->skip < 0) {
779+
if (!info->skipper.ShouldSkipPC(pc)) {
759780
info->numFrames++;
760781
(*info->callback)(info->numFrames, pc, nullptr, info->closure);
761782
if (info->maxFrames != 0 && info->numFrames == info->maxFrames) {
@@ -766,11 +787,12 @@ static _Unwind_Reason_Code unwind_callback(struct _Unwind_Context* context,
766787
return _URC_NO_REASON;
767788
}
768789

769-
MFBT_API void MozStackWalk(MozWalkStackCallback aCallback, uint32_t aSkipFrames,
770-
uint32_t aMaxFrames, void* aClosure) {
790+
MFBT_API void MozStackWalk(MozWalkStackCallback aCallback,
791+
const void* aFirstFramePC, uint32_t aMaxFrames,
792+
void* aClosure) {
771793
unwind_info info;
772794
info.callback = aCallback;
773-
info.skip = aSkipFrames + 1;
795+
info.skipper = FrameSkipper(aFirstFramePC ? aFirstFramePC : CallerPC());
774796
info.maxFrames = aMaxFrames;
775797
info.numFrames = 0;
776798
info.closure = aClosure;
@@ -840,8 +862,9 @@ bool MFBT_API MozDescribeCodeAddress(void* aPC,
840862

841863
#else // unsupported platform.
842864

843-
MFBT_API void MozStackWalk(MozWalkStackCallback aCallback, uint32_t aSkipFrames,
844-
uint32_t aMaxFrames, void* aClosure) {}
865+
MFBT_API void MozStackWalk(MozWalkStackCallback aCallback,
866+
const void* aFirstFramePC, uint32_t aMaxFrames,
867+
void* aClosure) {}
845868

846869
MFBT_API bool MozDescribeCodeAddress(void* aPC,
847870
MozCodeAddressDetails* aDetails) {
@@ -859,13 +882,14 @@ MFBT_API bool MozDescribeCodeAddress(void* aPC,
859882
#if defined(XP_WIN) || defined(XP_MACOSX) || defined(XP_LINUX)
860883
MOZ_ASAN_BLACKLIST
861884
static void DoFramePointerStackWalk(MozWalkStackCallback aCallback,
862-
uint32_t aSkipFrames, uint32_t aMaxFrames,
863-
void* aClosure, void** aBp,
864-
void* aStackEnd) {
885+
const void* aFirstFramePC,
886+
uint32_t aMaxFrames, void* aClosure,
887+
void** aBp, void* aStackEnd) {
865888
// Stack walking code courtesy Kipp's "leaky".
866889

867-
int32_t skip = aSkipFrames;
890+
FrameSkipper skipper(aFirstFramePC);
868891
uint32_t numFrames = 0;
892+
869893
while (aBp) {
870894
void** next = (void**)*aBp;
871895
// aBp may not be a frame pointer on i386 if code was compiled with
@@ -885,7 +909,7 @@ static void DoFramePointerStackWalk(MozWalkStackCallback aCallback,
885909
void* pc = *(aBp + 1);
886910
aBp += 2;
887911
# endif
888-
if (--skip < 0) {
912+
if (!skipper.ShouldSkipPC(pc)) {
889913
// Assume that the SP points to the BP of the function
890914
// it called. We can't know the exact location of the SP
891915
// but this should be sufficient for our use the SP
@@ -904,8 +928,10 @@ namespace mozilla {
904928

905929
void FramePointerStackWalk(MozWalkStackCallback aCallback, uint32_t aMaxFrames,
906930
void* aClosure, void** aBp, void* aStackEnd) {
907-
DoFramePointerStackWalk(aCallback, /* aSkipFrames = */ 0, aMaxFrames,
908-
aClosure, aBp, aStackEnd);
931+
// We don't pass a aFirstFramePC because we start walking the stack from the
932+
// frame at aBp.
933+
DoFramePointerStackWalk(aCallback, nullptr, aMaxFrames, aClosure, aBp,
934+
aStackEnd);
909935
}
910936

911937
} // namespace mozilla
@@ -914,6 +940,7 @@ void FramePointerStackWalk(MozWalkStackCallback aCallback, uint32_t aMaxFrames,
914940

915941
namespace mozilla {
916942
MFBT_API void FramePointerStackWalk(MozWalkStackCallback aCallback,
943+
const void* aFirstFramePC,
917944
uint32_t aMaxFrames, void* aClosure,
918945
void** aBp, void* aStackEnd) {}
919946
} // namespace mozilla
@@ -1006,10 +1033,11 @@ static bool WalkTheStackEnabled() {
10061033
return result;
10071034
}
10081035

1009-
MFBT_API void MozWalkTheStack(FILE* aStream, uint32_t aSkipFrames,
1036+
MFBT_API void MozWalkTheStack(FILE* aStream, const void* aFirstFramePC,
10101037
uint32_t aMaxFrames) {
10111038
if (WalkTheStackEnabled()) {
1012-
MozStackWalk(PrintStackFrame, aSkipFrames + 1, aMaxFrames, aStream);
1039+
MozStackWalk(PrintStackFrame, aFirstFramePC ? aFirstFramePC : CallerPC(),
1040+
aMaxFrames, aStream);
10131041
}
10141042
}
10151043

@@ -1022,9 +1050,10 @@ static void WriteStackFrame(uint32_t aFrameNumber, void* aPC, void* aSP,
10221050
}
10231051

10241052
MFBT_API void MozWalkTheStackWithWriter(void (*aWriter)(const char*),
1025-
uint32_t aSkipFrames,
1053+
const void* aFirstFramePC,
10261054
uint32_t aMaxFrames) {
10271055
if (WalkTheStackEnabled()) {
1028-
MozStackWalk(WriteStackFrame, aSkipFrames + 1, aMaxFrames, (void*)aWriter);
1056+
MozStackWalk(WriteStackFrame, aFirstFramePC ? aFirstFramePC : CallerPC(),
1057+
aMaxFrames, (void*)aWriter);
10291058
}
10301059
}

mozglue/misc/StackWalk.h

Lines changed: 45 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,24 @@
1515

1616
MOZ_BEGIN_EXTERN_C
1717

18+
/**
19+
* Returns the position of the Program Counter for the caller of the current
20+
* function. This is meant to be used to feed the aFirstFramePC argument to
21+
* MozStackWalk or MozWalkTheStack*, and should be used in the last function
22+
* that should be skipped in the trace, and passed down to MozStackWalk or
23+
* MozWalkTheStack*, through any intermediaries.
24+
*
25+
* THIS DOES NOT 100% RELIABLY GIVE THE CALLER PC, but marking functions
26+
* calling this macro with MOZ_NEVER_INLINE gets us close. In cases it doesn't
27+
* give the caller's PC, it may give the caller of the caller, or its caller,
28+
* etc. depending on tail call optimization.
29+
*
30+
* Past versions of stackwalking relied on passing a constant number of frames
31+
* to skip to MozStackWalk or MozWalkTheStack, which fell short in more cases
32+
* (inlining of intermediaries, tail call optimization).
33+
*/
34+
#define CallerPC() __builtin_extract_return_addr(__builtin_return_address(0))
35+
1836
/**
1937
* The callback for MozStackWalk and MozStackWalkThread.
2038
*
@@ -34,18 +52,20 @@ typedef void (*MozWalkStackCallback)(uint32_t aFrameNumber, void* aPC,
3452
* Call aCallback for each stack frame on the current thread, from
3553
* the caller of MozStackWalk to main (or above).
3654
*
37-
* @param aCallback Callback function, called once per frame.
38-
* @param aSkipFrames Number of initial frames to skip. 0 means that
39-
* the first callback will be for the caller of
40-
* MozStackWalk.
41-
* @param aMaxFrames Maximum number of frames to trace. 0 means no limit.
42-
* @param aClosure Caller-supplied data passed through to aCallback.
55+
* @param aCallback Callback function, called once per frame.
56+
* @param aFirstFramePC Position of the Program Counter where the trace
57+
* starts from. All frames seen before reaching that
58+
* address are skipped. Nullptr means that the first
59+
* callback will be for the caller of MozStackWalk.
60+
* @param aMaxFrames Maximum number of frames to trace. 0 means no limit.
61+
* @param aClosure Caller-supplied data passed through to aCallback.
4362
*
4463
* May skip some stack frames due to compiler optimizations or code
4564
* generation.
4665
*/
47-
MFBT_API void MozStackWalk(MozWalkStackCallback aCallback, uint32_t aSkipFrames,
48-
uint32_t aMaxFrames, void* aClosure);
66+
MFBT_API void MozStackWalk(MozWalkStackCallback aCallback,
67+
const void* aFirstFramePC, uint32_t aMaxFrames,
68+
void* aClosure);
4969

5070
typedef struct {
5171
/*
@@ -147,29 +167,32 @@ MFBT_API int MozFormatCodeAddressDetails(char* aBuffer, uint32_t aBufferSize,
147167
/**
148168
* Walk the stack and print the stack trace to the given stream.
149169
*
150-
* @param aStream A stdio stream.
151-
* @param aSkipFrames Number of initial frames to skip. 0 means that
152-
* the first callback will be for the caller of
153-
* MozWalkTheStack.
154-
* @param aMaxFrames Maximum number of frames to trace. 0 means no limit.
170+
* @param aStream A stdio stream.
171+
* @param aFirstFramePC Position of the Program Counter where the trace
172+
* starts from. All frames seen before reaching that
173+
* address are skipped. Nullptr means that the first
174+
* callback will be for the caller of MozWalkTheStack.
175+
* @param aMaxFrames Maximum number of frames to trace. 0 means no limit.
155176
*/
156177
MFBT_API void MozWalkTheStack(FILE* aStream,
157-
uint32_t aSkipFrames FRAMES_DEFAULT,
178+
const void* aFirstFramePC FRAMES_DEFAULT,
158179
uint32_t aMaxFrames FRAMES_DEFAULT);
159180

160181
/**
161182
* Walk the stack and send each stack trace line to a callback writer.
162183
* Each line string is null terminated but doesn't contain a '\n' character.
163184
*
164-
* @param aWriter The callback.
165-
* @param aSkipFrames Number of initial frames to skip. 0 means that
166-
* the first callback will be for the caller of
167-
* MozWalkTheStack.
168-
* @param aMaxFrames Maximum number of frames to trace. 0 means no limit.
185+
* @param aWriter The callback.
186+
* @param aFirstFramePC Position of the Program Counter where the trace
187+
* starts from. All frames seen before reaching that
188+
* address are skipped. Nullptr means that the first
189+
* callback will be for the caller of
190+
* MozWalkTheStackWithWriter.
191+
* @param aMaxFrames Maximum number of frames to trace. 0 means no limit.
169192
*/
170-
MFBT_API void MozWalkTheStackWithWriter(void (*aWriter)(const char*),
171-
uint32_t aSkipFrames FRAMES_DEFAULT,
172-
uint32_t aMaxFrames FRAMES_DEFAULT);
193+
MFBT_API void MozWalkTheStackWithWriter(
194+
void (*aWriter)(const char*), const void* aFirstFramePC FRAMES_DEFAULT,
195+
uint32_t aMaxFrames FRAMES_DEFAULT);
173196

174197
#undef FRAMES_DEFAULT
175198

0 commit comments

Comments
 (0)