Bug 1874684 - Part 4: Prefer const references instead of copying Instant values....
[gecko.git] / mozglue / misc / StackWalk.cpp
blob2fefc5bf4dbacffe09facf4ca9eb3019f8864229
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 /* API for getting a stack trace of the C/C++ stack on the current thread */
9 #include "mozilla/ArrayUtils.h"
10 #include "mozilla/Atomics.h"
11 #include "mozilla/Attributes.h"
12 #include "mozilla/StackWalk.h"
13 #ifdef XP_WIN
14 # include "mozilla/StackWalkThread.h"
15 # include <io.h>
16 #else
17 # include <unistd.h>
18 #endif
19 #include "mozilla/Sprintf.h"
21 #include <string.h>
23 #if defined(ANDROID) && defined(MOZ_LINKER)
24 # include "Linker.h"
25 # include <android/log.h>
26 #endif
28 using namespace mozilla;
30 // for _Unwind_Backtrace from libcxxrt or libunwind
31 // cxxabi.h from libcxxrt implicitly includes unwind.h first
32 #if defined(HAVE__UNWIND_BACKTRACE) && !defined(_GNU_SOURCE)
33 # define _GNU_SOURCE
34 #endif
36 #if defined(HAVE_DLOPEN) || defined(XP_DARWIN)
37 # include <dlfcn.h>
38 #endif
40 #if (defined(XP_DARWIN) && \
41 (defined(__i386) || defined(__ppc__) || defined(HAVE__UNWIND_BACKTRACE)))
42 # define MOZ_STACKWALK_SUPPORTS_MACOSX 1
43 #else
44 # define MOZ_STACKWALK_SUPPORTS_MACOSX 0
45 #endif
47 #if (defined(linux) && \
48 ((defined(__GNUC__) && (defined(__i386) || defined(PPC))) || \
49 defined(HAVE__UNWIND_BACKTRACE)))
50 # define MOZ_STACKWALK_SUPPORTS_LINUX 1
51 #else
52 # define MOZ_STACKWALK_SUPPORTS_LINUX 0
53 #endif
55 #if __GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 1)
56 # define HAVE___LIBC_STACK_END 1
57 #else
58 # define HAVE___LIBC_STACK_END 0
59 #endif
61 #if HAVE___LIBC_STACK_END
62 extern MOZ_EXPORT void* __libc_stack_end; // from ld-linux.so
63 #endif
65 #ifdef ANDROID
66 # include <algorithm>
67 # include <unistd.h>
68 # include <pthread.h>
69 #endif
71 class FrameSkipper {
72 public:
73 constexpr FrameSkipper() : mSkipUntilAddr(0) {}
74 static uintptr_t AddressFromPC(const void* aPC) {
75 #ifdef __arm__
76 // On 32-bit ARM, mask off the thumb bit to get the instruction address.
77 return uintptr_t(aPC) & ~1;
78 #else
79 return uintptr_t(aPC);
80 #endif
82 bool ShouldSkipPC(void* aPC) {
83 // Skip frames until we encounter the one we were initialized with,
84 // and then never skip again.
85 uintptr_t instructionAddress = AddressFromPC(aPC);
86 if (mSkipUntilAddr != 0) {
87 if (mSkipUntilAddr != instructionAddress) {
88 return true;
90 mSkipUntilAddr = 0;
92 return false;
94 explicit FrameSkipper(const void* aPC) : mSkipUntilAddr(AddressFromPC(aPC)) {}
96 private:
97 uintptr_t mSkipUntilAddr;
100 #ifdef XP_WIN
102 # include <windows.h>
103 # include <process.h>
104 # include <stdio.h>
105 # include <malloc.h>
106 # include "mozilla/ArrayUtils.h"
107 # include "mozilla/Atomics.h"
108 # include "mozilla/StackWalk_windows.h"
109 # include "mozilla/WindowsVersion.h"
111 # include <imagehlp.h>
112 // We need a way to know if we are building for WXP (or later), as if we are, we
113 // need to use the newer 64-bit APIs. API_VERSION_NUMBER seems to fit the bill.
114 // A value of 9 indicates we want to use the new APIs.
115 # if API_VERSION_NUMBER < 9
116 # error Too old imagehlp.h
117 # endif
119 // DbgHelp functions are not thread-safe and should therefore be protected by
120 // using this critical section. Only use the critical section after a
121 // successful call to InitializeDbgHelp().
122 CRITICAL_SECTION gDbgHelpCS;
124 # if defined(_M_AMD64) || defined(_M_ARM64)
125 // Because various Win64 APIs acquire function-table locks, we need a way of
126 // preventing stack walking while those APIs are being called. Otherwise, the
127 // stack walker may suspend a thread holding such a lock, and deadlock when the
128 // stack unwind code attempts to wait for that lock.
130 // We're using an atomic counter rather than a critical section because we
131 // don't require mutual exclusion with the stack walker. If the stack walker
132 // determines that it's safe to start unwinding the suspended thread (i.e.
133 // there are no suppressions when the unwind begins), then it's safe to
134 // continue unwinding that thread even if other threads request suppressions
135 // in the meantime, because we can't deadlock with those other threads.
137 // XXX: This global variable is a larger-than-necessary hammer. A more scoped
138 // solution would be to maintain a counter per thread, but then it would be
139 // more difficult for WalkStackMain64 to read the suspended thread's counter.
140 static Atomic<size_t> sStackWalkSuppressions;
142 void SuppressStackWalking() { ++sStackWalkSuppressions; }
144 void DesuppressStackWalking() {
145 auto previousValue = sStackWalkSuppressions--;
146 // We should never desuppress from 0. See bug 1687510 comment 10 for an
147 // example in which this occured.
148 MOZ_RELEASE_ASSERT(previousValue);
151 MFBT_API
152 AutoSuppressStackWalking::AutoSuppressStackWalking() { SuppressStackWalking(); }
154 MFBT_API
155 AutoSuppressStackWalking::~AutoSuppressStackWalking() {
156 DesuppressStackWalking();
159 static uint8_t* sJitCodeRegionStart;
160 static size_t sJitCodeRegionSize;
161 uint8_t* sMsMpegJitCodeRegionStart;
162 size_t sMsMpegJitCodeRegionSize;
164 MFBT_API void RegisterJitCodeRegion(uint8_t* aStart, size_t aSize) {
165 // Currently we can only handle one JIT code region at a time
166 MOZ_RELEASE_ASSERT(!sJitCodeRegionStart);
168 sJitCodeRegionStart = aStart;
169 sJitCodeRegionSize = aSize;
172 MFBT_API void UnregisterJitCodeRegion(uint8_t* aStart, size_t aSize) {
173 // Currently we can only handle one JIT code region at a time
174 MOZ_RELEASE_ASSERT(sJitCodeRegionStart && sJitCodeRegionStart == aStart &&
175 sJitCodeRegionSize == aSize);
177 sJitCodeRegionStart = nullptr;
178 sJitCodeRegionSize = 0;
181 # endif // _M_AMD64 || _M_ARM64
183 // Routine to print an error message to standard error.
184 static void PrintError(const char* aPrefix) {
185 LPSTR lpMsgBuf;
186 DWORD lastErr = GetLastError();
187 FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
188 FORMAT_MESSAGE_IGNORE_INSERTS,
189 nullptr, lastErr,
190 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
191 (LPSTR)&lpMsgBuf, 0, nullptr);
192 fprintf(stderr, "### ERROR: %s: %s", aPrefix,
193 lpMsgBuf ? lpMsgBuf : "(null)\n");
194 fflush(stderr);
195 LocalFree(lpMsgBuf);
198 enum class DbgHelpInitFlags : bool {
199 BasicInit,
200 WithSymbolSupport,
203 // This function ensures that DbgHelp.dll is loaded in the current process,
204 // and initializes the gDbgHelpCS critical section that we use to protect calls
205 // to DbgHelp functions. If DbgHelpInitFlags::WithSymbolSupport is set, we
206 // additionally call the symbol initialization functions from DbgHelp so that
207 // symbol-related functions can be used.
209 // This function is thread-safe and reentrancy-safe. In debug and fuzzing
210 // builds, MOZ_ASSERT and MOZ_CRASH walk the stack to print it before actually
211 // crashing. Hence *any* MOZ_ASSERT or MOZ_CRASH failure reached from
212 // InitializeDbgHelp() leads to rentrancy (see bug 1869997 for an example).
213 // Such failures can occur indirectly when we load dbghelp.dll, because we
214 // override various Microsoft-internal functions that are called upon DLL
215 // loading.
216 [[nodiscard]] static bool InitializeDbgHelp(
217 DbgHelpInitFlags aInitFlags = DbgHelpInitFlags::BasicInit) {
218 // In the code below, it is only safe to reach MOZ_ASSERT or MOZ_CRASH while
219 // sInitializationThreadId is set to the current thread id.
220 static Atomic<DWORD> sInitializationThreadId{0};
221 DWORD currentThreadId = ::GetCurrentThreadId();
223 // This code relies on Windows never giving us a current thread ID of zero.
224 // We make this assumption explicit, by failing if that should ever occur.
225 if (!currentThreadId) {
226 return false;
229 if (sInitializationThreadId == currentThreadId) {
230 // This is a reentrant call and we must abort here.
231 return false;
234 static const bool sHasInitializedDbgHelp = [currentThreadId]() {
235 sInitializationThreadId = currentThreadId;
237 ::InitializeCriticalSection(&gDbgHelpCS);
238 bool dbgHelpLoaded = static_cast<bool>(::LoadLibraryW(L"dbghelp.dll"));
240 MOZ_ASSERT(dbgHelpLoaded);
241 sInitializationThreadId = 0;
242 return dbgHelpLoaded;
243 }();
245 // If we don't need symbol initialization, we are done. If we need it, we
246 // can only proceed if DbgHelp initialization was successful.
247 if (aInitFlags == DbgHelpInitFlags::BasicInit || !sHasInitializedDbgHelp) {
248 return sHasInitializedDbgHelp;
251 static const bool sHasInitializedSymbols = [currentThreadId]() {
252 sInitializationThreadId = currentThreadId;
254 EnterCriticalSection(&gDbgHelpCS);
255 SymSetOptions(SYMOPT_LOAD_LINES | SYMOPT_UNDNAME);
256 bool symbolsInitialized = SymInitialize(GetCurrentProcess(), nullptr, TRUE);
257 /* XXX At some point we need to arrange to call SymCleanup */
258 LeaveCriticalSection(&gDbgHelpCS);
260 if (!symbolsInitialized) {
261 PrintError("SymInitialize");
264 MOZ_ASSERT(symbolsInitialized);
265 sInitializationThreadId = 0;
266 return symbolsInitialized;
267 }();
269 return sHasInitializedSymbols;
272 // Wrapper around a reference to a CONTEXT, to simplify access to main
273 // platform-specific execution registers.
274 // It also avoids using CONTEXT* nullable pointers.
275 class CONTEXTGenericAccessors {
276 public:
277 explicit CONTEXTGenericAccessors(CONTEXT& aCONTEXT) : mCONTEXT(aCONTEXT) {}
279 CONTEXT* CONTEXTPtr() { return &mCONTEXT; }
281 inline auto& PC() {
282 # if defined(_M_AMD64)
283 return mCONTEXT.Rip;
284 # elif defined(_M_ARM64)
285 return mCONTEXT.Pc;
286 # elif defined(_M_IX86)
287 return mCONTEXT.Eip;
288 # else
289 # error "unknown platform"
290 # endif
293 inline auto& SP() {
294 # if defined(_M_AMD64)
295 return mCONTEXT.Rsp;
296 # elif defined(_M_ARM64)
297 return mCONTEXT.Sp;
298 # elif defined(_M_IX86)
299 return mCONTEXT.Esp;
300 # else
301 # error "unknown platform"
302 # endif
305 inline auto& BP() {
306 # if defined(_M_AMD64)
307 return mCONTEXT.Rbp;
308 # elif defined(_M_ARM64)
309 return mCONTEXT.Fp;
310 # elif defined(_M_IX86)
311 return mCONTEXT.Ebp;
312 # else
313 # error "unknown platform"
314 # endif
317 private:
318 CONTEXT& mCONTEXT;
322 * Walk the stack, translating PC's found into strings and recording the
323 * chain in aBuffer. For this to work properly, the DLLs must be rebased
324 * so that the address in the file agrees with the address in memory.
325 * Otherwise StackWalk will return FALSE when it hits a frame in a DLL
326 * whose in memory address doesn't match its in-file address.
329 static void DoMozStackWalkThread(MozWalkStackCallback aCallback,
330 const void* aFirstFramePC, uint32_t aMaxFrames,
331 void* aClosure, HANDLE aThread,
332 CONTEXT* aContext) {
333 # if defined(_M_IX86)
334 if (!InitializeDbgHelp()) {
335 return;
337 # endif
339 HANDLE targetThread = aThread;
340 bool walkCallingThread;
341 if (!targetThread) {
342 targetThread = ::GetCurrentThread();
343 walkCallingThread = true;
344 } else {
345 DWORD targetThreadId = ::GetThreadId(targetThread);
346 DWORD currentThreadId = ::GetCurrentThreadId();
347 walkCallingThread = (targetThreadId == currentThreadId);
350 // If not already provided, get a context for the specified thread.
351 CONTEXT context_buf;
352 if (!aContext) {
353 memset(&context_buf, 0, sizeof(CONTEXT));
354 context_buf.ContextFlags = CONTEXT_FULL;
355 if (walkCallingThread) {
356 ::RtlCaptureContext(&context_buf);
357 } else if (!GetThreadContext(targetThread, &context_buf)) {
358 return;
361 CONTEXTGenericAccessors context{aContext ? *aContext : context_buf};
363 # if defined(_M_IX86)
364 // Setup initial stack frame to walk from.
365 STACKFRAME64 frame64;
366 memset(&frame64, 0, sizeof(frame64));
367 frame64.AddrPC.Offset = context.PC();
368 frame64.AddrStack.Offset = context.SP();
369 frame64.AddrFrame.Offset = context.BP();
370 frame64.AddrPC.Mode = AddrModeFlat;
371 frame64.AddrStack.Mode = AddrModeFlat;
372 frame64.AddrFrame.Mode = AddrModeFlat;
373 frame64.AddrReturn.Mode = AddrModeFlat;
374 # endif
376 # if defined(_M_AMD64) || defined(_M_ARM64)
377 // If there are any active suppressions, then at least one thread (we don't
378 // know which) is holding a lock that can deadlock RtlVirtualUnwind. Since
379 // that thread may be the one that we're trying to unwind, we can't proceed.
381 // But if there are no suppressions, then our target thread can't be holding
382 // a lock, and it's safe to proceed. By virtue of being suspended, the target
383 // thread can't acquire any new locks during the unwind process, so we only
384 // need to do this check once. After that, sStackWalkSuppressions can be
385 // changed by other threads while we're unwinding, and that's fine because
386 // we can't deadlock with those threads.
387 if (sStackWalkSuppressions) {
388 return;
391 bool firstFrame = true;
392 # endif
394 FrameSkipper skipper(aFirstFramePC);
396 uint32_t frames = 0;
398 // Now walk the stack.
399 while (true) {
400 DWORD64 addr;
401 DWORD64 spaddr;
403 # if defined(_M_IX86)
404 // 32-bit frame unwinding.
405 // Debug routines are not threadsafe, so grab the lock.
406 EnterCriticalSection(&gDbgHelpCS);
407 BOOL ok =
408 StackWalk64(IMAGE_FILE_MACHINE_I386, ::GetCurrentProcess(),
409 targetThread, &frame64, context.CONTEXTPtr(), nullptr,
410 SymFunctionTableAccess64, // function table access routine
411 SymGetModuleBase64, // module base routine
413 LeaveCriticalSection(&gDbgHelpCS);
415 if (ok) {
416 addr = frame64.AddrPC.Offset;
417 spaddr = frame64.AddrStack.Offset;
418 } else {
419 addr = 0;
420 spaddr = 0;
421 if (walkCallingThread) {
422 PrintError("WalkStack64");
426 if (!ok) {
427 break;
430 # elif defined(_M_AMD64) || defined(_M_ARM64)
432 auto currentInstr = context.PC();
434 // If we reach a frame in JIT code, we don't have enough information to
435 // unwind, so we have to give up.
436 if (sJitCodeRegionStart && (uint8_t*)currentInstr >= sJitCodeRegionStart &&
437 (uint8_t*)currentInstr < sJitCodeRegionStart + sJitCodeRegionSize) {
438 break;
441 // We must also avoid msmpeg2vdec.dll's JIT region: they don't generate
442 // unwind data, so their JIT unwind callback just throws up its hands and
443 // terminates the process.
444 if (sMsMpegJitCodeRegionStart &&
445 (uint8_t*)currentInstr >= sMsMpegJitCodeRegionStart &&
446 (uint8_t*)currentInstr <
447 sMsMpegJitCodeRegionStart + sMsMpegJitCodeRegionSize) {
448 break;
451 // 64-bit frame unwinding.
452 // Try to look up unwind metadata for the current function.
453 ULONG64 imageBase;
454 PRUNTIME_FUNCTION runtimeFunction =
455 RtlLookupFunctionEntry(currentInstr, &imageBase, NULL);
457 if (runtimeFunction) {
458 PVOID dummyHandlerData;
459 ULONG64 dummyEstablisherFrame;
460 RtlVirtualUnwind(UNW_FLAG_NHANDLER, imageBase, currentInstr,
461 runtimeFunction, context.CONTEXTPtr(), &dummyHandlerData,
462 &dummyEstablisherFrame, nullptr);
463 } else if (firstFrame) {
464 // Leaf functions can be unwound by hand.
465 context.PC() = *reinterpret_cast<DWORD64*>(context.SP());
466 context.SP() += sizeof(void*);
467 } else {
468 // Something went wrong.
469 break;
472 addr = context.PC();
473 spaddr = context.SP();
474 firstFrame = false;
475 # else
476 # error "unknown platform"
477 # endif
479 if (addr == 0) {
480 break;
483 if (skipper.ShouldSkipPC((void*)addr)) {
484 continue;
487 aCallback(++frames, (void*)addr, (void*)spaddr, aClosure);
489 if (aMaxFrames != 0 && frames == aMaxFrames) {
490 break;
493 # if defined(_M_IX86)
494 if (frame64.AddrReturn.Offset == 0) {
495 break;
497 # endif
501 MFBT_API void MozStackWalkThread(MozWalkStackCallback aCallback,
502 uint32_t aMaxFrames, void* aClosure,
503 HANDLE aThread, CONTEXT* aContext) {
504 // We don't pass a aFirstFramePC because we walk the stack for another
505 // thread.
506 DoMozStackWalkThread(aCallback, nullptr, aMaxFrames, aClosure, aThread,
507 aContext);
510 MFBT_API void MozStackWalk(MozWalkStackCallback aCallback,
511 const void* aFirstFramePC, uint32_t aMaxFrames,
512 void* aClosure) {
513 DoMozStackWalkThread(aCallback, aFirstFramePC ? aFirstFramePC : CallerPC(),
514 aMaxFrames, aClosure, nullptr, nullptr);
517 static BOOL CALLBACK callbackEspecial64(PCSTR aModuleName, DWORD64 aModuleBase,
518 ULONG aModuleSize, PVOID aUserContext) {
519 BOOL retval = TRUE;
520 DWORD64 addr = *(DWORD64*)aUserContext;
523 * You'll want to control this if we are running on an
524 * architecture where the addresses go the other direction.
525 * Not sure this is even a realistic consideration.
527 const BOOL addressIncreases = TRUE;
530 * If it falls in side the known range, load the symbols.
532 if (addressIncreases
533 ? (addr >= aModuleBase && addr <= (aModuleBase + aModuleSize))
534 : (addr <= aModuleBase && addr >= (aModuleBase - aModuleSize))) {
535 retval = !!SymLoadModule64(GetCurrentProcess(), nullptr, (PSTR)aModuleName,
536 nullptr, aModuleBase, aModuleSize);
537 if (!retval) {
538 PrintError("SymLoadModule64");
542 return retval;
546 * SymGetModuleInfoEspecial
548 * Attempt to determine the module information.
549 * Bug 112196 says this DLL may not have been loaded at the time
550 * SymInitialize was called, and thus the module information
551 * and symbol information is not available.
552 * This code rectifies that problem.
555 // New members were added to IMAGEHLP_MODULE64 (that show up in the
556 // Platform SDK that ships with VC8, but not the Platform SDK that ships
557 // with VC7.1, i.e., between DbgHelp 6.0 and 6.1), but we don't need to
558 // use them, and it's useful to be able to function correctly with the
559 // older library. (Stock Windows XP SP2 seems to ship with dbghelp.dll
560 // version 5.1.) Since Platform SDK version need not correspond to
561 // compiler version, and the version number in debughlp.h was NOT bumped
562 // when these changes were made, ifdef based on a constant that was
563 // added between these versions.
564 # ifdef SSRVOPT_SETCONTEXT
565 # define NS_IMAGEHLP_MODULE64_SIZE \
566 (((offsetof(IMAGEHLP_MODULE64, LoadedPdbName) + sizeof(DWORD64) - 1) / \
567 sizeof(DWORD64)) * \
568 sizeof(DWORD64))
569 # else
570 # define NS_IMAGEHLP_MODULE64_SIZE sizeof(IMAGEHLP_MODULE64)
571 # endif
573 BOOL SymGetModuleInfoEspecial64(HANDLE aProcess, DWORD64 aAddr,
574 PIMAGEHLP_MODULE64 aModuleInfo,
575 PIMAGEHLP_LINE64 aLineInfo) {
576 BOOL retval = FALSE;
579 * Init the vars if we have em.
581 aModuleInfo->SizeOfStruct = NS_IMAGEHLP_MODULE64_SIZE;
582 if (aLineInfo) {
583 aLineInfo->SizeOfStruct = sizeof(IMAGEHLP_LINE64);
587 * Give it a go.
588 * It may already be loaded.
590 retval = SymGetModuleInfo64(aProcess, aAddr, aModuleInfo);
591 if (retval == FALSE) {
593 * Not loaded, here's the magic.
594 * Go through all the modules.
596 // Need to cast to PENUMLOADED_MODULES_CALLBACK64 because the
597 // constness of the first parameter of
598 // PENUMLOADED_MODULES_CALLBACK64 varies over SDK versions (from
599 // non-const to const over time). See bug 391848 and bug
600 // 415426.
601 BOOL enumRes = EnumerateLoadedModules64(
602 aProcess, (PENUMLOADED_MODULES_CALLBACK64)callbackEspecial64,
603 (PVOID)&aAddr);
604 if (enumRes != FALSE) {
606 * One final go.
607 * If it fails, then well, we have other problems.
609 retval = SymGetModuleInfo64(aProcess, aAddr, aModuleInfo);
614 * If we got module info, we may attempt line info as well.
615 * We will not report failure if this does not work.
617 if (retval != FALSE && aLineInfo) {
618 DWORD displacement = 0;
619 BOOL lineRes = FALSE;
620 lineRes = SymGetLineFromAddr64(aProcess, aAddr, &displacement, aLineInfo);
621 if (!lineRes) {
622 // Clear out aLineInfo to indicate that it's not valid
623 memset(aLineInfo, 0, sizeof(*aLineInfo));
627 return retval;
630 MFBT_API bool MozDescribeCodeAddress(void* aPC,
631 MozCodeAddressDetails* aDetails) {
632 aDetails->library[0] = '\0';
633 aDetails->loffset = 0;
634 aDetails->filename[0] = '\0';
635 aDetails->lineno = 0;
636 aDetails->function[0] = '\0';
637 aDetails->foffset = 0;
639 if (!InitializeDbgHelp(DbgHelpInitFlags::WithSymbolSupport)) {
640 return false;
643 HANDLE myProcess = ::GetCurrentProcess();
644 BOOL ok;
646 // debug routines are not threadsafe, so grab the lock.
647 EnterCriticalSection(&gDbgHelpCS);
650 // Attempt to load module info before we attempt to resolve the symbol.
651 // This just makes sure we get good info if available.
654 DWORD64 addr = (DWORD64)aPC;
655 IMAGEHLP_MODULE64 modInfo;
656 IMAGEHLP_LINE64 lineInfo;
657 BOOL modInfoRes;
658 modInfoRes = SymGetModuleInfoEspecial64(myProcess, addr, &modInfo, &lineInfo);
660 if (modInfoRes) {
661 strncpy(aDetails->library, modInfo.LoadedImageName,
662 sizeof(aDetails->library));
663 aDetails->library[mozilla::ArrayLength(aDetails->library) - 1] = '\0';
664 aDetails->loffset = (char*)aPC - (char*)modInfo.BaseOfImage;
666 if (lineInfo.FileName) {
667 strncpy(aDetails->filename, lineInfo.FileName,
668 sizeof(aDetails->filename));
669 aDetails->filename[mozilla::ArrayLength(aDetails->filename) - 1] = '\0';
670 aDetails->lineno = lineInfo.LineNumber;
674 ULONG64 buffer[(sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR) +
675 sizeof(ULONG64) - 1) /
676 sizeof(ULONG64)];
677 PSYMBOL_INFO pSymbol = (PSYMBOL_INFO)buffer;
678 pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO);
679 pSymbol->MaxNameLen = MAX_SYM_NAME;
681 DWORD64 displacement;
682 ok = SymFromAddr(myProcess, addr, &displacement, pSymbol);
684 if (ok) {
685 strncpy(aDetails->function, pSymbol->Name, sizeof(aDetails->function));
686 aDetails->function[mozilla::ArrayLength(aDetails->function) - 1] = '\0';
687 aDetails->foffset = static_cast<ptrdiff_t>(displacement);
690 LeaveCriticalSection(&gDbgHelpCS); // release our lock
691 return true;
694 // i386 or PPC Linux stackwalking code
696 // Changes to to OS/Architecture support here should be reflected in
697 // build/moz.configure/memory.configure
698 #elif HAVE_DLADDR && \
699 (HAVE__UNWIND_BACKTRACE || MOZ_STACKWALK_SUPPORTS_LINUX || \
700 MOZ_STACKWALK_SUPPORTS_MACOSX)
702 # include <stdlib.h>
703 # include <stdio.h>
705 // On glibc 2.1, the Dl_info api defined in <dlfcn.h> is only exposed
706 // if __USE_GNU is defined. I suppose its some kind of standards
707 // adherence thing.
709 # if (__GLIBC_MINOR__ >= 1) && !defined(__USE_GNU)
710 # define __USE_GNU
711 # endif
713 // This thing is exported by libstdc++
714 // Yes, this is a gcc only hack
715 # if defined(MOZ_DEMANGLE_SYMBOLS)
716 # include <cxxabi.h>
717 # endif // MOZ_DEMANGLE_SYMBOLS
719 namespace mozilla {
721 void DemangleSymbol(const char* aSymbol, char* aBuffer, int aBufLen) {
722 aBuffer[0] = '\0';
724 # if defined(MOZ_DEMANGLE_SYMBOLS)
725 /* See demangle.h in the gcc source for the voodoo */
726 char* demangled = abi::__cxa_demangle(aSymbol, 0, 0, 0);
728 if (demangled) {
729 strncpy(aBuffer, demangled, aBufLen);
730 aBuffer[aBufLen - 1] = '\0';
731 free(demangled);
733 # endif // MOZ_DEMANGLE_SYMBOLS
736 } // namespace mozilla
738 // {x86, ppc} x {Linux, Mac} stackwalking code.
740 // Changes to to OS/Architecture support here should be reflected in
741 // build/moz.configure/memory.configure
742 # if ((defined(__i386) || defined(PPC) || defined(__ppc__)) && \
743 (MOZ_STACKWALK_SUPPORTS_MACOSX || MOZ_STACKWALK_SUPPORTS_LINUX))
745 static void DoFramePointerStackWalk(MozWalkStackCallback aCallback,
746 const void* aFirstFramePC,
747 uint32_t aMaxFrames, void* aClosure,
748 void** aBp, void* aStackEnd);
750 MFBT_API void MozStackWalk(MozWalkStackCallback aCallback,
751 const void* aFirstFramePC, uint32_t aMaxFrames,
752 void* aClosure) {
753 // Get the frame pointer
754 void** bp = (void**)__builtin_frame_address(0);
756 void* stackEnd;
757 # if HAVE___LIBC_STACK_END
758 stackEnd = __libc_stack_end;
759 # elif defined(XP_DARWIN)
760 stackEnd = pthread_get_stackaddr_np(pthread_self());
761 # elif defined(ANDROID)
762 pthread_attr_t sattr;
763 pthread_attr_init(&sattr);
764 pthread_getattr_np(pthread_self(), &sattr);
765 void* stackBase = stackEnd = nullptr;
766 size_t stackSize = 0;
767 if (gettid() != getpid()) {
768 // bionic's pthread_attr_getstack doesn't tell the truth for the main
769 // thread (see bug 846670). So don't use it for the main thread.
770 if (!pthread_attr_getstack(&sattr, &stackBase, &stackSize)) {
771 stackEnd = static_cast<char*>(stackBase) + stackSize;
772 } else {
773 stackEnd = nullptr;
776 if (!stackEnd) {
777 // So consider the current frame pointer + an arbitrary size of 8MB
778 // (modulo overflow ; not really arbitrary as it's the default stack
779 // size for the main thread) if pthread_attr_getstack failed for
780 // some reason (or was skipped).
781 static const uintptr_t kMaxStackSize = 8 * 1024 * 1024;
782 uintptr_t maxStackStart = uintptr_t(-1) - kMaxStackSize;
783 uintptr_t stackStart = std::max(maxStackStart, uintptr_t(bp));
784 stackEnd = reinterpret_cast<void*>(stackStart + kMaxStackSize);
786 # else
787 # error Unsupported configuration
788 # endif
789 DoFramePointerStackWalk(aCallback, aFirstFramePC, aMaxFrames, aClosure, bp,
790 stackEnd);
793 # elif defined(HAVE__UNWIND_BACKTRACE)
795 // libgcc_s.so symbols _Unwind_Backtrace@@GCC_3.3 and _Unwind_GetIP@@GCC_3.0
796 # include <unwind.h>
798 struct unwind_info {
799 MozWalkStackCallback callback;
800 FrameSkipper skipper;
801 int maxFrames;
802 int numFrames;
803 void* closure;
806 static _Unwind_Reason_Code unwind_callback(struct _Unwind_Context* context,
807 void* closure) {
808 unwind_info* info = static_cast<unwind_info*>(closure);
809 void* pc = reinterpret_cast<void*>(_Unwind_GetIP(context));
810 // TODO Use something like '_Unwind_GetGR()' to get the stack pointer.
811 if (!info->skipper.ShouldSkipPC(pc)) {
812 info->numFrames++;
813 (*info->callback)(info->numFrames, pc, nullptr, info->closure);
814 if (info->maxFrames != 0 && info->numFrames == info->maxFrames) {
815 // Again, any error code that stops the walk will do.
816 return _URC_FOREIGN_EXCEPTION_CAUGHT;
819 return _URC_NO_REASON;
822 MFBT_API void MozStackWalk(MozWalkStackCallback aCallback,
823 const void* aFirstFramePC, uint32_t aMaxFrames,
824 void* aClosure) {
825 unwind_info info;
826 info.callback = aCallback;
827 info.skipper = FrameSkipper(aFirstFramePC ? aFirstFramePC : CallerPC());
828 info.maxFrames = aMaxFrames;
829 info.numFrames = 0;
830 info.closure = aClosure;
832 // We ignore the return value from _Unwind_Backtrace. There are three main
833 // reasons for this.
834 // - On ARM/Android bionic's _Unwind_Backtrace usually (always?) returns
835 // _URC_FAILURE. See
836 // https://bugzilla.mozilla.org/show_bug.cgi?id=717853#c110.
837 // - If aMaxFrames != 0, we want to stop early, and the only way to do that
838 // is to make unwind_callback return something other than _URC_NO_REASON,
839 // which causes _Unwind_Backtrace to return a non-success code.
840 // - MozStackWalk doesn't have a return value anyway.
841 (void)_Unwind_Backtrace(unwind_callback, &info);
844 # endif
846 bool MFBT_API MozDescribeCodeAddress(void* aPC,
847 MozCodeAddressDetails* aDetails) {
848 aDetails->library[0] = '\0';
849 aDetails->loffset = 0;
850 aDetails->filename[0] = '\0';
851 aDetails->lineno = 0;
852 aDetails->function[0] = '\0';
853 aDetails->foffset = 0;
855 Dl_info info;
857 # if defined(ANDROID) && defined(MOZ_LINKER)
858 int ok = __wrap_dladdr(aPC, &info);
859 # else
860 int ok = dladdr(aPC, &info);
861 # endif
863 if (!ok) {
864 return true;
867 strncpy(aDetails->library, info.dli_fname, sizeof(aDetails->library));
868 aDetails->library[mozilla::ArrayLength(aDetails->library) - 1] = '\0';
869 aDetails->loffset = (char*)aPC - (char*)info.dli_fbase;
871 # if !defined(XP_FREEBSD)
872 // On FreeBSD, dli_sname is unusably bad, it often returns things like
873 // 'gtk_xtbin_new' or 'XRE_GetBootstrap' instead of long C++ symbols. Just let
874 // GetFunction do the lookup directly in the ELF image.
876 const char* symbol = info.dli_sname;
877 if (!symbol || symbol[0] == '\0') {
878 return true;
881 DemangleSymbol(symbol, aDetails->function, sizeof(aDetails->function));
883 if (aDetails->function[0] == '\0') {
884 // Just use the mangled symbol if demangling failed.
885 strncpy(aDetails->function, symbol, sizeof(aDetails->function));
886 aDetails->function[mozilla::ArrayLength(aDetails->function) - 1] = '\0';
889 aDetails->foffset = (char*)aPC - (char*)info.dli_saddr;
890 # endif
892 return true;
895 #else // unsupported platform.
897 MFBT_API void MozStackWalk(MozWalkStackCallback aCallback,
898 const void* aFirstFramePC, uint32_t aMaxFrames,
899 void* aClosure) {}
901 MFBT_API bool MozDescribeCodeAddress(void* aPC,
902 MozCodeAddressDetails* aDetails) {
903 aDetails->library[0] = '\0';
904 aDetails->loffset = 0;
905 aDetails->filename[0] = '\0';
906 aDetails->lineno = 0;
907 aDetails->function[0] = '\0';
908 aDetails->foffset = 0;
909 return false;
912 #endif
914 #if defined(XP_WIN) || defined(XP_MACOSX) || defined(XP_LINUX)
916 # if defined(XP_MACOSX) && defined(__aarch64__)
917 // On macOS arm64, system libraries are arm64e binaries, and arm64e can do
918 // pointer authentication: The low bits of the pointer are the actual pointer
919 // value, and the high bits are an encrypted hash. During stackwalking, we need
920 // to strip off this hash. In theory, ptrauth_strip would be the right function
921 // to call for this. However, that function is a no-op unless it's called from
922 // code which also builds as arm64e - which we do not. So we cannot use it. So
923 // for now, we hardcode a mask that seems to work today: 40 bits for the pointer
924 // and 24 bits for the hash seems to do the trick. We can worry about
925 // dynamically computing the correct mask if this ever stops working.
926 const uintptr_t kPointerMask =
927 (uintptr_t(1) << 40) - 1; // 40 bits pointer, 24 bit PAC
928 # else
929 const uintptr_t kPointerMask = ~uintptr_t(0);
930 # endif
932 MOZ_ASAN_IGNORE
933 static void DoFramePointerStackWalk(MozWalkStackCallback aCallback,
934 const void* aFirstFramePC,
935 uint32_t aMaxFrames, void* aClosure,
936 void** aBp, void* aStackEnd) {
937 // Stack walking code courtesy Kipp's "leaky".
939 FrameSkipper skipper(aFirstFramePC);
940 uint32_t numFrames = 0;
942 // Sanitize the given aBp. Assume that something reasonably close to
943 // but before the stack end is going be a valid frame pointer. Also
944 // check that it is an aligned address. This increases the chances
945 // that if the pointer is not valid (which might happen if the caller
946 // called __builtin_frame_address(1) and its frame is busted for some
947 // reason), we won't read it, leading to a crash. Because the calling
948 // code is not using frame pointers when returning, it might actually
949 // recover just fine.
950 static const uintptr_t kMaxStackSize = 8 * 1024 * 1024;
951 if (uintptr_t(aBp) < uintptr_t(aStackEnd) -
952 std::min(kMaxStackSize, uintptr_t(aStackEnd)) ||
953 aBp >= aStackEnd || (uintptr_t(aBp) & 3)) {
954 return;
957 while (aBp) {
958 void** next = (void**)*aBp;
959 // aBp may not be a frame pointer on i386 if code was compiled with
960 // -fomit-frame-pointer, so do some sanity checks.
961 // (aBp should be a frame pointer on ppc(64) but checking anyway may help
962 // a little if the stack has been corrupted.)
963 // We don't need to check against the begining of the stack because
964 // we can assume that aBp > sp
965 if (next <= aBp || next >= aStackEnd || (uintptr_t(next) & 3)) {
966 break;
968 # if (defined(__ppc__) && defined(XP_MACOSX)) || defined(__powerpc64__)
969 // ppc mac or powerpc64 linux
970 void* pc = *(aBp + 2);
971 aBp += 3;
972 # else // i386 or powerpc32 linux
973 void* pc = *(aBp + 1);
974 aBp += 2;
975 # endif
977 // Strip off pointer authentication hash, if present. For now, it looks
978 // like only return addresses require stripping, and stack pointers do
979 // not. This might change in the future.
980 pc = (void*)((uintptr_t)pc & kPointerMask);
982 if (!skipper.ShouldSkipPC(pc)) {
983 // Assume that the SP points to the BP of the function
984 // it called. We can't know the exact location of the SP
985 // but this should be sufficient for our use the SP
986 // to order elements on the stack.
987 numFrames++;
988 (*aCallback)(numFrames, pc, aBp, aClosure);
989 if (aMaxFrames != 0 && numFrames == aMaxFrames) {
990 break;
993 aBp = next;
997 namespace mozilla {
999 MFBT_API void FramePointerStackWalk(MozWalkStackCallback aCallback,
1000 uint32_t aMaxFrames, void* aClosure,
1001 void** aBp, void* aStackEnd) {
1002 // We don't pass a aFirstFramePC because we start walking the stack from the
1003 // frame at aBp.
1004 DoFramePointerStackWalk(aCallback, nullptr, aMaxFrames, aClosure, aBp,
1005 aStackEnd);
1008 } // namespace mozilla
1010 #else
1012 namespace mozilla {
1013 MFBT_API void FramePointerStackWalk(MozWalkStackCallback aCallback,
1014 uint32_t aMaxFrames, void* aClosure,
1015 void** aBp, void* aStackEnd) {}
1016 } // namespace mozilla
1018 #endif
1020 MFBT_API int MozFormatCodeAddressDetails(
1021 char* aBuffer, uint32_t aBufferSize, uint32_t aFrameNumber, void* aPC,
1022 const MozCodeAddressDetails* aDetails) {
1023 return MozFormatCodeAddress(aBuffer, aBufferSize, aFrameNumber, aPC,
1024 aDetails->function, aDetails->library,
1025 aDetails->loffset, aDetails->filename,
1026 aDetails->lineno);
1029 MFBT_API int MozFormatCodeAddress(char* aBuffer, uint32_t aBufferSize,
1030 uint32_t aFrameNumber, const void* aPC,
1031 const char* aFunction, const char* aLibrary,
1032 ptrdiff_t aLOffset, const char* aFileName,
1033 uint32_t aLineNo) {
1034 const char* function = aFunction && aFunction[0] ? aFunction : "???";
1035 if (aFileName && aFileName[0]) {
1036 // We have a filename and (presumably) a line number. Use them.
1037 return SprintfBuf(aBuffer, aBufferSize, "#%02u: %s (%s:%u)", aFrameNumber,
1038 function, aFileName, aLineNo);
1039 } else if (aLibrary && aLibrary[0]) {
1040 // We have no filename, but we do have a library name. Use it and the
1041 // library offset, and print them in a way that `fix_stacks.py` can
1042 // post-process.
1043 return SprintfBuf(aBuffer, aBufferSize, "#%02u: %s[%s +0x%" PRIxPTR "]",
1044 aFrameNumber, function, aLibrary,
1045 static_cast<uintptr_t>(aLOffset));
1046 } else {
1047 // We have nothing useful to go on. (The format string is split because
1048 // '??)' is a trigraph and causes a warning, sigh.)
1049 return SprintfBuf(aBuffer, aBufferSize,
1050 "#%02u: ??? (???:???"
1051 ")",
1052 aFrameNumber);
1056 static void EnsureWrite(FILE* aStream, const char* aBuf, size_t aLen) {
1057 #ifdef XP_WIN
1058 int fd = _fileno(aStream);
1059 #else
1060 int fd = fileno(aStream);
1061 #endif
1062 while (aLen > 0) {
1063 #ifdef XP_WIN
1064 auto written = _write(fd, aBuf, aLen);
1065 #else
1066 auto written = write(fd, aBuf, aLen);
1067 #endif
1068 if (written <= 0 || size_t(written) > aLen) {
1069 break;
1071 aBuf += written;
1072 aLen -= written;
1076 template <int N>
1077 static int PrintStackFrameBuf(char (&aBuf)[N], uint32_t aFrameNumber, void* aPC,
1078 void* aSP) {
1079 MozCodeAddressDetails details;
1080 MozDescribeCodeAddress(aPC, &details);
1081 int len =
1082 MozFormatCodeAddressDetails(aBuf, N - 1, aFrameNumber, aPC, &details);
1083 len = std::min(len, N - 2);
1084 aBuf[len++] = '\n';
1085 aBuf[len] = '\0';
1086 return len;
1089 static void PrintStackFrame(uint32_t aFrameNumber, void* aPC, void* aSP,
1090 void* aClosure) {
1091 FILE* stream = (FILE*)aClosure;
1092 char buf[1025]; // 1024 + 1 for trailing '\n'
1093 int len = PrintStackFrameBuf(buf, aFrameNumber, aPC, aSP);
1094 fflush(stream);
1095 EnsureWrite(stream, buf, len);
1098 static bool WalkTheStackEnabled() {
1099 static bool result = [] {
1100 char* value = getenv("MOZ_DISABLE_WALKTHESTACK");
1101 return !(value && value[0]);
1102 }();
1103 return result;
1106 MFBT_API void MozWalkTheStack(FILE* aStream, const void* aFirstFramePC,
1107 uint32_t aMaxFrames) {
1108 if (WalkTheStackEnabled()) {
1109 MozStackWalk(PrintStackFrame, aFirstFramePC ? aFirstFramePC : CallerPC(),
1110 aMaxFrames, aStream);
1114 static void WriteStackFrame(uint32_t aFrameNumber, void* aPC, void* aSP,
1115 void* aClosure) {
1116 auto writer = (void (*)(const char*))aClosure;
1117 char buf[1024];
1118 PrintStackFrameBuf(buf, aFrameNumber, aPC, aSP);
1119 writer(buf);
1122 MFBT_API void MozWalkTheStackWithWriter(void (*aWriter)(const char*),
1123 const void* aFirstFramePC,
1124 uint32_t aMaxFrames) {
1125 if (WalkTheStackEnabled()) {
1126 MozStackWalk(WriteStackFrame, aFirstFramePC ? aFirstFramePC : CallerPC(),
1127 aMaxFrames, (void*)aWriter);