| /* -*- 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/ArrayUtils.h" |
| #include "mozilla/Assertions.h" |
| #include "mozilla/IntegerPrintfMacros.h" |
| #include "mozilla/StackWalk.h" |
| |
| #include <string.h> |
| |
| #if defined(_MSC_VER) && _MSC_VER < 1900 |
| #define snprintf _snprintf |
| #endif |
| |
| using namespace mozilla; |
| |
| // The presence of this address is the stack must stop the stack walk. If |
| // there is no such address, the structure will be {nullptr, true}. |
| struct CriticalAddress |
| { |
| void* mAddr; |
| bool mInit; |
| }; |
| static CriticalAddress gCriticalAddress; |
| |
| // for _Unwind_Backtrace from libcxxrt or libunwind |
| // cxxabi.h from libcxxrt implicitly includes unwind.h first |
| #if defined(HAVE__UNWIND_BACKTRACE) && !defined(_GNU_SOURCE) |
| #define _GNU_SOURCE |
| #endif |
| |
| #if defined(HAVE_DLOPEN) || defined(XP_DARWIN) |
| #include <dlfcn.h> |
| #endif |
| |
| #define MOZ_STACKWALK_SUPPORTS_MACOSX \ |
| (defined(XP_DARWIN) && \ |
| (defined(__i386) || defined(__ppc__) || defined(HAVE__UNWIND_BACKTRACE))) |
| |
| #define MOZ_STACKWALK_SUPPORTS_LINUX \ |
| (defined(linux) && \ |
| ((defined(__GNUC__) && (defined(__i386) || defined(PPC))) || \ |
| defined(HAVE__UNWIND_BACKTRACE))) |
| |
| #if __GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 1) |
| #define HAVE___LIBC_STACK_END 1 |
| #else |
| #define HAVE___LIBC_STACK_END 0 |
| #endif |
| |
| #if HAVE___LIBC_STACK_END |
| extern MOZ_EXPORT void* __libc_stack_end; // from ld-linux.so |
| #endif |
| |
| #ifdef ANDROID |
| #include <algorithm> |
| #include <unistd.h> |
| #include <pthread.h> |
| #endif |
| |
| #if MOZ_STACKWALK_SUPPORTS_MACOSX |
| #include <pthread.h> |
| #include <sys/errno.h> |
| #ifdef MOZ_WIDGET_COCOA |
| #include <CoreServices/CoreServices.h> |
| #endif |
| |
| typedef void |
| malloc_logger_t(uint32_t aType, |
| uintptr_t aArg1, uintptr_t aArg2, uintptr_t aArg3, |
| uintptr_t aResult, uint32_t aNumHotFramesToSkip); |
| extern malloc_logger_t* malloc_logger; |
| |
| static void |
| stack_callback(uint32_t aFrameNumber, void* aPc, void* aSp, void* aClosure) |
| { |
| const char* name = static_cast<char*>(aClosure); |
| Dl_info info; |
| |
| // On Leopard dladdr returns the wrong value for "new_sem_from_pool". The |
| // stack shows up as having two pthread_cond_wait$UNIX2003 frames. The |
| // correct one is the first that we find on our way up, so the |
| // following check for gCriticalAddress.mAddr is critical. |
| if (gCriticalAddress.mAddr || dladdr(aPc, &info) == 0 || |
| !info.dli_sname || strcmp(info.dli_sname, name) != 0) { |
| return; |
| } |
| gCriticalAddress.mAddr = aPc; |
| } |
| |
| #if defined(MOZ_WIDGET_COCOA) && defined(DEBUG) |
| #define MAC_OS_X_VERSION_10_7_HEX 0x00001070 |
| |
| static int32_t OSXVersion() |
| { |
| static int32_t gOSXVersion = 0x0; |
| if (gOSXVersion == 0x0) { |
| OSErr err = ::Gestalt(gestaltSystemVersion, (SInt32*)&gOSXVersion); |
| MOZ_ASSERT(err == noErr); |
| } |
| return gOSXVersion; |
| } |
| |
| static bool OnLionOrLater() |
| { |
| return (OSXVersion() >= MAC_OS_X_VERSION_10_7_HEX); |
| } |
| #endif |
| |
| static void |
| my_malloc_logger(uint32_t aType, |
| uintptr_t aArg1, uintptr_t aArg2, uintptr_t aArg3, |
| uintptr_t aResult, uint32_t aNumHotFramesToSkip) |
| { |
| static bool once = false; |
| if (once) { |
| return; |
| } |
| once = true; |
| |
| // On Leopard dladdr returns the wrong value for "new_sem_from_pool". The |
| // stack shows up as having two pthread_cond_wait$UNIX2003 frames. |
| const char* name = "new_sem_from_pool"; |
| MozStackWalk(stack_callback, /* skipFrames */ 0, /* maxFrames */ 0, |
| const_cast<char*>(name), 0, nullptr); |
| } |
| |
| // This is called from NS_LogInit() and from the stack walking functions, but |
| // only the first call has any effect. We need to call this function from both |
| // places because it must run before any mutexes are created, and also before |
| // any objects whose refcounts we're logging are created. Running this |
| // function during NS_LogInit() ensures that we meet the first criterion, and |
| // running this function during the stack walking functions ensures we meet the |
| // second criterion. |
| MFBT_API void |
| StackWalkInitCriticalAddress() |
| { |
| if (gCriticalAddress.mInit) { |
| return; |
| } |
| gCriticalAddress.mInit = true; |
| // We must not do work when 'new_sem_from_pool' calls realloc, since |
| // it holds a non-reentrant spin-lock and we will quickly deadlock. |
| // new_sem_from_pool is not directly accessible using dlsym, so |
| // we force a situation where new_sem_from_pool is on the stack and |
| // use dladdr to check the addresses. |
| |
| // malloc_logger can be set by external tools like 'Instruments' or 'leaks' |
| malloc_logger_t* old_malloc_logger = malloc_logger; |
| malloc_logger = my_malloc_logger; |
| |
| pthread_cond_t cond; |
| int r = pthread_cond_init(&cond, 0); |
| MOZ_ASSERT(r == 0); |
| pthread_mutex_t mutex; |
| r = pthread_mutex_init(&mutex, 0); |
| MOZ_ASSERT(r == 0); |
| r = pthread_mutex_lock(&mutex); |
| MOZ_ASSERT(r == 0); |
| struct timespec abstime = { 0, 1 }; |
| r = pthread_cond_timedwait_relative_np(&cond, &mutex, &abstime); |
| |
| // restore the previous malloc logger |
| malloc_logger = old_malloc_logger; |
| |
| // On Lion, malloc is no longer called from pthread_cond_*wait*. This prevents |
| // us from finding the address, but that is fine, since with no call to malloc |
| // there is no critical address. |
| #ifdef MOZ_WIDGET_COCOA |
| MOZ_ASSERT(OnLionOrLater() || gCriticalAddress.mAddr != nullptr); |
| #endif |
| MOZ_ASSERT(r == ETIMEDOUT); |
| r = pthread_mutex_unlock(&mutex); |
| MOZ_ASSERT(r == 0); |
| r = pthread_mutex_destroy(&mutex); |
| MOZ_ASSERT(r == 0); |
| r = pthread_cond_destroy(&cond); |
| MOZ_ASSERT(r == 0); |
| } |
| |
| static bool |
| IsCriticalAddress(void* aPC) |
| { |
| return gCriticalAddress.mAddr == aPC; |
| } |
| #else |
| static bool |
| IsCriticalAddress(void* aPC) |
| { |
| return false; |
| } |
| // We still initialize gCriticalAddress.mInit so that this code behaves |
| // the same on all platforms. Otherwise a failure to init would be visible |
| // only on OS X. |
| MFBT_API void |
| StackWalkInitCriticalAddress() |
| { |
| gCriticalAddress.mInit = true; |
| } |
| #endif |
| |
| #if defined(_WIN32) && (defined(_M_IX86) || defined(_M_AMD64) || defined(_M_IA64)) // WIN32 x86 stack walking code |
| |
| #include <windows.h> |
| #include <process.h> |
| #include <stdio.h> |
| #include <malloc.h> |
| #include "mozilla/ArrayUtils.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 |
| |
| struct WalkStackData |
| { |
| // Are we walking the stack of the calling thread? Note that we need to avoid |
| // calling fprintf and friends if this is false, in order to avoid deadlocks. |
| bool walkCallingThread; |
| uint32_t skipFrames; |
| HANDLE thread; |
| HANDLE process; |
| HANDLE eventStart; |
| HANDLE eventEnd; |
| void** pcs; |
| uint32_t pc_size; |
| uint32_t pc_count; |
| uint32_t pc_max; |
| void** sps; |
| uint32_t sp_size; |
| uint32_t sp_count; |
| void* platformData; |
| }; |
| |
| DWORD gStackWalkThread; |
| CRITICAL_SECTION gDbgHelpCS; |
| |
| // Routine to print an error message to standard error. |
| static void |
| PrintError(const char* aPrefix) |
| { |
| LPVOID lpMsgBuf; |
| DWORD lastErr = GetLastError(); |
| FormatMessageA( |
| FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, |
| nullptr, |
| lastErr, |
| MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language |
| (LPSTR)&lpMsgBuf, |
| 0, |
| nullptr |
| ); |
| fprintf(stderr, "### ERROR: %s: %s", |
| aPrefix, lpMsgBuf ? lpMsgBuf : "(null)\n"); |
| fflush(stderr); |
| LocalFree(lpMsgBuf); |
| } |
| |
| static unsigned int WINAPI WalkStackThread(void* aData); |
| |
| static bool |
| EnsureWalkThreadReady() |
| { |
| static bool walkThreadReady = false; |
| static HANDLE stackWalkThread = nullptr; |
| static HANDLE readyEvent = nullptr; |
| |
| if (walkThreadReady) { |
| return walkThreadReady; |
| } |
| |
| if (!stackWalkThread) { |
| readyEvent = ::CreateEvent(nullptr, FALSE /* auto-reset*/, |
| FALSE /* initially non-signaled */, |
| nullptr); |
| if (!readyEvent) { |
| PrintError("CreateEvent"); |
| return false; |
| } |
| |
| unsigned int threadID; |
| stackWalkThread = (HANDLE)_beginthreadex(nullptr, 0, WalkStackThread, |
| (void*)readyEvent, 0, &threadID); |
| if (!stackWalkThread) { |
| PrintError("CreateThread"); |
| ::CloseHandle(readyEvent); |
| readyEvent = nullptr; |
| return false; |
| } |
| gStackWalkThread = threadID; |
| ::CloseHandle(stackWalkThread); |
| } |
| |
| MOZ_ASSERT((stackWalkThread && readyEvent) || |
| (!stackWalkThread && !readyEvent)); |
| |
| // The thread was created. Try to wait an arbitrary amount of time (1 second |
| // should be enough) for its event loop to start before posting events to it. |
| DWORD waitRet = ::WaitForSingleObject(readyEvent, 1000); |
| if (waitRet == WAIT_TIMEOUT) { |
| // We get a timeout if we're called during static initialization because |
| // the thread will only start executing after we return so it couldn't |
| // have signalled the event. If that is the case, give up for now and |
| // try again next time we're called. |
| return false; |
| } |
| ::CloseHandle(readyEvent); |
| stackWalkThread = nullptr; |
| readyEvent = nullptr; |
| |
| |
| ::InitializeCriticalSection(&gDbgHelpCS); |
| |
| return walkThreadReady = true; |
| } |
| |
| static void |
| WalkStackMain64(struct WalkStackData* aData) |
| { |
| // Get a context for the specified thread. |
| CONTEXT context; |
| if (!aData->platformData) { |
| memset(&context, 0, sizeof(CONTEXT)); |
| context.ContextFlags = CONTEXT_FULL; |
| if (!GetThreadContext(aData->thread, &context)) { |
| if (aData->walkCallingThread) { |
| PrintError("GetThreadContext"); |
| } |
| return; |
| } |
| } else { |
| context = *static_cast<CONTEXT*>(aData->platformData); |
| } |
| |
| #if defined(_M_IX86) || defined(_M_IA64) |
| // Setup initial stack frame to walk from. |
| STACKFRAME64 frame64; |
| memset(&frame64, 0, sizeof(frame64)); |
| #ifdef _M_IX86 |
| frame64.AddrPC.Offset = context.Eip; |
| frame64.AddrStack.Offset = context.Esp; |
| frame64.AddrFrame.Offset = context.Ebp; |
| #elif defined _M_IA64 |
| frame64.AddrPC.Offset = context.StIIP; |
| frame64.AddrStack.Offset = context.SP; |
| frame64.AddrFrame.Offset = context.RsBSP; |
| #endif |
| frame64.AddrPC.Mode = AddrModeFlat; |
| frame64.AddrStack.Mode = AddrModeFlat; |
| frame64.AddrFrame.Mode = AddrModeFlat; |
| frame64.AddrReturn.Mode = AddrModeFlat; |
| #endif |
| |
| // Skip our own stack walking frames. |
| int skip = (aData->walkCallingThread ? 3 : 0) + aData->skipFrames; |
| |
| // Now walk the stack. |
| while (true) { |
| DWORD64 addr; |
| DWORD64 spaddr; |
| |
| #if defined(_M_IX86) || defined(_M_IA64) |
| // 32-bit frame unwinding. |
| // Debug routines are not threadsafe, so grab the lock. |
| EnterCriticalSection(&gDbgHelpCS); |
| BOOL ok = StackWalk64( |
| #if defined _M_IA64 |
| IMAGE_FILE_MACHINE_IA64, |
| #elif defined _M_IX86 |
| IMAGE_FILE_MACHINE_I386, |
| #endif |
| aData->process, |
| aData->thread, |
| &frame64, |
| &context, |
| nullptr, |
| SymFunctionTableAccess64, // function table access routine |
| SymGetModuleBase64, // module base routine |
| 0 |
| ); |
| LeaveCriticalSection(&gDbgHelpCS); |
| |
| if (ok) { |
| addr = frame64.AddrPC.Offset; |
| spaddr = frame64.AddrStack.Offset; |
| } else { |
| addr = 0; |
| spaddr = 0; |
| if (aData->walkCallingThread) { |
| PrintError("WalkStack64"); |
| } |
| } |
| |
| if (!ok) { |
| break; |
| } |
| |
| #elif defined(_M_AMD64) |
| // 64-bit frame unwinding. |
| // Try to look up unwind metadata for the current function. |
| ULONG64 imageBase; |
| PRUNTIME_FUNCTION runtimeFunction = |
| RtlLookupFunctionEntry(context.Rip, &imageBase, NULL); |
| |
| if (!runtimeFunction) { |
| // Alas, this is probably a JIT frame, for which we don't generate unwind |
| // info and so we have to give up. |
| break; |
| } |
| |
| PVOID dummyHandlerData; |
| ULONG64 dummyEstablisherFrame; |
| RtlVirtualUnwind(UNW_FLAG_NHANDLER, |
| imageBase, |
| context.Rip, |
| runtimeFunction, |
| &context, |
| &dummyHandlerData, |
| &dummyEstablisherFrame, |
| nullptr); |
| |
| addr = context.Rip; |
| spaddr = context.Rsp; |
| |
| #else |
| #error "unknown platform" |
| #endif |
| |
| if (addr == 0) { |
| break; |
| } |
| |
| if (skip-- > 0) { |
| continue; |
| } |
| |
| if (aData->pc_count < aData->pc_size) { |
| aData->pcs[aData->pc_count] = (void*)addr; |
| } |
| ++aData->pc_count; |
| |
| if (aData->sp_count < aData->sp_size) { |
| aData->sps[aData->sp_count] = (void*)spaddr; |
| } |
| ++aData->sp_count; |
| |
| if (aData->pc_max != 0 && aData->pc_count == aData->pc_max) { |
| break; |
| } |
| |
| #if defined(_M_IX86) || defined(_M_IA64) |
| if (frame64.AddrReturn.Offset == 0) { |
| break; |
| } |
| #endif |
| } |
| } |
| |
| static unsigned int WINAPI |
| WalkStackThread(void* aData) |
| { |
| BOOL msgRet; |
| MSG msg; |
| |
| // Call PeekMessage to force creation of a message queue so that |
| // other threads can safely post events to us. |
| ::PeekMessage(&msg, nullptr, WM_USER, WM_USER, PM_NOREMOVE); |
| |
| // and tell the thread that created us that we're ready. |
| HANDLE readyEvent = (HANDLE)aData; |
| ::SetEvent(readyEvent); |
| |
| while ((msgRet = ::GetMessage(&msg, (HWND)-1, 0, 0)) != 0) { |
| if (msgRet == -1) { |
| PrintError("GetMessage"); |
| } else { |
| DWORD ret; |
| |
| struct WalkStackData* data = (WalkStackData*)msg.lParam; |
| if (!data) { |
| continue; |
| } |
| |
| // Don't suspend the calling thread until it's waiting for |
| // us; otherwise the number of frames on the stack could vary. |
| ret = ::WaitForSingleObject(data->eventStart, INFINITE); |
| if (ret != WAIT_OBJECT_0) { |
| PrintError("WaitForSingleObject"); |
| } |
| |
| // Suspend the calling thread, dump his stack, and then resume him. |
| // He's currently waiting for us to finish so now should be a good time. |
| ret = ::SuspendThread(data->thread); |
| if (ret == -1) { |
| PrintError("ThreadSuspend"); |
| } else { |
| WalkStackMain64(data); |
| |
| ret = ::ResumeThread(data->thread); |
| if (ret == -1) { |
| PrintError("ThreadResume"); |
| } |
| } |
| |
| ::SetEvent(data->eventEnd); |
| } |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * 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. |
| */ |
| |
| MFBT_API bool |
| MozStackWalk(MozWalkStackCallback aCallback, uint32_t aSkipFrames, |
| uint32_t aMaxFrames, void* aClosure, uintptr_t aThread, |
| void* aPlatformData) |
| { |
| StackWalkInitCriticalAddress(); |
| static HANDLE myProcess = nullptr; |
| HANDLE myThread; |
| DWORD walkerReturn; |
| struct WalkStackData data; |
| |
| if (!EnsureWalkThreadReady()) { |
| return false; |
| } |
| |
| HANDLE currentThread = ::GetCurrentThread(); |
| HANDLE targetThread = |
| aThread ? reinterpret_cast<HANDLE>(aThread) : currentThread; |
| data.walkCallingThread = (targetThread == currentThread); |
| |
| // Have to duplicate handle to get a real handle. |
| if (!myProcess) { |
| if (!::DuplicateHandle(::GetCurrentProcess(), |
| ::GetCurrentProcess(), |
| ::GetCurrentProcess(), |
| &myProcess, |
| PROCESS_ALL_ACCESS, FALSE, 0)) { |
| if (data.walkCallingThread) { |
| PrintError("DuplicateHandle (process)"); |
| } |
| return false; |
| } |
| } |
| if (!::DuplicateHandle(::GetCurrentProcess(), |
| targetThread, |
| ::GetCurrentProcess(), |
| &myThread, |
| THREAD_ALL_ACCESS, FALSE, 0)) { |
| if (data.walkCallingThread) { |
| PrintError("DuplicateHandle (thread)"); |
| } |
| return false; |
| } |
| |
| data.skipFrames = aSkipFrames; |
| data.thread = myThread; |
| data.process = myProcess; |
| void* local_pcs[1024]; |
| data.pcs = local_pcs; |
| data.pc_count = 0; |
| data.pc_size = ArrayLength(local_pcs); |
| data.pc_max = aMaxFrames; |
| void* local_sps[1024]; |
| data.sps = local_sps; |
| data.sp_count = 0; |
| data.sp_size = ArrayLength(local_sps); |
| data.platformData = aPlatformData; |
| |
| if (aThread) { |
| // If we're walking the stack of another thread, we don't need to |
| // use a separate walker thread. |
| WalkStackMain64(&data); |
| |
| if (data.pc_count > data.pc_size) { |
| data.pcs = (void**)_alloca(data.pc_count * sizeof(void*)); |
| data.pc_size = data.pc_count; |
| data.pc_count = 0; |
| data.sps = (void**)_alloca(data.sp_count * sizeof(void*)); |
| data.sp_size = data.sp_count; |
| data.sp_count = 0; |
| WalkStackMain64(&data); |
| } |
| } else { |
| data.eventStart = ::CreateEvent(nullptr, FALSE /* auto-reset*/, |
| FALSE /* initially non-signaled */, nullptr); |
| data.eventEnd = ::CreateEvent(nullptr, FALSE /* auto-reset*/, |
| FALSE /* initially non-signaled */, nullptr); |
| |
| ::PostThreadMessage(gStackWalkThread, WM_USER, 0, (LPARAM)&data); |
| |
| walkerReturn = ::SignalObjectAndWait(data.eventStart, |
| data.eventEnd, INFINITE, FALSE); |
| if (walkerReturn != WAIT_OBJECT_0 && data.walkCallingThread) { |
| PrintError("SignalObjectAndWait (1)"); |
| } |
| if (data.pc_count > data.pc_size) { |
| data.pcs = (void**)_alloca(data.pc_count * sizeof(void*)); |
| data.pc_size = data.pc_count; |
| data.pc_count = 0; |
| data.sps = (void**)_alloca(data.sp_count * sizeof(void*)); |
| data.sp_size = data.sp_count; |
| data.sp_count = 0; |
| ::PostThreadMessage(gStackWalkThread, WM_USER, 0, (LPARAM)&data); |
| walkerReturn = ::SignalObjectAndWait(data.eventStart, |
| data.eventEnd, INFINITE, FALSE); |
| if (walkerReturn != WAIT_OBJECT_0 && data.walkCallingThread) { |
| PrintError("SignalObjectAndWait (2)"); |
| } |
| } |
| |
| ::CloseHandle(data.eventStart); |
| ::CloseHandle(data.eventEnd); |
| } |
| |
| ::CloseHandle(myThread); |
| |
| for (uint32_t i = 0; i < data.pc_count; ++i) { |
| (*aCallback)(i + 1, data.pcs[i], data.sps[i], aClosure); |
| } |
| |
| return data.pc_count != 0; |
| } |
| |
| |
| static BOOL CALLBACK |
| callbackEspecial64( |
| PCSTR aModuleName, |
| DWORD64 aModuleBase, |
| ULONG aModuleSize, |
| PVOID aUserContext) |
| { |
| BOOL retval = TRUE; |
| DWORD64 addr = *(DWORD64*)aUserContext; |
| |
| /* |
| * 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. |
| */ |
| const BOOL 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(GetCurrentProcess(), nullptr, |
| (PSTR)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 |
| |
| BOOL SymGetModuleInfoEspecial64(HANDLE aProcess, DWORD64 aAddr, |
| PIMAGEHLP_MODULE64 aModuleInfo, |
| PIMAGEHLP_LINE64 aLineInfo) |
| { |
| BOOL retval = FALSE; |
| |
| /* |
| * 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(aProcess, aAddr, aModuleInfo); |
| if (retval == FALSE) { |
| /* |
| * Not loaded, here's the magic. |
| * Go through all the modules. |
| */ |
| // Need to cast to PENUMLOADED_MODULES_CALLBACK64 because the |
| // constness of the first parameter of |
| // PENUMLOADED_MODULES_CALLBACK64 varies over SDK versions (from |
| // non-const to const over time). See bug 391848 and bug |
| // 415426. |
| BOOL enumRes = EnumerateLoadedModules64( |
| aProcess, |
| (PENUMLOADED_MODULES_CALLBACK64)callbackEspecial64, |
| (PVOID)&aAddr); |
| if (enumRes != FALSE) { |
| /* |
| * One final go. |
| * If it fails, then well, we have other problems. |
| */ |
| retval = SymGetModuleInfo64(aProcess, 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(aProcess, aAddr, &displacement, aLineInfo); |
| if (!lineRes) { |
| // Clear out aLineInfo to indicate that it's not valid |
| memset(aLineInfo, 0, sizeof(*aLineInfo)); |
| } |
| } |
| |
| return retval; |
| } |
| |
| static bool |
| EnsureSymInitialized() |
| { |
| static bool gInitialized = false; |
| bool retStat; |
| |
| if (gInitialized) { |
| return gInitialized; |
| } |
| |
| if (!EnsureWalkThreadReady()) { |
| return false; |
| } |
| |
| SymSetOptions(SYMOPT_LOAD_LINES | SYMOPT_UNDNAME); |
| retStat = SymInitialize(GetCurrentProcess(), nullptr, TRUE); |
| if (!retStat) { |
| PrintError("SymInitialize"); |
| } |
| |
| gInitialized = retStat; |
| /* XXX At some point we need to arrange to call SymCleanup */ |
| |
| return retStat; |
| } |
| |
| |
| MFBT_API bool |
| MozDescribeCodeAddress(void* aPC, MozCodeAddressDetails* aDetails) |
| { |
| aDetails->library[0] = '\0'; |
| aDetails->loffset = 0; |
| aDetails->filename[0] = '\0'; |
| aDetails->lineno = 0; |
| aDetails->function[0] = '\0'; |
| aDetails->foffset = 0; |
| |
| if (!EnsureSymInitialized()) { |
| return false; |
| } |
| |
| HANDLE myProcess = ::GetCurrentProcess(); |
| BOOL ok; |
| |
| // debug routines are not threadsafe, so grab the lock. |
| EnterCriticalSection(&gDbgHelpCS); |
| |
| // |
| // 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 = SymGetModuleInfoEspecial64(myProcess, addr, &modInfo, &lineInfo); |
| |
| if (modInfoRes) { |
| strncpy(aDetails->library, modInfo.ModuleName, |
| sizeof(aDetails->library)); |
| aDetails->library[mozilla::ArrayLength(aDetails->library) - 1] = '\0'; |
| aDetails->loffset = (char*)aPC - (char*)modInfo.BaseOfImage; |
| |
| if (lineInfo.FileName) { |
| strncpy(aDetails->filename, lineInfo.FileName, |
| sizeof(aDetails->filename)); |
| aDetails->filename[mozilla::ArrayLength(aDetails->filename) - 1] = '\0'; |
| aDetails->lineno = lineInfo.LineNumber; |
| } |
| } |
| |
| ULONG64 buffer[(sizeof(SYMBOL_INFO) + |
| MAX_SYM_NAME * sizeof(TCHAR) + sizeof(ULONG64) - 1) / sizeof(ULONG64)]; |
| PSYMBOL_INFO pSymbol = (PSYMBOL_INFO)buffer; |
| pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO); |
| pSymbol->MaxNameLen = MAX_SYM_NAME; |
| |
| DWORD64 displacement; |
| ok = SymFromAddr(myProcess, addr, &displacement, pSymbol); |
| |
| if (ok) { |
| strncpy(aDetails->function, pSymbol->Name, |
| sizeof(aDetails->function)); |
| aDetails->function[mozilla::ArrayLength(aDetails->function) - 1] = '\0'; |
| aDetails->foffset = static_cast<ptrdiff_t>(displacement); |
| } |
| |
| LeaveCriticalSection(&gDbgHelpCS); // release our lock |
| return true; |
| } |
| |
| // i386 or PPC Linux stackwalking code |
| #elif HAVE_DLADDR && (HAVE__UNWIND_BACKTRACE || MOZ_STACKWALK_SUPPORTS_LINUX || MOZ_STACKWALK_SUPPORTS_MACOSX) |
| |
| #include <stdlib.h> |
| #include <string.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 |
| #if defined(MOZ_DEMANGLE_SYMBOLS) |
| #include <cxxabi.h> |
| #endif // MOZ_DEMANGLE_SYMBOLS |
| |
| void DemangleSymbol(const char* aSymbol, |
| char* aBuffer, |
| int aBufLen) |
| { |
| aBuffer[0] = '\0'; |
| |
| #if defined(MOZ_DEMANGLE_SYMBOLS) |
| /* See demangle.h in the gcc source for the voodoo */ |
| char* demangled = abi::__cxa_demangle(aSymbol, 0, 0, 0); |
| |
| if (demangled) { |
| strncpy(aBuffer, demangled, aBufLen); |
| aBuffer[aBufLen - 1] = '\0'; |
| free(demangled); |
| } |
| #endif // MOZ_DEMANGLE_SYMBOLS |
| } |
| |
| #define X86_OR_PPC (defined(__i386) || defined(PPC) || defined(__ppc__)) |
| #if X86_OR_PPC && (MOZ_STACKWALK_SUPPORTS_MACOSX || MOZ_STACKWALK_SUPPORTS_LINUX) // i386 or PPC Linux or Mac stackwalking code |
| |
| MFBT_API bool |
| MozStackWalk(MozWalkStackCallback aCallback, uint32_t aSkipFrames, |
| uint32_t aMaxFrames, void* aClosure, uintptr_t aThread, |
| void* aPlatformData) |
| { |
| MOZ_ASSERT(!aThread); |
| MOZ_ASSERT(!aPlatformData); |
| StackWalkInitCriticalAddress(); |
| |
| // 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). |
| static const 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 |
| return FramePointerStackWalk(aCallback, aSkipFrames, 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; |
| int skip; |
| int maxFrames; |
| int numFrames; |
| bool isCriticalAbort; |
| void* closure; |
| }; |
| |
| static _Unwind_Reason_Code |
| unwind_callback(struct _Unwind_Context* context, void* closure) |
| { |
| unwind_info* info = static_cast<unwind_info*>(closure); |
| void* pc = reinterpret_cast<void*>(_Unwind_GetIP(context)); |
| // TODO Use something like '_Unwind_GetGR()' to get the stack pointer. |
| if (IsCriticalAddress(pc)) { |
| info->isCriticalAbort = true; |
| // We just want to stop the walk, so any error code will do. Using |
| // _URC_NORMAL_STOP would probably be the most accurate, but it is not |
| // defined on Android for ARM. |
| return _URC_FOREIGN_EXCEPTION_CAUGHT; |
| } |
| if (--info->skip < 0) { |
| 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 _URC_NO_REASON; |
| } |
| |
| MFBT_API bool |
| MozStackWalk(MozWalkStackCallback aCallback, uint32_t aSkipFrames, |
| uint32_t aMaxFrames, void* aClosure, uintptr_t aThread, |
| void* aPlatformData) |
| { |
| MOZ_ASSERT(!aThread); |
| MOZ_ASSERT(!aPlatformData); |
| StackWalkInitCriticalAddress(); |
| unwind_info info; |
| info.callback = aCallback; |
| info.skip = aSkipFrames + 1; |
| info.maxFrames = aMaxFrames; |
| info.numFrames = 0; |
| info.isCriticalAbort = false; |
| info.closure = aClosure; |
| |
| (void)_Unwind_Backtrace(unwind_callback, &info); |
| |
| // We ignore the return value from _Unwind_Backtrace and instead determine |
| // the outcome from |info|. There are two 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. |
| if (info.isCriticalAbort) { |
| return false; |
| } |
| return info.numFrames != 0; |
| } |
| |
| #endif |
| |
| bool MFBT_API |
| MozDescribeCodeAddress(void* aPC, MozCodeAddressDetails* aDetails) |
| { |
| aDetails->library[0] = '\0'; |
| aDetails->loffset = 0; |
| aDetails->filename[0] = '\0'; |
| aDetails->lineno = 0; |
| aDetails->function[0] = '\0'; |
| aDetails->foffset = 0; |
| |
| Dl_info info; |
| int ok = dladdr(aPC, &info); |
| if (!ok) { |
| return true; |
| } |
| |
| strncpy(aDetails->library, info.dli_fname, sizeof(aDetails->library)); |
| aDetails->library[mozilla::ArrayLength(aDetails->library) - 1] = '\0'; |
| aDetails->loffset = (char*)aPC - (char*)info.dli_fbase; |
| |
| const char* symbol = info.dli_sname; |
| if (!symbol || symbol[0] == '\0') { |
| return true; |
| } |
| |
| DemangleSymbol(symbol, aDetails->function, sizeof(aDetails->function)); |
| |
| if (aDetails->function[0] == '\0') { |
| // Just use the mangled symbol if demangling failed. |
| strncpy(aDetails->function, symbol, sizeof(aDetails->function)); |
| aDetails->function[mozilla::ArrayLength(aDetails->function) - 1] = '\0'; |
| } |
| |
| aDetails->foffset = (char*)aPC - (char*)info.dli_saddr; |
| return true; |
| } |
| |
| #else // unsupported platform. |
| |
| MFBT_API bool |
| MozStackWalk(MozWalkStackCallback aCallback, uint32_t aSkipFrames, |
| uint32_t aMaxFrames, void* aClosure, uintptr_t aThread, |
| void* aPlatformData) |
| { |
| MOZ_ASSERT(!aThread); |
| MOZ_ASSERT(!aPlatformData); |
| return false; |
| } |
| |
| MFBT_API bool |
| MozDescribeCodeAddress(void* aPC, MozCodeAddressDetails* aDetails) |
| { |
| aDetails->library[0] = '\0'; |
| aDetails->loffset = 0; |
| aDetails->filename[0] = '\0'; |
| aDetails->lineno = 0; |
| aDetails->function[0] = '\0'; |
| aDetails->foffset = 0; |
| return false; |
| } |
| |
| #endif |
| |
| #if defined(XP_WIN) || defined (XP_MACOSX) || defined (XP_LINUX) |
| namespace mozilla { |
| bool |
| FramePointerStackWalk(MozWalkStackCallback aCallback, uint32_t aSkipFrames, |
| uint32_t aMaxFrames, void* aClosure, void** bp, |
| void* aStackEnd) |
| { |
| // Stack walking code courtesy Kipp's "leaky". |
| |
| int32_t skip = aSkipFrames; |
| uint32_t numFrames = 0; |
| while (bp) { |
| void** next = (void**)*bp; |
| // bp may not be a frame pointer on i386 if code was compiled with |
| // -fomit-frame-pointer, so do some sanity checks. |
| // (bp 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 bp > sp |
| if (next <= bp || |
| next > aStackEnd || |
| (uintptr_t(next) & 3)) { |
| break; |
| } |
| #if (defined(__ppc__) && defined(XP_MACOSX)) || defined(__powerpc64__) |
| // ppc mac or powerpc64 linux |
| void* pc = *(bp + 2); |
| bp += 3; |
| #else // i386 or powerpc32 linux |
| void* pc = *(bp + 1); |
| bp += 2; |
| #endif |
| if (IsCriticalAddress(pc)) { |
| return false; |
| } |
| if (--skip < 0) { |
| // 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, bp, aClosure); |
| if (aMaxFrames != 0 && numFrames == aMaxFrames) { |
| break; |
| } |
| } |
| bp = next; |
| } |
| return numFrames != 0; |
| } |
| } // namespace mozilla |
| |
| #else |
| |
| namespace mozilla { |
| MFBT_API bool |
| FramePointerStackWalk(MozWalkStackCallback aCallback, uint32_t aSkipFrames, |
| void* aClosure, void** aBp) |
| { |
| return false; |
| } |
| } |
| |
| #endif |
| |
| MFBT_API void |
| MozFormatCodeAddressDetails(char* aBuffer, uint32_t aBufferSize, |
| uint32_t aFrameNumber, void* aPC, |
| const MozCodeAddressDetails* aDetails) |
| { |
| MozFormatCodeAddress(aBuffer, aBufferSize, |
| aFrameNumber, aPC, aDetails->function, |
| aDetails->library, aDetails->loffset, |
| aDetails->filename, aDetails->lineno); |
| } |
| |
| MFBT_API void |
| MozFormatCodeAddress(char* aBuffer, uint32_t aBufferSize, uint32_t aFrameNumber, |
| const void* aPC, const char* aFunction, |
| const char* aLibrary, ptrdiff_t aLOffset, |
| const char* aFileName, uint32_t aLineNo) |
| { |
| const char* function = aFunction && aFunction[0] ? aFunction : "???"; |
| if (aFileName && aFileName[0]) { |
| // We have a filename and (presumably) a line number. Use them. |
| snprintf(aBuffer, aBufferSize, |
| "#%02u: %s (%s:%u)", |
| aFrameNumber, function, aFileName, aLineNo); |
| } else if (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 scripts like |
| // fix_{linux,macosx}_stacks.py can easily post-process. |
| snprintf(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.) |
| snprintf(aBuffer, aBufferSize, |
| "#%02u: ??? (???:???" ")", |
| aFrameNumber); |
| } |
| } |