michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* API for getting a stack trace of the C/C++ stack on the current thread */ michael@0: michael@0: #include "mozilla/Assertions.h" michael@0: #include "mozilla/IntegerPrintfMacros.h" michael@0: #include "mozilla/StackWalk.h" michael@0: #include "nsStackWalkPrivate.h" michael@0: michael@0: #include "nsStackWalk.h" michael@0: michael@0: using namespace mozilla; michael@0: michael@0: // The presence of this address is the stack must stop the stack walk. If michael@0: // there is no such address, the structure will be {nullptr, true}. michael@0: struct CriticalAddress { michael@0: void* mAddr; michael@0: bool mInit; michael@0: }; michael@0: static CriticalAddress gCriticalAddress; michael@0: michael@0: // for _Unwind_Backtrace from libcxxrt or libunwind michael@0: // cxxabi.h from libcxxrt implicitly includes unwind.h first michael@0: #if defined(HAVE__UNWIND_BACKTRACE) && !defined(_GNU_SOURCE) michael@0: #define _GNU_SOURCE michael@0: #endif michael@0: michael@0: #if defined(HAVE_DLOPEN) || defined(XP_MACOSX) michael@0: #include michael@0: #endif michael@0: michael@0: #define NSSTACKWALK_SUPPORTS_MACOSX \ michael@0: (defined(XP_MACOSX) && \ michael@0: (defined(__i386) || defined(__ppc__) || defined(HAVE__UNWIND_BACKTRACE))) michael@0: michael@0: #define NSSTACKWALK_SUPPORTS_LINUX \ michael@0: (defined(linux) && \ michael@0: ((defined(__GNUC__) && (defined(__i386) || defined(PPC))) || \ michael@0: defined(HAVE__UNWIND_BACKTRACE))) michael@0: michael@0: #define NSSTACKWALK_SUPPORTS_SOLARIS \ michael@0: (defined(__sun) && \ michael@0: (defined(__sparc) || defined(sparc) || defined(__i386) || defined(i386))) michael@0: michael@0: #if NSSTACKWALK_SUPPORTS_MACOSX michael@0: #include michael@0: #include michael@0: michael@0: typedef void michael@0: malloc_logger_t(uint32_t type, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3, michael@0: uintptr_t result, uint32_t num_hot_frames_to_skip); michael@0: extern malloc_logger_t *malloc_logger; michael@0: michael@0: static void michael@0: stack_callback(void *pc, void *sp, void *closure) michael@0: { michael@0: const char *name = reinterpret_cast(closure); michael@0: Dl_info info; michael@0: michael@0: // On Leopard dladdr returns the wrong value for "new_sem_from_pool". The michael@0: // stack shows up as having two pthread_cond_wait$UNIX2003 frames. The michael@0: // correct one is the first that we find on our way up, so the michael@0: // following check for gCriticalAddress.mAddr is critical. michael@0: if (gCriticalAddress.mAddr || dladdr(pc, &info) == 0 || michael@0: info.dli_sname == nullptr || strcmp(info.dli_sname, name) != 0) michael@0: return; michael@0: gCriticalAddress.mAddr = pc; michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: #define MAC_OS_X_VERSION_10_7_HEX 0x00001070 michael@0: michael@0: static int32_t OSXVersion() michael@0: { michael@0: static int32_t gOSXVersion = 0x0; michael@0: if (gOSXVersion == 0x0) { michael@0: OSErr err = ::Gestalt(gestaltSystemVersion, (SInt32*)&gOSXVersion); michael@0: MOZ_ASSERT(err == noErr); michael@0: } michael@0: return gOSXVersion; michael@0: } michael@0: michael@0: static bool OnLionOrLater() michael@0: { michael@0: return (OSXVersion() >= MAC_OS_X_VERSION_10_7_HEX); michael@0: } michael@0: #endif michael@0: michael@0: static void michael@0: my_malloc_logger(uint32_t type, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3, michael@0: uintptr_t result, uint32_t num_hot_frames_to_skip) michael@0: { michael@0: static bool once = false; michael@0: if (once) michael@0: return; michael@0: once = true; michael@0: michael@0: // On Leopard dladdr returns the wrong value for "new_sem_from_pool". The michael@0: // stack shows up as having two pthread_cond_wait$UNIX2003 frames. michael@0: const char *name = "new_sem_from_pool"; michael@0: NS_StackWalk(stack_callback, /* skipFrames */ 0, /* maxFrames */ 0, michael@0: const_cast(name), 0, nullptr); michael@0: } michael@0: michael@0: // This is called from NS_LogInit() and from the stack walking functions, but michael@0: // only the first call has any effect. We need to call this function from both michael@0: // places because it must run before any mutexes are created, and also before michael@0: // any objects whose refcounts we're logging are created. Running this michael@0: // function during NS_LogInit() ensures that we meet the first criterion, and michael@0: // running this function during the stack walking functions ensures we meet the michael@0: // second criterion. michael@0: void michael@0: StackWalkInitCriticalAddress() michael@0: { michael@0: if(gCriticalAddress.mInit) michael@0: return; michael@0: gCriticalAddress.mInit = true; michael@0: // We must not do work when 'new_sem_from_pool' calls realloc, since michael@0: // it holds a non-reentrant spin-lock and we will quickly deadlock. michael@0: // new_sem_from_pool is not directly accessible using dlsym, so michael@0: // we force a situation where new_sem_from_pool is on the stack and michael@0: // use dladdr to check the addresses. michael@0: michael@0: // malloc_logger can be set by external tools like 'Instruments' or 'leaks' michael@0: malloc_logger_t *old_malloc_logger = malloc_logger; michael@0: malloc_logger = my_malloc_logger; michael@0: michael@0: pthread_cond_t cond; michael@0: int r = pthread_cond_init(&cond, 0); michael@0: MOZ_ASSERT(r == 0); michael@0: pthread_mutex_t mutex; michael@0: r = pthread_mutex_init(&mutex,0); michael@0: MOZ_ASSERT(r == 0); michael@0: r = pthread_mutex_lock(&mutex); michael@0: MOZ_ASSERT(r == 0); michael@0: struct timespec abstime = {0, 1}; michael@0: r = pthread_cond_timedwait_relative_np(&cond, &mutex, &abstime); michael@0: michael@0: // restore the previous malloc logger michael@0: malloc_logger = old_malloc_logger; michael@0: michael@0: // On Lion, malloc is no longer called from pthread_cond_*wait*. This prevents michael@0: // us from finding the address, but that is fine, since with no call to malloc michael@0: // there is no critical address. michael@0: MOZ_ASSERT(OnLionOrLater() || gCriticalAddress.mAddr != nullptr); michael@0: MOZ_ASSERT(r == ETIMEDOUT); michael@0: r = pthread_mutex_unlock(&mutex); michael@0: MOZ_ASSERT(r == 0); michael@0: r = pthread_mutex_destroy(&mutex); michael@0: MOZ_ASSERT(r == 0); michael@0: r = pthread_cond_destroy(&cond); michael@0: MOZ_ASSERT(r == 0); michael@0: } michael@0: michael@0: static bool IsCriticalAddress(void* aPC) michael@0: { michael@0: return gCriticalAddress.mAddr == aPC; michael@0: } michael@0: #else michael@0: static bool IsCriticalAddress(void* aPC) michael@0: { michael@0: return false; michael@0: } michael@0: // We still initialize gCriticalAddress.mInit so that this code behaves michael@0: // the same on all platforms. Otherwise a failure to init would be visible michael@0: // only on OS X. michael@0: void michael@0: StackWalkInitCriticalAddress() michael@0: { michael@0: gCriticalAddress.mInit = true; michael@0: } michael@0: #endif michael@0: michael@0: #if defined(_WIN32) && (defined(_M_IX86) || defined(_M_AMD64) || defined(_M_IA64)) // WIN32 x86 stack walking code michael@0: michael@0: #include "nscore.h" michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include "plstr.h" michael@0: #include "mozilla/ArrayUtils.h" michael@0: michael@0: #include "nspr.h" michael@0: #include michael@0: // We need a way to know if we are building for WXP (or later), as if we are, we michael@0: // need to use the newer 64-bit APIs. API_VERSION_NUMBER seems to fit the bill. michael@0: // A value of 9 indicates we want to use the new APIs. michael@0: #if API_VERSION_NUMBER < 9 michael@0: #error Too old imagehlp.h michael@0: #endif michael@0: michael@0: // Define these as static pointers so that we can load the DLL on the michael@0: // fly (and not introduce a link-time dependency on it). Tip o' the michael@0: // hat to Matt Pietrick for this idea. See: michael@0: // michael@0: // http://msdn.microsoft.com/library/periodic/period97/F1/D3/S245C6.htm michael@0: // michael@0: extern "C" { michael@0: michael@0: extern HANDLE hStackWalkMutex; michael@0: michael@0: bool EnsureSymInitialized(); michael@0: michael@0: bool EnsureWalkThreadReady(); michael@0: michael@0: struct WalkStackData { michael@0: uint32_t skipFrames; michael@0: HANDLE thread; michael@0: bool walkCallingThread; michael@0: HANDLE process; michael@0: HANDLE eventStart; michael@0: HANDLE eventEnd; michael@0: void **pcs; michael@0: uint32_t pc_size; michael@0: uint32_t pc_count; michael@0: uint32_t pc_max; michael@0: void **sps; michael@0: uint32_t sp_size; michael@0: uint32_t sp_count; michael@0: void *platformData; michael@0: }; michael@0: michael@0: void PrintError(char *prefix, WalkStackData* data); michael@0: unsigned int WINAPI WalkStackThread(void* data); michael@0: void WalkStackMain64(struct WalkStackData* data); michael@0: michael@0: michael@0: DWORD gStackWalkThread; michael@0: CRITICAL_SECTION gDbgHelpCS; michael@0: michael@0: } michael@0: michael@0: // Routine to print an error message to standard error. michael@0: void PrintError(const char *prefix) michael@0: { michael@0: LPVOID lpMsgBuf; michael@0: DWORD lastErr = GetLastError(); michael@0: FormatMessageA( michael@0: FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, michael@0: nullptr, michael@0: lastErr, michael@0: MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language michael@0: (LPSTR) &lpMsgBuf, michael@0: 0, michael@0: nullptr michael@0: ); michael@0: fprintf(stderr, "### ERROR: %s: %s", michael@0: prefix, lpMsgBuf ? lpMsgBuf : "(null)\n"); michael@0: fflush(stderr); michael@0: LocalFree(lpMsgBuf); michael@0: } michael@0: michael@0: bool michael@0: EnsureWalkThreadReady() michael@0: { michael@0: static bool walkThreadReady = false; michael@0: static HANDLE stackWalkThread = nullptr; michael@0: static HANDLE readyEvent = nullptr; michael@0: michael@0: if (walkThreadReady) michael@0: return walkThreadReady; michael@0: michael@0: if (stackWalkThread == nullptr) { michael@0: readyEvent = ::CreateEvent(nullptr, FALSE /* auto-reset*/, michael@0: FALSE /* initially non-signaled */, michael@0: nullptr); michael@0: if (readyEvent == nullptr) { michael@0: PrintError("CreateEvent"); michael@0: return false; michael@0: } michael@0: michael@0: unsigned int threadID; michael@0: stackWalkThread = (HANDLE) michael@0: _beginthreadex(nullptr, 0, WalkStackThread, (void*)readyEvent, michael@0: 0, &threadID); michael@0: if (stackWalkThread == nullptr) { michael@0: PrintError("CreateThread"); michael@0: ::CloseHandle(readyEvent); michael@0: readyEvent = nullptr; michael@0: return false; michael@0: } michael@0: gStackWalkThread = threadID; michael@0: ::CloseHandle(stackWalkThread); michael@0: } michael@0: michael@0: MOZ_ASSERT((stackWalkThread != nullptr && readyEvent != nullptr) || michael@0: (stackWalkThread == nullptr && readyEvent == nullptr)); michael@0: michael@0: // The thread was created. Try to wait an arbitrary amount of time (1 second michael@0: // should be enough) for its event loop to start before posting events to it. michael@0: DWORD waitRet = ::WaitForSingleObject(readyEvent, 1000); michael@0: if (waitRet == WAIT_TIMEOUT) { michael@0: // We get a timeout if we're called during static initialization because michael@0: // the thread will only start executing after we return so it couldn't michael@0: // have signalled the event. If that is the case, give up for now and michael@0: // try again next time we're called. michael@0: return false; michael@0: } michael@0: ::CloseHandle(readyEvent); michael@0: stackWalkThread = nullptr; michael@0: readyEvent = nullptr; michael@0: michael@0: michael@0: ::InitializeCriticalSection(&gDbgHelpCS); michael@0: michael@0: return walkThreadReady = true; michael@0: } michael@0: michael@0: void michael@0: WalkStackMain64(struct WalkStackData* data) michael@0: { michael@0: // Get the context information for the thread. That way we will michael@0: // know where our sp, fp, pc, etc. are and can fill in the michael@0: // STACKFRAME64 with the initial values. michael@0: CONTEXT context; michael@0: HANDLE myProcess = data->process; michael@0: HANDLE myThread = data->thread; michael@0: DWORD64 addr; michael@0: DWORD64 spaddr; michael@0: STACKFRAME64 frame64; michael@0: // skip our own stack walking frames michael@0: int skip = (data->walkCallingThread ? 3 : 0) + data->skipFrames; michael@0: BOOL ok; michael@0: michael@0: // Get a context for the specified thread. michael@0: if (!data->platformData) { michael@0: memset(&context, 0, sizeof(CONTEXT)); michael@0: context.ContextFlags = CONTEXT_FULL; michael@0: if (!GetThreadContext(myThread, &context)) { michael@0: if (data->walkCallingThread) { michael@0: PrintError("GetThreadContext"); michael@0: } michael@0: return; michael@0: } michael@0: } else { michael@0: context = *static_cast(data->platformData); michael@0: } michael@0: michael@0: // Setup initial stack frame to walk from michael@0: memset(&frame64, 0, sizeof(frame64)); michael@0: #ifdef _M_IX86 michael@0: frame64.AddrPC.Offset = context.Eip; michael@0: frame64.AddrStack.Offset = context.Esp; michael@0: frame64.AddrFrame.Offset = context.Ebp; michael@0: #elif defined _M_AMD64 michael@0: frame64.AddrPC.Offset = context.Rip; michael@0: frame64.AddrStack.Offset = context.Rsp; michael@0: frame64.AddrFrame.Offset = context.Rbp; michael@0: #elif defined _M_IA64 michael@0: frame64.AddrPC.Offset = context.StIIP; michael@0: frame64.AddrStack.Offset = context.SP; michael@0: frame64.AddrFrame.Offset = context.RsBSP; michael@0: #else michael@0: #error "Should not have compiled this code" michael@0: #endif michael@0: frame64.AddrPC.Mode = AddrModeFlat; michael@0: frame64.AddrStack.Mode = AddrModeFlat; michael@0: frame64.AddrFrame.Mode = AddrModeFlat; michael@0: frame64.AddrReturn.Mode = AddrModeFlat; michael@0: michael@0: // Now walk the stack michael@0: while (1) { michael@0: michael@0: // debug routines are not threadsafe, so grab the lock. michael@0: EnterCriticalSection(&gDbgHelpCS); michael@0: ok = StackWalk64( michael@0: #ifdef _M_AMD64 michael@0: IMAGE_FILE_MACHINE_AMD64, michael@0: #elif defined _M_IA64 michael@0: IMAGE_FILE_MACHINE_IA64, michael@0: #elif defined _M_IX86 michael@0: IMAGE_FILE_MACHINE_I386, michael@0: #else michael@0: #error "Should not have compiled this code" michael@0: #endif michael@0: myProcess, michael@0: myThread, michael@0: &frame64, michael@0: &context, michael@0: nullptr, michael@0: SymFunctionTableAccess64, // function table access routine michael@0: SymGetModuleBase64, // module base routine michael@0: 0 michael@0: ); michael@0: LeaveCriticalSection(&gDbgHelpCS); michael@0: michael@0: if (ok) { michael@0: addr = frame64.AddrPC.Offset; michael@0: spaddr = frame64.AddrStack.Offset; michael@0: } else { michael@0: addr = 0; michael@0: spaddr = 0; michael@0: if (data->walkCallingThread) { michael@0: PrintError("WalkStack64"); michael@0: } michael@0: } michael@0: michael@0: if (!ok || (addr == 0)) { michael@0: break; michael@0: } michael@0: michael@0: if (skip-- > 0) { michael@0: continue; michael@0: } michael@0: michael@0: if (data->pc_count < data->pc_size) michael@0: data->pcs[data->pc_count] = (void*)addr; michael@0: ++data->pc_count; michael@0: michael@0: if (data->sp_count < data->sp_size) michael@0: data->sps[data->sp_count] = (void*)spaddr; michael@0: ++data->sp_count; michael@0: michael@0: if (data->pc_max != 0 && data->pc_count == data->pc_max) michael@0: break; michael@0: michael@0: if (frame64.AddrReturn.Offset == 0) michael@0: break; michael@0: } michael@0: return; michael@0: } michael@0: michael@0: michael@0: unsigned int WINAPI michael@0: WalkStackThread(void* aData) michael@0: { michael@0: BOOL msgRet; michael@0: MSG msg; michael@0: michael@0: // Call PeekMessage to force creation of a message queue so that michael@0: // other threads can safely post events to us. michael@0: ::PeekMessage(&msg, nullptr, WM_USER, WM_USER, PM_NOREMOVE); michael@0: michael@0: // and tell the thread that created us that we're ready. michael@0: HANDLE readyEvent = (HANDLE)aData; michael@0: ::SetEvent(readyEvent); michael@0: michael@0: while ((msgRet = ::GetMessage(&msg, (HWND)-1, 0, 0)) != 0) { michael@0: if (msgRet == -1) { michael@0: PrintError("GetMessage"); michael@0: } else { michael@0: DWORD ret; michael@0: michael@0: struct WalkStackData *data = (WalkStackData *)msg.lParam; michael@0: if (!data) michael@0: continue; michael@0: michael@0: // Don't suspend the calling thread until it's waiting for michael@0: // us; otherwise the number of frames on the stack could vary. michael@0: ret = ::WaitForSingleObject(data->eventStart, INFINITE); michael@0: if (ret != WAIT_OBJECT_0) michael@0: PrintError("WaitForSingleObject"); michael@0: michael@0: // Suspend the calling thread, dump his stack, and then resume him. michael@0: // He's currently waiting for us to finish so now should be a good time. michael@0: ret = ::SuspendThread( data->thread ); michael@0: if (ret == -1) { michael@0: PrintError("ThreadSuspend"); michael@0: } michael@0: else { michael@0: WalkStackMain64(data); michael@0: michael@0: ret = ::ResumeThread(data->thread); michael@0: if (ret == -1) { michael@0: PrintError("ThreadResume"); michael@0: } michael@0: } michael@0: michael@0: ::SetEvent(data->eventEnd); michael@0: } michael@0: } michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: /** michael@0: * Walk the stack, translating PC's found into strings and recording the michael@0: * chain in aBuffer. For this to work properly, the DLLs must be rebased michael@0: * so that the address in the file agrees with the address in memory. michael@0: * Otherwise StackWalk will return FALSE when it hits a frame in a DLL michael@0: * whose in memory address doesn't match its in-file address. michael@0: */ michael@0: michael@0: EXPORT_XPCOM_API(nsresult) michael@0: NS_StackWalk(NS_WalkStackCallback aCallback, uint32_t aSkipFrames, michael@0: uint32_t aMaxFrames, void *aClosure, uintptr_t aThread, michael@0: void *aPlatformData) michael@0: { michael@0: StackWalkInitCriticalAddress(); michael@0: static HANDLE myProcess = nullptr; michael@0: HANDLE myThread; michael@0: DWORD walkerReturn; michael@0: struct WalkStackData data; michael@0: michael@0: if (!EnsureWalkThreadReady()) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: HANDLE targetThread = ::GetCurrentThread(); michael@0: data.walkCallingThread = true; michael@0: if (aThread) { michael@0: HANDLE threadToWalk = reinterpret_cast (aThread); michael@0: // walkCallingThread indicates whether we are walking the caller's stack michael@0: data.walkCallingThread = (threadToWalk == targetThread); michael@0: targetThread = threadToWalk; michael@0: } michael@0: michael@0: // We need to avoid calling fprintf and friends if we're walking the stack of michael@0: // another thread, in order to avoid deadlocks. michael@0: const bool shouldBeThreadSafe = !!aThread; michael@0: michael@0: // Have to duplicate handle to get a real handle. michael@0: if (!myProcess) { michael@0: if (!::DuplicateHandle(::GetCurrentProcess(), michael@0: ::GetCurrentProcess(), michael@0: ::GetCurrentProcess(), michael@0: &myProcess, michael@0: PROCESS_ALL_ACCESS, FALSE, 0)) { michael@0: if (!shouldBeThreadSafe) { michael@0: PrintError("DuplicateHandle (process)"); michael@0: } michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: } michael@0: if (!::DuplicateHandle(::GetCurrentProcess(), michael@0: targetThread, michael@0: ::GetCurrentProcess(), michael@0: &myThread, michael@0: THREAD_ALL_ACCESS, FALSE, 0)) { michael@0: if (!shouldBeThreadSafe) { michael@0: PrintError("DuplicateHandle (thread)"); michael@0: } michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: data.skipFrames = aSkipFrames; michael@0: data.thread = myThread; michael@0: data.process = myProcess; michael@0: void *local_pcs[1024]; michael@0: data.pcs = local_pcs; michael@0: data.pc_count = 0; michael@0: data.pc_size = ArrayLength(local_pcs); michael@0: data.pc_max = aMaxFrames; michael@0: void *local_sps[1024]; michael@0: data.sps = local_sps; michael@0: data.sp_count = 0; michael@0: data.sp_size = ArrayLength(local_sps); michael@0: data.platformData = aPlatformData; michael@0: michael@0: if (aThread) { michael@0: // If we're walking the stack of another thread, we don't need to michael@0: // use a separate walker thread. michael@0: WalkStackMain64(&data); michael@0: michael@0: if (data.pc_count > data.pc_size) { michael@0: data.pcs = (void**) _alloca(data.pc_count * sizeof(void*)); michael@0: data.pc_size = data.pc_count; michael@0: data.pc_count = 0; michael@0: data.sps = (void**) _alloca(data.sp_count * sizeof(void*)); michael@0: data.sp_size = data.sp_count; michael@0: data.sp_count = 0; michael@0: WalkStackMain64(&data); michael@0: } michael@0: } else { michael@0: data.eventStart = ::CreateEvent(nullptr, FALSE /* auto-reset*/, michael@0: FALSE /* initially non-signaled */, nullptr); michael@0: data.eventEnd = ::CreateEvent(nullptr, FALSE /* auto-reset*/, michael@0: FALSE /* initially non-signaled */, nullptr); michael@0: michael@0: ::PostThreadMessage(gStackWalkThread, WM_USER, 0, (LPARAM)&data); michael@0: michael@0: walkerReturn = ::SignalObjectAndWait(data.eventStart, michael@0: data.eventEnd, INFINITE, FALSE); michael@0: if (walkerReturn != WAIT_OBJECT_0 && !shouldBeThreadSafe) michael@0: PrintError("SignalObjectAndWait (1)"); michael@0: if (data.pc_count > data.pc_size) { michael@0: data.pcs = (void**) _alloca(data.pc_count * sizeof(void*)); michael@0: data.pc_size = data.pc_count; michael@0: data.pc_count = 0; michael@0: data.sps = (void**) _alloca(data.sp_count * sizeof(void*)); michael@0: data.sp_size = data.sp_count; michael@0: data.sp_count = 0; michael@0: ::PostThreadMessage(gStackWalkThread, WM_USER, 0, (LPARAM)&data); michael@0: walkerReturn = ::SignalObjectAndWait(data.eventStart, michael@0: data.eventEnd, INFINITE, FALSE); michael@0: if (walkerReturn != WAIT_OBJECT_0 && !shouldBeThreadSafe) michael@0: PrintError("SignalObjectAndWait (2)"); michael@0: } michael@0: michael@0: ::CloseHandle(data.eventStart); michael@0: ::CloseHandle(data.eventEnd); michael@0: } michael@0: michael@0: ::CloseHandle(myThread); michael@0: michael@0: for (uint32_t i = 0; i < data.pc_count; ++i) michael@0: (*aCallback)(data.pcs[i], data.sps[i], aClosure); michael@0: michael@0: return data.pc_count == 0 ? NS_ERROR_FAILURE : NS_OK; michael@0: } michael@0: michael@0: michael@0: static BOOL CALLBACK callbackEspecial64( michael@0: PCSTR aModuleName, michael@0: DWORD64 aModuleBase, michael@0: ULONG aModuleSize, michael@0: PVOID aUserContext) michael@0: { michael@0: BOOL retval = TRUE; michael@0: DWORD64 addr = *(DWORD64*)aUserContext; michael@0: michael@0: /* michael@0: * You'll want to control this if we are running on an michael@0: * architecture where the addresses go the other direction. michael@0: * Not sure this is even a realistic consideration. michael@0: */ michael@0: const BOOL addressIncreases = TRUE; michael@0: michael@0: /* michael@0: * If it falls in side the known range, load the symbols. michael@0: */ michael@0: if (addressIncreases michael@0: ? (addr >= aModuleBase && addr <= (aModuleBase + aModuleSize)) michael@0: : (addr <= aModuleBase && addr >= (aModuleBase - aModuleSize)) michael@0: ) { michael@0: retval = !!SymLoadModule64(GetCurrentProcess(), nullptr, michael@0: (PSTR)aModuleName, nullptr, michael@0: aModuleBase, aModuleSize); michael@0: if (!retval) michael@0: PrintError("SymLoadModule64"); michael@0: } michael@0: michael@0: return retval; michael@0: } michael@0: michael@0: /* michael@0: * SymGetModuleInfoEspecial michael@0: * michael@0: * Attempt to determine the module information. michael@0: * Bug 112196 says this DLL may not have been loaded at the time michael@0: * SymInitialize was called, and thus the module information michael@0: * and symbol information is not available. michael@0: * This code rectifies that problem. michael@0: */ michael@0: michael@0: // New members were added to IMAGEHLP_MODULE64 (that show up in the michael@0: // Platform SDK that ships with VC8, but not the Platform SDK that ships michael@0: // with VC7.1, i.e., between DbgHelp 6.0 and 6.1), but we don't need to michael@0: // use them, and it's useful to be able to function correctly with the michael@0: // older library. (Stock Windows XP SP2 seems to ship with dbghelp.dll michael@0: // version 5.1.) Since Platform SDK version need not correspond to michael@0: // compiler version, and the version number in debughlp.h was NOT bumped michael@0: // when these changes were made, ifdef based on a constant that was michael@0: // added between these versions. michael@0: #ifdef SSRVOPT_SETCONTEXT michael@0: #define NS_IMAGEHLP_MODULE64_SIZE (((offsetof(IMAGEHLP_MODULE64, LoadedPdbName) + sizeof(DWORD64) - 1) / sizeof(DWORD64)) * sizeof(DWORD64)) michael@0: #else michael@0: #define NS_IMAGEHLP_MODULE64_SIZE sizeof(IMAGEHLP_MODULE64) michael@0: #endif michael@0: michael@0: BOOL SymGetModuleInfoEspecial64(HANDLE aProcess, DWORD64 aAddr, PIMAGEHLP_MODULE64 aModuleInfo, PIMAGEHLP_LINE64 aLineInfo) michael@0: { michael@0: BOOL retval = FALSE; michael@0: michael@0: /* michael@0: * Init the vars if we have em. michael@0: */ michael@0: aModuleInfo->SizeOfStruct = NS_IMAGEHLP_MODULE64_SIZE; michael@0: if (nullptr != aLineInfo) { michael@0: aLineInfo->SizeOfStruct = sizeof(IMAGEHLP_LINE64); michael@0: } michael@0: michael@0: /* michael@0: * Give it a go. michael@0: * It may already be loaded. michael@0: */ michael@0: retval = SymGetModuleInfo64(aProcess, aAddr, aModuleInfo); michael@0: michael@0: if (FALSE == retval) { michael@0: BOOL enumRes = FALSE; michael@0: michael@0: /* michael@0: * Not loaded, here's the magic. michael@0: * Go through all the modules. michael@0: */ michael@0: // Need to cast to PENUMLOADED_MODULES_CALLBACK64 because the michael@0: // constness of the first parameter of michael@0: // PENUMLOADED_MODULES_CALLBACK64 varies over SDK versions (from michael@0: // non-const to const over time). See bug 391848 and bug michael@0: // 415426. michael@0: enumRes = EnumerateLoadedModules64(aProcess, (PENUMLOADED_MODULES_CALLBACK64)callbackEspecial64, (PVOID)&aAddr); michael@0: if (FALSE != enumRes) michael@0: { michael@0: /* michael@0: * One final go. michael@0: * If it fails, then well, we have other problems. michael@0: */ michael@0: retval = SymGetModuleInfo64(aProcess, aAddr, aModuleInfo); michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * If we got module info, we may attempt line info as well. michael@0: * We will not report failure if this does not work. michael@0: */ michael@0: if (FALSE != retval && nullptr != aLineInfo) { michael@0: DWORD displacement = 0; michael@0: BOOL lineRes = FALSE; michael@0: lineRes = SymGetLineFromAddr64(aProcess, aAddr, &displacement, aLineInfo); michael@0: if (!lineRes) { michael@0: // Clear out aLineInfo to indicate that it's not valid michael@0: memset(aLineInfo, 0, sizeof(*aLineInfo)); michael@0: } michael@0: } michael@0: michael@0: return retval; michael@0: } michael@0: michael@0: bool michael@0: EnsureSymInitialized() michael@0: { michael@0: static bool gInitialized = false; michael@0: bool retStat; michael@0: michael@0: if (gInitialized) michael@0: return gInitialized; michael@0: michael@0: if (!EnsureWalkThreadReady()) michael@0: return false; michael@0: michael@0: SymSetOptions(SYMOPT_LOAD_LINES | SYMOPT_UNDNAME); michael@0: retStat = SymInitialize(GetCurrentProcess(), nullptr, TRUE); michael@0: if (!retStat) michael@0: PrintError("SymInitialize"); michael@0: michael@0: gInitialized = retStat; michael@0: /* XXX At some point we need to arrange to call SymCleanup */ michael@0: michael@0: return retStat; michael@0: } michael@0: michael@0: michael@0: EXPORT_XPCOM_API(nsresult) michael@0: NS_DescribeCodeAddress(void *aPC, nsCodeAddressDetails *aDetails) michael@0: { michael@0: aDetails->library[0] = '\0'; michael@0: aDetails->loffset = 0; michael@0: aDetails->filename[0] = '\0'; michael@0: aDetails->lineno = 0; michael@0: aDetails->function[0] = '\0'; michael@0: aDetails->foffset = 0; michael@0: michael@0: if (!EnsureSymInitialized()) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: HANDLE myProcess = ::GetCurrentProcess(); michael@0: BOOL ok; michael@0: michael@0: // debug routines are not threadsafe, so grab the lock. michael@0: EnterCriticalSection(&gDbgHelpCS); michael@0: michael@0: // michael@0: // Attempt to load module info before we attempt to resolve the symbol. michael@0: // This just makes sure we get good info if available. michael@0: // michael@0: michael@0: DWORD64 addr = (DWORD64)aPC; michael@0: IMAGEHLP_MODULE64 modInfo; michael@0: IMAGEHLP_LINE64 lineInfo; michael@0: BOOL modInfoRes; michael@0: modInfoRes = SymGetModuleInfoEspecial64(myProcess, addr, &modInfo, &lineInfo); michael@0: michael@0: if (modInfoRes) { michael@0: PL_strncpyz(aDetails->library, modInfo.ModuleName, michael@0: sizeof(aDetails->library)); michael@0: aDetails->loffset = (char*) aPC - (char*) modInfo.BaseOfImage; michael@0: michael@0: if (lineInfo.FileName) { michael@0: PL_strncpyz(aDetails->filename, lineInfo.FileName, michael@0: sizeof(aDetails->filename)); michael@0: aDetails->lineno = lineInfo.LineNumber; michael@0: } michael@0: } michael@0: michael@0: ULONG64 buffer[(sizeof(SYMBOL_INFO) + michael@0: MAX_SYM_NAME*sizeof(TCHAR) + sizeof(ULONG64) - 1) / sizeof(ULONG64)]; michael@0: PSYMBOL_INFO pSymbol = (PSYMBOL_INFO)buffer; michael@0: pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO); michael@0: pSymbol->MaxNameLen = MAX_SYM_NAME; michael@0: michael@0: DWORD64 displacement; michael@0: ok = SymFromAddr(myProcess, addr, &displacement, pSymbol); michael@0: michael@0: if (ok) { michael@0: PL_strncpyz(aDetails->function, pSymbol->Name, michael@0: sizeof(aDetails->function)); michael@0: aDetails->foffset = static_cast(displacement); michael@0: } michael@0: michael@0: LeaveCriticalSection(&gDbgHelpCS); // release our lock michael@0: return NS_OK; michael@0: } michael@0: michael@0: EXPORT_XPCOM_API(nsresult) michael@0: NS_FormatCodeAddressDetails(void *aPC, const nsCodeAddressDetails *aDetails, michael@0: char *aBuffer, uint32_t aBufferSize) michael@0: { michael@0: if (aDetails->function[0]) { michael@0: _snprintf(aBuffer, aBufferSize, "%s+0x%08lX [%s +0x%016lX]", michael@0: aDetails->function, aDetails->foffset, michael@0: aDetails->library, aDetails->loffset); michael@0: } else if (aDetails->library[0]) { michael@0: _snprintf(aBuffer, aBufferSize, "UNKNOWN [%s +0x%016lX]", michael@0: aDetails->library, aDetails->loffset); michael@0: } else { michael@0: _snprintf(aBuffer, aBufferSize, "UNKNOWN 0x%016lX", aPC); michael@0: } michael@0: michael@0: aBuffer[aBufferSize - 1] = '\0'; michael@0: michael@0: uint32_t len = strlen(aBuffer); michael@0: if (aDetails->filename[0]) { michael@0: _snprintf(aBuffer + len, aBufferSize - len, " (%s, line %d)\n", michael@0: aDetails->filename, aDetails->lineno); michael@0: } else { michael@0: aBuffer[len] = '\n'; michael@0: if (++len != aBufferSize) michael@0: aBuffer[len] = '\0'; michael@0: } michael@0: aBuffer[aBufferSize - 2] = '\n'; michael@0: aBuffer[aBufferSize - 1] = '\0'; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // WIN32 x86 stack walking code michael@0: // i386 or PPC Linux stackwalking code or Solaris michael@0: #elif HAVE_DLADDR && (HAVE__UNWIND_BACKTRACE || NSSTACKWALK_SUPPORTS_LINUX || NSSTACKWALK_SUPPORTS_SOLARIS || NSSTACKWALK_SUPPORTS_MACOSX) michael@0: michael@0: #include michael@0: #include michael@0: #include "nscore.h" michael@0: #include michael@0: #include "plstr.h" michael@0: michael@0: // On glibc 2.1, the Dl_info api defined in is only exposed michael@0: // if __USE_GNU is defined. I suppose its some kind of standards michael@0: // adherence thing. michael@0: // michael@0: #if (__GLIBC_MINOR__ >= 1) && !defined(__USE_GNU) michael@0: #define __USE_GNU michael@0: #endif michael@0: michael@0: // This thing is exported by libstdc++ michael@0: // Yes, this is a gcc only hack michael@0: #if defined(MOZ_DEMANGLE_SYMBOLS) michael@0: #include michael@0: #endif // MOZ_DEMANGLE_SYMBOLS michael@0: michael@0: void DemangleSymbol(const char * aSymbol, michael@0: char * aBuffer, michael@0: int aBufLen) michael@0: { michael@0: aBuffer[0] = '\0'; michael@0: michael@0: #if defined(MOZ_DEMANGLE_SYMBOLS) michael@0: /* See demangle.h in the gcc source for the voodoo */ michael@0: char * demangled = abi::__cxa_demangle(aSymbol,0,0,0); michael@0: michael@0: if (demangled) michael@0: { michael@0: PL_strncpyz(aBuffer,demangled,aBufLen); michael@0: free(demangled); michael@0: } michael@0: #endif // MOZ_DEMANGLE_SYMBOLS michael@0: } michael@0: michael@0: michael@0: #if NSSTACKWALK_SUPPORTS_SOLARIS michael@0: michael@0: /* michael@0: * Stack walking code for Solaris courtesy of Bart Smaalder's "memtrak". michael@0: */ michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: static int load_address ( void * pc, void * arg ); michael@0: static struct bucket * newbucket ( void * pc ); michael@0: static struct frame * cs_getmyframeptr ( void ); michael@0: static void cs_walk_stack ( void * (*read_func)(char * address), michael@0: struct frame * fp, michael@0: int (*operate_func)(void *, void *, void *), michael@0: void * usrarg ); michael@0: static void cs_operate ( void (*operate_func)(void *, void *, void *), michael@0: void * usrarg ); michael@0: michael@0: #ifndef STACK_BIAS michael@0: #define STACK_BIAS 0 michael@0: #endif /*STACK_BIAS*/ michael@0: michael@0: #define LOGSIZE 4096 michael@0: michael@0: /* type of demangling function */ michael@0: typedef int demf_t(const char *, char *, size_t); michael@0: michael@0: static demf_t *demf; michael@0: michael@0: static int initialized = 0; michael@0: michael@0: #if defined(sparc) || defined(__sparc) michael@0: #define FRAME_PTR_REGISTER REG_SP michael@0: #endif michael@0: michael@0: #if defined(i386) || defined(__i386) michael@0: #define FRAME_PTR_REGISTER EBP michael@0: #endif michael@0: michael@0: struct bucket { michael@0: void * pc; michael@0: int index; michael@0: struct bucket * next; michael@0: }; michael@0: michael@0: struct my_user_args { michael@0: NS_WalkStackCallback callback; michael@0: uint32_t skipFrames; michael@0: uint32_t maxFrames; michael@0: uint32_t numFrames; michael@0: void *closure; michael@0: }; michael@0: michael@0: michael@0: static void myinit(); michael@0: michael@0: #pragma init (myinit) michael@0: michael@0: static void michael@0: myinit() michael@0: { michael@0: michael@0: if (! initialized) { michael@0: #ifndef __GNUC__ michael@0: void *handle; michael@0: const char *libdem = "libdemangle.so.1"; michael@0: michael@0: /* load libdemangle if we can and need to (only try this once) */ michael@0: if ((handle = dlopen(libdem, RTLD_LAZY)) != nullptr) { michael@0: demf = (demf_t *)dlsym(handle, michael@0: "cplus_demangle"); /*lint !e611 */ michael@0: /* michael@0: * lint override above is to prevent lint from michael@0: * complaining about "suspicious cast". michael@0: */ michael@0: } michael@0: #endif /*__GNUC__*/ michael@0: } michael@0: initialized = 1; michael@0: } michael@0: michael@0: michael@0: static int michael@0: load_address(void * pc, void * arg) michael@0: { michael@0: static struct bucket table[2048]; michael@0: static mutex_t lock; michael@0: struct bucket * ptr; michael@0: struct my_user_args * args = (struct my_user_args *) arg; michael@0: michael@0: unsigned int val = NS_PTR_TO_INT32(pc); michael@0: michael@0: ptr = table + ((val >> 2)&2047); michael@0: michael@0: mutex_lock(&lock); michael@0: while (ptr->next) { michael@0: if (ptr->next->pc == pc) michael@0: break; michael@0: ptr = ptr->next; michael@0: } michael@0: michael@0: int stop = 0; michael@0: if (ptr->next) { michael@0: mutex_unlock(&lock); michael@0: } else { michael@0: (args->callback)(pc, args->closure); michael@0: args->numFrames++; michael@0: if (args->maxFrames != 0 && args->numFrames == args->maxFrames) michael@0: stop = 1; // causes us to stop getting frames michael@0: michael@0: ptr->next = newbucket(pc); michael@0: mutex_unlock(&lock); michael@0: } michael@0: return stop; michael@0: } michael@0: michael@0: michael@0: static struct bucket * michael@0: newbucket(void * pc) michael@0: { michael@0: struct bucket * ptr = (struct bucket *) malloc(sizeof (*ptr)); michael@0: static int index; /* protected by lock in caller */ michael@0: michael@0: ptr->index = index++; michael@0: ptr->next = nullptr; michael@0: ptr->pc = pc; michael@0: return (ptr); michael@0: } michael@0: michael@0: michael@0: static struct frame * michael@0: csgetframeptr() michael@0: { michael@0: ucontext_t u; michael@0: struct frame *fp; michael@0: michael@0: (void) getcontext(&u); michael@0: michael@0: fp = (struct frame *) michael@0: ((char *)u.uc_mcontext.gregs[FRAME_PTR_REGISTER] + michael@0: STACK_BIAS); michael@0: michael@0: /* make sure to return parents frame pointer.... */ michael@0: michael@0: return ((struct frame *)((ulong_t)fp->fr_savfp + STACK_BIAS)); michael@0: } michael@0: michael@0: michael@0: static void michael@0: cswalkstack(struct frame *fp, int (*operate_func)(void *, void *, void *), michael@0: void *usrarg) michael@0: { michael@0: michael@0: while (fp != 0 && fp->fr_savpc != 0) { michael@0: michael@0: if (operate_func((void *)fp->fr_savpc, nullptr, usrarg) != 0) michael@0: break; michael@0: /* michael@0: * watch out - libthread stacks look funny at the top michael@0: * so they may not have their STACK_BIAS set michael@0: */ michael@0: michael@0: fp = (struct frame *)((ulong_t)fp->fr_savfp + michael@0: (fp->fr_savfp?(ulong_t)STACK_BIAS:0)); michael@0: } michael@0: } michael@0: michael@0: michael@0: static void michael@0: cs_operate(int (*operate_func)(void *, void *, void *), void * usrarg) michael@0: { michael@0: cswalkstack(csgetframeptr(), operate_func, usrarg); michael@0: } michael@0: michael@0: EXPORT_XPCOM_API(nsresult) michael@0: NS_StackWalk(NS_WalkStackCallback aCallback, uint32_t aSkipFrames, michael@0: uint32_t aMaxFrames, void *aClosure, uintptr_t aThread, michael@0: void *aPlatformData) michael@0: { michael@0: MOZ_ASSERT(!aThread); michael@0: MOZ_ASSERT(!aPlatformData); michael@0: struct my_user_args args; michael@0: michael@0: StackWalkInitCriticalAddress(); michael@0: michael@0: if (!initialized) michael@0: myinit(); michael@0: michael@0: args.callback = aCallback; michael@0: args.skipFrames = aSkipFrames; /* XXX Not handled! */ michael@0: args.maxFrames = aMaxFrames; michael@0: args.numFrames = 0; michael@0: args.closure = aClosure; michael@0: cs_operate(load_address, &args); michael@0: return args.numFrames == 0 ? NS_ERROR_FAILURE : NS_OK; michael@0: } michael@0: michael@0: EXPORT_XPCOM_API(nsresult) michael@0: NS_DescribeCodeAddress(void *aPC, nsCodeAddressDetails *aDetails) michael@0: { michael@0: aDetails->library[0] = '\0'; michael@0: aDetails->loffset = 0; michael@0: aDetails->filename[0] = '\0'; michael@0: aDetails->lineno = 0; michael@0: aDetails->function[0] = '\0'; michael@0: aDetails->foffset = 0; michael@0: michael@0: char dembuff[4096]; michael@0: Dl_info info; michael@0: michael@0: if (dladdr(aPC, & info)) { michael@0: if (info.dli_fname) { michael@0: PL_strncpyz(aDetails->library, info.dli_fname, michael@0: sizeof(aDetails->library)); michael@0: aDetails->loffset = (char*)aPC - (char*)info.dli_fbase; michael@0: } michael@0: if (info.dli_sname) { michael@0: aDetails->foffset = (char*)aPC - (char*)info.dli_saddr; michael@0: #ifdef __GNUC__ michael@0: DemangleSymbol(info.dli_sname, dembuff, sizeof(dembuff)); michael@0: #else michael@0: if (!demf || demf(info.dli_sname, dembuff, sizeof (dembuff))) michael@0: dembuff[0] = 0; michael@0: #endif /*__GNUC__*/ michael@0: PL_strncpyz(aDetails->function, michael@0: (dembuff[0] != '\0') ? dembuff : info.dli_sname, michael@0: sizeof(aDetails->function)); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: EXPORT_XPCOM_API(nsresult) michael@0: NS_FormatCodeAddressDetails(void *aPC, const nsCodeAddressDetails *aDetails, michael@0: char *aBuffer, uint32_t aBufferSize) michael@0: { michael@0: snprintf(aBuffer, aBufferSize, "%p %s:%s+0x%lx\n", michael@0: aPC, michael@0: aDetails->library[0] ? aDetails->library : "??", michael@0: aDetails->function[0] ? aDetails->function : "??", michael@0: aDetails->foffset); michael@0: return NS_OK; michael@0: } michael@0: michael@0: #else // not __sun-specific michael@0: michael@0: #if __GLIBC__ > 2 || __GLIBC_MINOR > 1 michael@0: #define HAVE___LIBC_STACK_END 1 michael@0: #else michael@0: #define HAVE___LIBC_STACK_END 0 michael@0: #endif michael@0: michael@0: #if HAVE___LIBC_STACK_END michael@0: extern void *__libc_stack_end; // from ld-linux.so michael@0: #endif michael@0: namespace mozilla { michael@0: nsresult michael@0: FramePointerStackWalk(NS_WalkStackCallback aCallback, uint32_t aSkipFrames, michael@0: uint32_t aMaxFrames, void *aClosure, void **bp, michael@0: void *aStackEnd) michael@0: { michael@0: // Stack walking code courtesy Kipp's "leaky". michael@0: michael@0: int32_t skip = aSkipFrames; michael@0: uint32_t numFrames = 0; michael@0: while (1) { michael@0: void **next = (void**)*bp; michael@0: // bp may not be a frame pointer on i386 if code was compiled with michael@0: // -fomit-frame-pointer, so do some sanity checks. michael@0: // (bp should be a frame pointer on ppc(64) but checking anyway may help michael@0: // a little if the stack has been corrupted.) michael@0: // We don't need to check against the begining of the stack because michael@0: // we can assume that bp > sp michael@0: if (next <= bp || michael@0: next > aStackEnd || michael@0: (long(next) & 3)) { michael@0: break; michael@0: } michael@0: #if (defined(__ppc__) && defined(XP_MACOSX)) || defined(__powerpc64__) michael@0: // ppc mac or powerpc64 linux michael@0: void *pc = *(bp+2); michael@0: bp += 3; michael@0: #else // i386 or powerpc32 linux michael@0: void *pc = *(bp+1); michael@0: bp += 2; michael@0: #endif michael@0: if (IsCriticalAddress(pc)) { michael@0: printf("Aborting stack trace, PC is critical\n"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: if (--skip < 0) { michael@0: // Assume that the SP points to the BP of the function michael@0: // it called. We can't know the exact location of the SP michael@0: // but this should be sufficient for our use the SP michael@0: // to order elements on the stack. michael@0: (*aCallback)(pc, bp, aClosure); michael@0: numFrames++; michael@0: if (aMaxFrames != 0 && numFrames == aMaxFrames) michael@0: break; michael@0: } michael@0: bp = next; michael@0: } michael@0: return numFrames == 0 ? NS_ERROR_FAILURE : NS_OK; michael@0: } michael@0: michael@0: } michael@0: michael@0: #define X86_OR_PPC (defined(__i386) || defined(PPC) || defined(__ppc__)) michael@0: #if X86_OR_PPC && (NSSTACKWALK_SUPPORTS_MACOSX || NSSTACKWALK_SUPPORTS_LINUX) // i386 or PPC Linux or Mac stackwalking code michael@0: michael@0: EXPORT_XPCOM_API(nsresult) michael@0: NS_StackWalk(NS_WalkStackCallback aCallback, uint32_t aSkipFrames, michael@0: uint32_t aMaxFrames, void *aClosure, uintptr_t aThread, michael@0: void *aPlatformData) michael@0: { michael@0: MOZ_ASSERT(!aThread); michael@0: MOZ_ASSERT(!aPlatformData); michael@0: StackWalkInitCriticalAddress(); michael@0: michael@0: // Get the frame pointer michael@0: void **bp; michael@0: #if defined(__i386) michael@0: __asm__( "movl %%ebp, %0" : "=g"(bp)); michael@0: #else michael@0: // It would be nice if this worked uniformly, but at least on i386 and michael@0: // x86_64, it stopped working with gcc 4.1, because it points to the michael@0: // end of the saved registers instead of the start. michael@0: bp = (void**) __builtin_frame_address(0); michael@0: #endif michael@0: michael@0: void *stackEnd; michael@0: #if HAVE___LIBC_STACK_END michael@0: stackEnd = __libc_stack_end; michael@0: #else michael@0: stackEnd = reinterpret_cast(-1); michael@0: #endif michael@0: return FramePointerStackWalk(aCallback, aSkipFrames, aMaxFrames, michael@0: aClosure, bp, stackEnd); michael@0: michael@0: } michael@0: michael@0: #elif defined(HAVE__UNWIND_BACKTRACE) michael@0: michael@0: // libgcc_s.so symbols _Unwind_Backtrace@@GCC_3.3 and _Unwind_GetIP@@GCC_3.0 michael@0: #include michael@0: michael@0: struct unwind_info { michael@0: NS_WalkStackCallback callback; michael@0: int skip; michael@0: int maxFrames; michael@0: int numFrames; michael@0: bool isCriticalAbort; michael@0: void *closure; michael@0: }; michael@0: michael@0: static _Unwind_Reason_Code michael@0: unwind_callback (struct _Unwind_Context *context, void *closure) michael@0: { michael@0: unwind_info *info = static_cast(closure); michael@0: void *pc = reinterpret_cast(_Unwind_GetIP(context)); michael@0: // TODO Use something like '_Unwind_GetGR()' to get the stack pointer. michael@0: if (IsCriticalAddress(pc)) { michael@0: printf("Aborting stack trace, PC is critical\n"); michael@0: info->isCriticalAbort = true; michael@0: // We just want to stop the walk, so any error code will do. Using michael@0: // _URC_NORMAL_STOP would probably be the most accurate, but it is not michael@0: // defined on Android for ARM. michael@0: return _URC_FOREIGN_EXCEPTION_CAUGHT; michael@0: } michael@0: if (--info->skip < 0) { michael@0: (*info->callback)(pc, nullptr, info->closure); michael@0: info->numFrames++; michael@0: if (info->maxFrames != 0 && info->numFrames == info->maxFrames) { michael@0: // Again, any error code that stops the walk will do. michael@0: return _URC_FOREIGN_EXCEPTION_CAUGHT; michael@0: } michael@0: } michael@0: return _URC_NO_REASON; michael@0: } michael@0: michael@0: EXPORT_XPCOM_API(nsresult) michael@0: NS_StackWalk(NS_WalkStackCallback aCallback, uint32_t aSkipFrames, michael@0: uint32_t aMaxFrames, void *aClosure, uintptr_t aThread, michael@0: void *aPlatformData) michael@0: { michael@0: MOZ_ASSERT(!aThread); michael@0: MOZ_ASSERT(!aPlatformData); michael@0: StackWalkInitCriticalAddress(); michael@0: unwind_info info; michael@0: info.callback = aCallback; michael@0: info.skip = aSkipFrames + 1; michael@0: info.maxFrames = aMaxFrames; michael@0: info.numFrames = 0; michael@0: info.isCriticalAbort = false; michael@0: info.closure = aClosure; michael@0: michael@0: (void)_Unwind_Backtrace(unwind_callback, &info); michael@0: michael@0: // We ignore the return value from _Unwind_Backtrace and instead determine michael@0: // the outcome from |info|. There are two main reasons for this: michael@0: // - On ARM/Android bionic's _Unwind_Backtrace usually (always?) returns michael@0: // _URC_FAILURE. See michael@0: // https://bugzilla.mozilla.org/show_bug.cgi?id=717853#c110. michael@0: // - If aMaxFrames != 0, we want to stop early, and the only way to do that michael@0: // is to make unwind_callback return something other than _URC_NO_REASON, michael@0: // which causes _Unwind_Backtrace to return a non-success code. michael@0: if (info.isCriticalAbort) michael@0: return NS_ERROR_UNEXPECTED; michael@0: return info.numFrames == 0 ? NS_ERROR_FAILURE : NS_OK; michael@0: } michael@0: michael@0: #endif michael@0: michael@0: EXPORT_XPCOM_API(nsresult) michael@0: NS_DescribeCodeAddress(void *aPC, nsCodeAddressDetails *aDetails) michael@0: { michael@0: aDetails->library[0] = '\0'; michael@0: aDetails->loffset = 0; michael@0: aDetails->filename[0] = '\0'; michael@0: aDetails->lineno = 0; michael@0: aDetails->function[0] = '\0'; michael@0: aDetails->foffset = 0; michael@0: michael@0: Dl_info info; michael@0: int ok = dladdr(aPC, &info); michael@0: if (!ok) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: PL_strncpyz(aDetails->library, info.dli_fname, sizeof(aDetails->library)); michael@0: aDetails->loffset = (char*)aPC - (char*)info.dli_fbase; michael@0: michael@0: const char * symbol = info.dli_sname; michael@0: if (!symbol || symbol[0] == '\0') { michael@0: return NS_OK; michael@0: } michael@0: michael@0: DemangleSymbol(symbol, aDetails->function, sizeof(aDetails->function)); michael@0: michael@0: if (aDetails->function[0] == '\0') { michael@0: // Just use the mangled symbol if demangling failed. michael@0: PL_strncpyz(aDetails->function, symbol, sizeof(aDetails->function)); michael@0: } michael@0: michael@0: aDetails->foffset = (char*)aPC - (char*)info.dli_saddr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: EXPORT_XPCOM_API(nsresult) michael@0: NS_FormatCodeAddressDetails(void *aPC, const nsCodeAddressDetails *aDetails, michael@0: char *aBuffer, uint32_t aBufferSize) michael@0: { michael@0: if (!aDetails->library[0]) { michael@0: snprintf(aBuffer, aBufferSize, "UNKNOWN %p\n", aPC); michael@0: } else if (!aDetails->function[0]) { michael@0: snprintf(aBuffer, aBufferSize, "UNKNOWN [%s +0x%08" PRIXPTR "]\n", michael@0: aDetails->library, aDetails->loffset); michael@0: } else { michael@0: snprintf(aBuffer, aBufferSize, "%s+0x%08" PRIXPTR michael@0: " [%s +0x%08" PRIXPTR "]\n", michael@0: aDetails->function, aDetails->foffset, michael@0: aDetails->library, aDetails->loffset); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: #endif michael@0: michael@0: #else // unsupported platform. michael@0: michael@0: EXPORT_XPCOM_API(nsresult) michael@0: NS_StackWalk(NS_WalkStackCallback aCallback, uint32_t aSkipFrames, michael@0: uint32_t aMaxFrames, void *aClosure, uintptr_t aThread, michael@0: void *aPlatformData) michael@0: { michael@0: MOZ_ASSERT(!aThread); michael@0: MOZ_ASSERT(!aPlatformData); michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: namespace mozilla { michael@0: nsresult michael@0: FramePointerStackWalk(NS_WalkStackCallback aCallback, uint32_t aSkipFrames, michael@0: void *aClosure, void **bp) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: } michael@0: michael@0: EXPORT_XPCOM_API(nsresult) michael@0: NS_DescribeCodeAddress(void *aPC, nsCodeAddressDetails *aDetails) michael@0: { michael@0: aDetails->library[0] = '\0'; michael@0: aDetails->loffset = 0; michael@0: aDetails->filename[0] = '\0'; michael@0: aDetails->lineno = 0; michael@0: aDetails->function[0] = '\0'; michael@0: aDetails->foffset = 0; michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: EXPORT_XPCOM_API(nsresult) michael@0: NS_FormatCodeAddressDetails(void *aPC, const nsCodeAddressDetails *aDetails, michael@0: char *aBuffer, uint32_t aBufferSize) michael@0: { michael@0: aBuffer[0] = '\0'; michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: #endif