/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* API for getting a stack trace of the C/C++ stack on the current thread */
#include"mozilla/Array.h" #include"mozilla/ArrayUtils.h" #include"mozilla/Atomics.h" #include"mozilla/Attributes.h" #include"mozilla/StackWalk.h" #ifdef XP_WIN # include "mozilla/StackWalkThread.h" # include <io.h> #else # include <unistd.h> #endif #include"mozilla/Sprintf.h"
#include <string.h>
#ifdefined(ANDROID) && defined(MOZ_LINKER) # include "Linker.h" # include <android/log.h> #endif
usingnamespace mozilla;
// for _Unwind_Backtrace from libcxxrt or libunwind // cxxabi.h from libcxxrt implicitly includes unwind.h first #ifdefined(HAVE__UNWIND_BACKTRACE) && !defined(_GNU_SOURCE) # define _GNU_SOURCE #endif
#ifdefined(HAVE_DLFCN_H) || defined(XP_DARWIN) # include <dlfcn.h> #endif
#ifdef ANDROID # include <algorithm> # include <unistd.h> # include <pthread.h> #endif
class FrameSkipper { public:
constexpr FrameSkipper() : mSkipUntilAddr(0) {} static uintptr_t AddressFromPC(constvoid* aPC) { #ifdef __arm__ // On 32-bit ARM, mask off the thumb bit to get the instruction address. return uintptr_t(aPC) & ~1; #else return uintptr_t(aPC); #endif
} bool ShouldSkipPC(void* aPC) { // Skip frames until we encounter the one we were initialized with, // and then never skip again.
uintptr_t instructionAddress = AddressFromPC(aPC); if (mSkipUntilAddr != 0) { if (mSkipUntilAddr != instructionAddress) { returntrue;
}
mSkipUntilAddr = 0;
} returnfalse;
} explicit FrameSkipper(constvoid* aPC) : mSkipUntilAddr(AddressFromPC(aPC)) {}
private:
uintptr_t mSkipUntilAddr;
};
#ifdef XP_WIN
# include <windows.h> # include <process.h> # include <stdio.h> # include <malloc.h> # include "mozilla/ArrayUtils.h" # include "mozilla/Atomics.h" # include "mozilla/StackWalk_windows.h" # include "mozilla/WindowsVersion.h"
# include <imagehlp.h> // We need a way to know if we are building for WXP (or later), as if we are, we // need to use the newer 64-bit APIs. API_VERSION_NUMBER seems to fit the bill. // A value of 9 indicates we want to use the new APIs. # if API_VERSION_NUMBER < 9 # error Too old imagehlp.h # endif
# ifdefined(_M_AMD64) || defined(_M_ARM64) // We must use RtlLookupFunctionEntry to do stack walking on x86-64 and arm64, // but internally this function does a blocking shared acquire of SRW locks // that live in ntdll and are not exported. This is problematic when we want to // suspend a thread and walk its stack, like we do in the profiler and the // background hang reporter. If the suspended thread happens to hold one of the // locks exclusively while suspended, then the stack walking thread will // deadlock if it calls RtlLookupFunctionEntry. // // Note that we only care about deadlocks between the stack walking thread and // the suspended thread. Any other deadlock scenario is considered out of // scope, because they are unlikely to be our fault -- these other scenarios // imply that some thread that we did not suspend is stuck holding one of the // locks exclusively, and exclusive acquisition of these locks only happens for // a brief time during Microsoft API calls (e.g. LdrLoadDll, LdrUnloadDll). // // We use one of two alternative strategies to gracefully fail to capture a // stack instead of running into a deadlock: // (1) collect pointers to the ntdll internal locks at stack walk // initialization, then try to acquire them non-blockingly before // initiating any stack walk; // or (2) mark all code paths that can potentially end up doing an exclusive // acquisition of the locks as stack walk suppression paths, then check // if any thread is currently on a stack walk suppression path before // initiating any stack walk; // // Strategy (2) can only avoid all deadlocks under the easily wronged // assumption that we have correctly identified all existing paths that should // be stack suppression paths. With strategy (2) we cannot collect stacks e.g. // during the whole duration of a DLL load happening on any thread so the // profiling results are worse. // // Strategy (1) guarantees no deadlock. It also gives better profiling results // because it is more fine-grained. Therefore we always prefer strategy (1), // and we only use strategy (2) as a fallback.
// Strategy (1): Ntdll Internal Locks // // The external stack walk initialization code will feed us pointers to the // ntdll internal locks. Once we have them, we no longer need to rely on // strategy (2). static Atomic<bool> sStackWalkLocksInitialized; static Array<SRWLOCK*, 2> sStackWalkLocks;
// Strategy (2): Stack Walk Suppressions // // We're using an atomic counter rather than a critical section because we // don't require mutual exclusion with the stack walker. If the stack walker // determines that it's safe to start unwinding the suspended thread (i.e. // there are no suppressions when the unwind begins), then it's safe to // continue unwinding that thread even if other threads request suppressions // in the meantime, because we can't deadlock with those other threads. // // XXX: This global variable is a larger-than-necessary hammer. A more scoped // solution would be to maintain a counter per thread, but then it would be // more difficult for WalkStackMain64 to read the suspended thread's counter. static Atomic<size_t> sStackWalkSuppressions;
void DesuppressStackWalking() { auto previousValue = sStackWalkSuppressions--; // We should never desuppress from 0. See bug 1687510 comment 10 for an // example in which this occured.
MOZ_RELEASE_ASSERT(previousValue);
}
MFBT_API void RegisterJitCodeRegion(uint8_t* aStart, size_t aSize) { // Currently we can only handle one JIT code region at a time
MOZ_RELEASE_ASSERT(!sJitCodeRegionStart);
MFBT_API void UnregisterJitCodeRegion(uint8_t* aStart, size_t aSize) { // Currently we can only handle one JIT code region at a time
MOZ_RELEASE_ASSERT(sJitCodeRegionStart && sJitCodeRegionStart == aStart &&
sJitCodeRegionSize == aSize);
// A thread-safe safe object interface for Microsoft's DbgHelp.dll. DbgHelp // APIs are not thread-safe and they require the use of a unique HANDLE value // as an identifier for the current session. All this is handled internally by // DbgHelpWrapper. class DbgHelpWrapper { public: explicitinline DbgHelpWrapper() : DbgHelpWrapper(InitFlag::BasicInit) {}
DbgHelpWrapper(DbgHelpWrapper&& other) = delete;
DbgHelpWrapper operator=(DbgHelpWrapper&& other) = delete;
DbgHelpWrapper(const DbgHelpWrapper&) = delete;
DbgHelpWrapper operator=(const DbgHelpWrapper&) = delete;
// DbgHelp functions are not thread-safe and should therefore be protected // by using this critical section through a AutoCriticalSection. static CRITICAL_SECTION sCriticalSection;
// DbgHelp functions require a unique HANDLE hProcess that should be the same // throughout the current session. We refer to this handle as a session id. // Ideally the session id should be a valid HANDLE to the target process, // which in our case is the current process. // // However, in order to avoid conflicts with other sessions, the session id // should be unique and therefore not just GetCurrentProcess(), which other // pieces of code tend to already use (see bug 1699328). // // We therefore define sSessionId as a duplicate of the current process // handle, a solution that meets all the requirements listed above. static HANDLE sSessionId;
private: bool mInitSuccess;
// This function initializes sCriticalSection, sSessionId and loads DbgHelp. // It also calls SymInitialize if called with aInitFlag::WithSymbolSupport. // It is thread-safe and reentrancy-safe.
[[nodiscard]] staticbool Initialize(InitFlag aInitFlag);
// In debug and fuzzing builds, MOZ_ASSERT and MOZ_CRASH walk the stack to // print it before actually crashing. This code path uses a DbgHelpWrapper // object, hence *any* MOZ_ASSERT or MOZ_CRASH failure reached from // Initialize() leads to rentrancy (see bug 1869997 for an example). Such // failures can occur indirectly when we load dbghelp.dll, because we // override various Microsoft-internal functions that are called upon DLL // loading. We protect against reentrancy by keeping track of the ID of the // thread that runs the initialization code. static Atomic<DWORD> sInitializationThreadId;
};
// Thread-safety here is ensured by the C++ standard: scoped static // initialization is thread-safe. sInitializationThreadId is used to protect // against reentrancy -- and only for that purpose.
[[nodiscard]] /* static */ bool DbgHelpWrapper::Initialize(
DbgHelpWrapper::InitFlag aInitFlag) { // In the code below, it is only safe to reach MOZ_ASSERT or MOZ_CRASH while // sInitializationThreadId is set to the current thread id. static Atomic<DWORD> sInitializationThreadId{0};
DWORD currentThreadId = ::GetCurrentThreadId();
// This code relies on Windows never giving us a current thread ID of zero. // We make this assumption explicit, by failing if that should ever occur. if (!currentThreadId) { returnfalse;
}
if (sInitializationThreadId == currentThreadId) { // This is a reentrant call and we must abort here. returnfalse;
}
staticconstbool sHasInitializedDbgHelp = [currentThreadId]() { // Per the C++ standard, only one thread evers reaches this path.
sInitializationThreadId = currentThreadId;
// If we don't need symbol initialization, we are done. If we need it, we // can only proceed if DbgHelp initialization was successful. if (aInitFlag == InitFlag::BasicInit || !sHasInitializedDbgHelp) { return sHasInitializedDbgHelp;
}
staticconstbool sHasInitializedSymbols = [currentThreadId]() { // Per the C++ standard, only one thread evers reaches this path.
sInitializationThreadId = currentThreadId;
bool symbolsInitialized = false;
{
AutoCriticalSection guard(&sCriticalSection);
::SymSetOptions(SYMOPT_LOAD_LINES | SYMOPT_UNDNAME);
symbolsInitialized = static_cast<bool>(::SymInitializeW(sSessionId, nullptr, TRUE)); /* XXX At some point we need to arrange to call SymCleanup */
}
if (!symbolsInitialized) {
PrintError("SymInitialize");
}
// Some APIs such as SymFromAddr also require that the session id has gone // through SymInitialize. This is handled by child class DbgHelpWrapperSym. class DbgHelpWrapperSym : public DbgHelpWrapper { public: explicit DbgHelpWrapperSym() : DbgHelpWrapper(InitFlag::WithSymbolSupport) {}
// Wrapper around a reference to a CONTEXT, to simplify access to main // platform-specific execution registers. // It also avoids using CONTEXT* nullable pointers. class CONTEXTGenericAccessors { public: explicit CONTEXTGenericAccessors(CONTEXT& aCONTEXT) : mCONTEXT(aCONTEXT) {}
/** * Walk the stack, translating PC's found into strings and recording the * chain in aBuffer. For this to work properly, the DLLs must be rebased * so that the address in the file agrees with the address in memory. * Otherwise StackWalk will return FALSE when it hits a frame in a DLL * whose in memory address doesn't match its in-file address.
*/
# ifdefined(_M_AMD64) || defined(_M_ARM64) // If at least one thread (we don't know which) may be holding a lock that // can deadlock RtlLookupFunctionEntry, we can't proceed because that thread // may be the one that we're trying to walk the stack of. // // But if there is no such thread by this point, then our target thread can't // be holding a lock, so it's safe to proceed. By virtue of being suspended, // the target thread can't acquire any new locks during our stack walking, so // we only need to do this check once. Other threads may temporarily acquire // the locks while we're walking the stack, but that's mostly fine -- calling // RtlLookupFunctionEntry will make us wait for them to release the locks, // but at least we won't deadlock. if (!IsStackWalkingSafe()) { return;
}
bool firstFrame = true; # endif
FrameSkipper skipper(aFirstFramePC);
uint32_t frames = 0;
// Now walk the stack. while (true) {
DWORD64 addr;
DWORD64 spaddr;
# ifdefined(_M_IX86) // 32-bit frame unwinding. BOOL ok = dbgHelp.StackWalk64(
IMAGE_FILE_MACHINE_I386, targetThread, &frame64, context.CONTEXTPtr(),
nullptr,
::SymFunctionTableAccess64, // function table access routine
::SymGetModuleBase64, // module base routine
0);
// If we reach a frame in JIT code, we don't have enough information to // unwind, so we have to give up. if (sJitCodeRegionStart && (uint8_t*)currentInstr >= sJitCodeRegionStart &&
(uint8_t*)currentInstr < sJitCodeRegionStart + sJitCodeRegionSize) { break;
}
// We must also avoid msmpeg2vdec.dll's JIT region: they don't generate // unwind data, so their JIT unwind callback just throws up its hands and // terminates the process. if (sMsMpegJitCodeRegionStart &&
(uint8_t*)currentInstr >= sMsMpegJitCodeRegionStart &&
(uint8_t*)currentInstr <
sMsMpegJitCodeRegionStart + sMsMpegJitCodeRegionSize) { break;
}
// 64-bit frame unwinding. // Try to look up unwind metadata for the current function.
ULONG64 imageBase;
PRUNTIME_FUNCTION runtimeFunction =
RtlLookupFunctionEntry(currentInstr, &imageBase, NULL);
if (runtimeFunction) {
PVOID dummyHandlerData;
ULONG64 dummyEstablisherFrame;
RtlVirtualUnwind(UNW_FLAG_NHANDLER, imageBase, currentInstr,
runtimeFunction, context.CONTEXTPtr(), &dummyHandlerData,
&dummyEstablisherFrame, nullptr);
} elseif (firstFrame) { // Leaf functions can be unwound by hand.
context.PC() = *reinterpret_cast<DWORD64*>(context.SP());
context.SP() += sizeof(void*);
} else { // Something went wrong. break;
}
MFBT_API void MozStackWalkThread(MozWalkStackCallback aCallback,
uint32_t aMaxFrames, void* aClosure,
HANDLE aThread, CONTEXT* aContext) { // We don't pass a aFirstFramePC because we walk the stack for another // thread.
DoMozStackWalkThread(aCallback, nullptr, aMaxFrames, aClosure, aThread,
aContext);
}
/* * You'll want to control this if we are running on an * architecture where the addresses go the other direction. * Not sure this is even a realistic consideration.
*/ constBOOL addressIncreases = TRUE;
/* * If it falls in side the known range, load the symbols.
*/ if (addressIncreases
? (addr >= aModuleBase && addr <= (aModuleBase + aModuleSize))
: (addr <= aModuleBase && addr >= (aModuleBase - aModuleSize))) {
retval = !!::SymLoadModule64(context->mSessionId, nullptr, aModuleName,
nullptr, aModuleBase, aModuleSize); if (!retval) {
PrintError("SymLoadModule64");
}
}
return retval;
}
/* * SymGetModuleInfoEspecial * * Attempt to determine the module information. * Bug 112196 says this DLL may not have been loaded at the time * SymInitialize was called, and thus the module information * and symbol information is not available. * This code rectifies that problem.
*/
// New members were added to IMAGEHLP_MODULE64 (that show up in the // Platform SDK that ships with VC8, but not the Platform SDK that ships // with VC7.1, i.e., between DbgHelp 6.0 and 6.1), but we don't need to // use them, and it's useful to be able to function correctly with the // older library. (Stock Windows XP SP2 seems to ship with dbghelp.dll // version 5.1.) Since Platform SDK version need not correspond to // compiler version, and the version number in debughlp.h was NOT bumped // when these changes were made, ifdef based on a constant that was // added between these versions. # ifdef SSRVOPT_SETCONTEXT # define NS_IMAGEHLP_MODULE64_SIZE \
(((offsetof(IMAGEHLP_MODULE64, LoadedPdbName) + sizeof(DWORD64) - 1) / \ sizeof(DWORD64)) * \ sizeof(DWORD64)) # else # define NS_IMAGEHLP_MODULE64_SIZE sizeof(IMAGEHLP_MODULE64) # endif
/* * Init the vars if we have em.
*/
aModuleInfo->SizeOfStruct = NS_IMAGEHLP_MODULE64_SIZE; if (aLineInfo) {
aLineInfo->SizeOfStruct = sizeof(IMAGEHLP_LINE64);
}
/* * Give it a go. * It may already be loaded.
*/
retval = ::SymGetModuleInfo64(sSessionId, aAddr, aModuleInfo); if (retval == FALSE) { /* * Not loaded, here's the magic. * Go through all the modules.
*/
CallbackEspecial64UserContext context{
.mSessionId = sSessionId,
.mAddr = aAddr,
}; BOOL enumRes = ::EnumerateLoadedModules64(
sSessionId, CallbackEspecial64, reinterpret_cast<PVOID>(&context)); if (enumRes != FALSE) { /* * One final go. * If it fails, then well, we have other problems.
*/
retval = ::SymGetModuleInfo64(sSessionId, aAddr, aModuleInfo);
}
}
/* * If we got module info, we may attempt line info as well. * We will not report failure if this does not work.
*/ if (retval != FALSE && aLineInfo) {
DWORD displacement = 0; BOOL lineRes = FALSE;
lineRes =
::SymGetLineFromAddr64(sSessionId, aAddr, &displacement, aLineInfo); if (!lineRes) { // Clear out aLineInfo to indicate that it's not valid
memset(aLineInfo, 0, sizeof(*aLineInfo));
}
}
DbgHelpWrapperSym dbgHelp; if (!dbgHelp.ReadyToUse()) { returnfalse;
}
// Attempt to load module info before we attempt to resolve the symbol. // This just makes sure we get good info if available.
DWORD64 addr = (DWORD64)aPC;
IMAGEHLP_MODULE64 modInfo;
IMAGEHLP_LINE64 lineInfo; BOOL modInfoRes;
modInfoRes = dbgHelp.SymGetModuleInfoEspecial64(addr, &modInfo, &lineInfo);
// i386 or PPC Linux stackwalking code // // Changes to to OS/Architecture support here should be reflected in // build/moz.configure/memory.configure #elif HAVE_DLADDR && \
(HAVE__UNWIND_BACKTRACE || MOZ_STACKWALK_SUPPORTS_LINUX || \
MOZ_STACKWALK_SUPPORTS_MACOSX)
# include <stdlib.h> # include <stdio.h>
// On glibc 2.1, the Dl_info api defined in <dlfcn.h> is only exposed // if __USE_GNU is defined. I suppose its some kind of standards // adherence thing. // # if (__GLIBC_MINOR__ >= 1) && !defined(__USE_GNU) # define __USE_GNU # endif
// This thing is exported by libstdc++ // Yes, this is a gcc only hack # ifdefined(MOZ_DEMANGLE_SYMBOLS) # include <cxxabi.h> # endif // MOZ_DEMANGLE_SYMBOLS
// {x86, ppc} x {Linux, Mac} stackwalking code. // // Changes to to OS/Architecture support here should be reflected in // build/moz.configure/memory.configure # if ((defined(__i386) || defined(PPC) || defined(__ppc__)) && \
(MOZ_STACKWALK_SUPPORTS_MACOSX || MOZ_STACKWALK_SUPPORTS_LINUX))
MFBT_API void MozStackWalk(MozWalkStackCallback aCallback, constvoid* aFirstFramePC, uint32_t aMaxFrames, void* aClosure) { // Get the frame pointer void** bp = (void**)__builtin_frame_address(0);
void* stackEnd; # if HAVE___LIBC_STACK_END
stackEnd = __libc_stack_end; # elif defined(XP_DARWIN)
stackEnd = pthread_get_stackaddr_np(pthread_self()); # elif defined(ANDROID)
pthread_attr_t sattr;
pthread_attr_init(&sattr);
pthread_getattr_np(pthread_self(), &sattr); void* stackBase = stackEnd = nullptr;
size_t stackSize = 0; if (gettid() != getpid()) { // bionic's pthread_attr_getstack doesn't tell the truth for the main // thread (see bug 846670). So don't use it for the main thread. if (!pthread_attr_getstack(&sattr, &stackBase, &stackSize)) {
stackEnd = static_cast<char*>(stackBase) + stackSize;
} else {
stackEnd = nullptr;
}
} if (!stackEnd) { // So consider the current frame pointer + an arbitrary size of 8MB // (modulo overflow ; not really arbitrary as it's the default stack // size for the main thread) if pthread_attr_getstack failed for // some reason (or was skipped). staticconst uintptr_t kMaxStackSize = 8 * 1024 * 1024;
uintptr_t maxStackStart = uintptr_t(-1) - kMaxStackSize;
uintptr_t stackStart = std::max(maxStackStart, uintptr_t(bp));
stackEnd = reinterpret_cast<void*>(stackStart + kMaxStackSize);
} # else # error Unsupported configuration # endif
DoFramePointerStackWalk(aCallback, aFirstFramePC, aMaxFrames, aClosure, bp,
stackEnd);
}
# elif defined(HAVE__UNWIND_BACKTRACE)
// libgcc_s.so symbols _Unwind_Backtrace@@GCC_3.3 and _Unwind_GetIP@@GCC_3.0 # include <unwind.h>
struct unwind_info {
MozWalkStackCallback callback;
FrameSkipper skipper; int maxFrames; int numFrames; void* closure;
};
static _Unwind_Reason_Code unwind_callback(struct _Unwind_Context* context, void* closure) {
_Unwind_Reason_Code ret = _URC_NO_REASON;
unwind_info* info = static_cast<unwind_info*>(closure); void* pc = reinterpret_cast<void*>(_Unwind_GetIP(context)); # if HAVE___LIBC_STACK_END && defined(__aarch64__) // Work around https://sourceware.org/bugzilla/show_bug.cgi?id=32612 // The _dl_tlsdesc_dynamic function can't be unwound through with // _Unwind_Backtrace when glibc is built with aarch64 PAC (that leads // to a crash). // Unfortunately, we can't get the address of that specific function, so // we just disallow all of ld-linux-aarch64.so.1: when we hit an address // in there, we make _Unwind_Backtrace stop. // In the case of _dl_tlsdesc_dynamic, this would stop the stackwalk at // tls_get_addr_tail, which is enough information to know the stack comes // from ld.so, and we even get inlining info giving us malloc, // allocate_dtv_entry and allocate_and_init, which is plenty enough and // better than nothing^Hcrashing. // To figure out whether the frame falls into ld-linux-aarch64.so.1, we // use __libc_stack_end (which lives there and is .data) as upper bound // (assuming .data comes after .text), and get the base address of the // library via dladdr. if (!ldso_base) {
Dl_info info;
dladdr(&__libc_stack_end, &info);
ldso_base = (uintptr_t)info.dli_fbase;
} if (ldso_base && ((uintptr_t)pc > ldso_base) &&
(uintptr_t)pc < (uintptr_t)&__libc_stack_end) { // Any error code will do, we just want to stop the walk even when // we haven't reached the limit.
ret = _URC_FOREIGN_EXCEPTION_CAUGHT;
} # endif // TODO Use something like '_Unwind_GetGR()' to get the stack pointer. if (!info->skipper.ShouldSkipPC(pc)) {
info->numFrames++;
(*info->callback)(info->numFrames, pc, nullptr, info->closure); if (info->maxFrames != 0 && info->numFrames == info->maxFrames) { // Again, any error code that stops the walk will do. return _URC_FOREIGN_EXCEPTION_CAUGHT;
}
} return ret;
}
// We ignore the return value from _Unwind_Backtrace. There are three main // reasons for this. // - On ARM/Android bionic's _Unwind_Backtrace usually (always?) returns // _URC_FAILURE. See // https://bugzilla.mozilla.org/show_bug.cgi?id=717853#c110. // - If aMaxFrames != 0, we want to stop early, and the only way to do that // is to make unwind_callback return something other than _URC_NO_REASON, // which causes _Unwind_Backtrace to return a non-success code. // - MozStackWalk doesn't have a return value anyway.
(void)_Unwind_Backtrace(unwind_callback, &info);
}
# if !defined(XP_FREEBSD) // On FreeBSD, dli_sname is unusably bad, it often returns things like // 'gtk_xtbin_new' or 'XRE_GetBootstrap' instead of long C++ symbols. Just let // GetFunction do the lookup directly in the ELF image.
constchar* symbol = info.dli_sname; if (!symbol || symbol[0] == '\0') { returntrue;
}
if (aDetails->function[0] == '\0') { // Just use the mangled symbol if demangling failed.
strncpy(aDetails->function, symbol, sizeof(aDetails->function));
aDetails->function[std::size(aDetails->function) - 1] = '\0';
}
# ifdefined(XP_MACOSX) && defined(__aarch64__) // On macOS arm64, system libraries are arm64e binaries, and arm64e can do // pointer authentication: The low bits of the pointer are the actual pointer // value, and the high bits are an encrypted hash. During stackwalking, we need // to strip off this hash. In theory, ptrauth_strip would be the right function // to call for this. However, that function is a no-op unless it's called from // code which also builds as arm64e - which we do not. So we cannot use it. So // for now, we hardcode a mask that seems to work today: 40 bits for the pointer // and 24 bits for the hash seems to do the trick. We can worry about // dynamically computing the correct mask if this ever stops working. const uintptr_t kPointerMask =
(uintptr_t(1) << 40) - 1; // 40 bits pointer, 24 bit PAC # else const uintptr_t kPointerMask = ~uintptr_t(0); # endif
// Sanitize the given aBp. Assume that something reasonably close to // but before the stack end is going be a valid frame pointer. Also // check that it is an aligned address. This increases the chances // that if the pointer is not valid (which might happen if the caller // called __builtin_frame_address(1) and its frame is busted for some // reason), we won't read it, leading to a crash. Because the calling // code is not using frame pointers when returning, it might actually // recover just fine. staticconst uintptr_t kMaxStackSize = 8 * 1024 * 1024; if (uintptr_t(aBp) < uintptr_t(aStackEnd) -
std::min(kMaxStackSize, uintptr_t(aStackEnd)) ||
aBp >= aStackEnd || (uintptr_t(aBp) & 3)) { return;
}
while (aBp) { void** next = (void**)*aBp; // aBp may not be a frame pointer on i386 if code was compiled with // -fomit-frame-pointer, so do some sanity checks. // (aBp should be a frame pointer on ppc(64) but checking anyway may help // a little if the stack has been corrupted.) // We don't need to check against the begining of the stack because // we can assume that aBp > sp if (next <= aBp || next >= aStackEnd || (uintptr_t(next) & 3)) { break;
} # if (defined(__ppc__) && defined(XP_MACOSX)) || defined(__powerpc64__) // ppc mac or powerpc64 linux void* pc = *(aBp + 2);
aBp += 3; # else// i386 or powerpc32 linux void* pc = *(aBp + 1);
aBp += 2; # endif
// Strip off pointer authentication hash, if present. For now, it looks // like only return addresses require stripping, and stack pointers do // not. This might change in the future.
pc = (void*)((uintptr_t)pc & kPointerMask);
if (!skipper.ShouldSkipPC(pc)) { // Assume that the SP points to the BP of the function // it called. We can't know the exact location of the SP // but this should be sufficient for our use the SP // to order elements on the stack.
numFrames++;
(*aCallback)(numFrames, pc, aBp, aClosure); if (aMaxFrames != 0 && numFrames == aMaxFrames) { break;
}
}
aBp = next;
}
}
namespace mozilla {
MFBT_API void FramePointerStackWalk(MozWalkStackCallback aCallback,
uint32_t aMaxFrames, void* aClosure, void** aBp, void* aStackEnd) { // We don't pass a aFirstFramePC because we start walking the stack from the // frame at aBp.
DoFramePointerStackWalk(aCallback, nullptr, aMaxFrames, aClosure, aBp,
aStackEnd);
}
} // namespace mozilla
#else
namespace mozilla {
MFBT_API void FramePointerStackWalk(MozWalkStackCallback aCallback,
uint32_t aMaxFrames, void* aClosure, void** aBp, void* aStackEnd) {}
} // namespace mozilla
MFBT_API int MozFormatCodeAddress(char* aBuffer, uint32_t aBufferSize,
uint32_t aFrameNumber, constvoid* aPC, constchar* aFunction, constchar* aLibrary,
ptrdiff_t aLOffset, constchar* aFileName,
uint32_t aLineNo) { constchar* function = aFunction && aFunction[0] ? aFunction : "???"; if (aFileName && aFileName[0]) { // We have a filename and (presumably) a line number. Use them. return SprintfBuf(aBuffer, aBufferSize, "#%02u: %s (%s:%u)", aFrameNumber,
function, aFileName, aLineNo);
} elseif (aLibrary && aLibrary[0]) { // We have no filename, but we do have a library name. Use it and the // library offset, and print them in a way that `fix_stacks.py` can // post-process. return SprintfBuf(aBuffer, aBufferSize, "#%02u: %s[%s +0x%" PRIxPTR "]",
aFrameNumber, function, aLibrary, static_cast<uintptr_t>(aLOffset));
} else { // We have nothing useful to go on. (The format string is split because // '??)' is a trigraph and causes a warning, sigh.) return SprintfBuf(aBuffer, aBufferSize, "#%02u: ??? (???:???" ")",
aFrameNumber);
}
}
staticvoid EnsureWrite(FILE* aStream, constchar* aBuf, size_t aLen) { #ifdef XP_WIN int fd = _fileno(aStream); #else int fd = fileno(aStream); #endif while (aLen > 0) { #ifdef XP_WIN auto written = _write(fd, aBuf, aLen); #else auto written = write(fd, aBuf, aLen); #endif if (written <= 0 || size_t(written) > aLen) { break;
}
aBuf += written;
aLen -= written;
}
}
template <int N> staticint PrintStackFrameBuf(char (&aBuf)[N], uint32_t aFrameNumber, void* aPC, void* aSP) {
MozCodeAddressDetails details;
MozDescribeCodeAddress(aPC, &details); int len =
MozFormatCodeAddressDetails(aBuf, N - 1, aFrameNumber, aPC, &details);
len = std::min(len, N - 2);
aBuf[len++] = '\n';
aBuf[len] = '\0'; return len;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.