diff --git a/tools/trace-malloc/lib/nsTraceMalloc.c b/tools/trace-malloc/lib/nsTraceMalloc.c index 8f47110222ac..72098ace3d36 100644 --- a/tools/trace-malloc/lib/nsTraceMalloc.c +++ b/tools/trace-malloc/lib/nsTraceMalloc.c @@ -957,7 +957,7 @@ backtrace(tm_thread *t, int skip, int *immediate_abort) t->suppress_tracing++; if (!stacks_enabled) { -#if defined(XP_MACOSX) && defined(__i386) +#if defined(XP_MACOSX) /* Walk the stack, even if stacks_enabled is false. We do this to check if we must set immediate_abort. */ info->entries = 0; diff --git a/xpcom/base/nsStackWalk.cpp b/xpcom/base/nsStackWalk.cpp index 6512336b9262..0cbd5ff09e4b 100644 --- a/xpcom/base/nsStackWalk.cpp +++ b/xpcom/base/nsStackWalk.cpp @@ -41,9 +41,147 @@ /* API for getting a stack trace of the C/C++ stack on the current thread */ #include "mozilla/Util.h" +#include "nsDebug.h" +#include "nsStackWalkPrivate.h" #include "nsStackWalk.h" +// The presence of this address is the stack must stop the stack walk. If +// there is no such address, the structure will be {NULL, true}. +struct CriticalAddress { + void* mAddr; + bool mInit; +}; +static CriticalAddress gCriticalAddress; + +#if defined(HAVE_DLOPEN) || defined(XP_MACOSX) +#include +#endif + +#ifdef XP_MACOSX +#include +#include +#include + +typedef void +malloc_logger_t(uint32_t type, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3, + uintptr_t result, uint32_t num_hot_frames_to_skip); +extern malloc_logger_t *malloc_logger; + +static void +stack_callback(void *pc, void *closure) +{ + const char *name = reinterpret_cast(closure); + 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(pc, &info) == 0 || + info.dli_sname == NULL || strcmp(info.dli_sname, name) != 0) + return; + gCriticalAddress.mAddr = pc; +} + +#define MAC_OS_X_VERSION_10_7_HEX 0x00001070 +#define MAC_OS_X_VERSION_10_6_HEX 0x00001060 + +static PRInt32 OSXVersion() +{ + static PRInt32 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); +} + +static bool OnSnowLeopardOrLater() +{ + return (OSXVersion() >= MAC_OS_X_VERSION_10_6_HEX); +} + +static void +my_malloc_logger(uint32_t type, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3, + uintptr_t result, uint32_t num_hot_frames_to_skip) +{ + 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 = OnSnowLeopardOrLater() ? "new_sem_from_pool" : + "pthread_cond_wait$UNIX2003"; + NS_StackWalk(stack_callback, 0, const_cast(name)); +} + +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. + + MOZ_ASSERT(malloc_logger == NULL); + 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); + malloc_logger = NULL; + + // 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. + MOZ_ASSERT(OnLionOrLater() || gCriticalAddress.mAddr != NULL); + 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. +void +StackWalkInitCriticalAddress() +{ + gCriticalAddress.mInit = true; +} +#endif + #if defined(_WIN32) && (defined(_M_IX86) || defined(_M_AMD64) || defined(_M_IA64)) // WIN32 x86 stack walking code #include "nscore.h" @@ -655,6 +793,7 @@ EXPORT_XPCOM_API(nsresult) NS_StackWalk(NS_WalkStackCallback aCallback, PRUint32 aSkipFrames, void *aClosure) { + MOZ_ASSERT(gCriticalAddress.mInit); HANDLE myProcess, myThread; DWORD walkerReturn; struct WalkStackData data; @@ -1140,12 +1279,6 @@ NS_FormatCodeAddressDetails(void *aPC, const nsCodeAddressDetails *aDetails, #define __USE_GNU #endif -#if defined(HAVE_DLOPEN) || defined(XP_MACOSX) -#include -#endif - - - // This thing is exported by libstdc++ // Yes, this is a gcc only hack #if defined(MOZ_DEMANGLE_SYMBOLS) @@ -1348,6 +1481,7 @@ EXPORT_XPCOM_API(nsresult) NS_StackWalk(NS_WalkStackCallback aCallback, PRUint32 aSkipFrames, void *aClosure) { + MOZ_ASSERT(gCriticalAddress.mInit); struct my_user_args args; if (!initialized) @@ -1422,58 +1556,12 @@ NS_FormatCodeAddressDetails(void *aPC, const nsCodeAddressDetails *aDetails, extern void *__libc_stack_end; // from ld-linux.so #endif -#ifdef XP_MACOSX -struct AddressRange { - void* mStart; - void* mEnd; -}; -// Addresses in this range must stop the stack walk -static AddressRange gCriticalRange; - -static void FindFunctionAddresses(const char* aName, AddressRange* aRange) -{ - aRange->mStart = dlsym(RTLD_DEFAULT, aName); - if (!aRange->mStart) - return; - aRange->mEnd = aRange->mStart; - while (true) { - Dl_info info; - if (!dladdr(aRange->mEnd, &info)) - break; - if (strcmp(info.dli_sname, aName)) - break; - aRange->mEnd = (char*)aRange->mEnd + 1; - } -} - -static void InitCriticalRanges() -{ - if (gCriticalRange.mStart) - return; - // 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 dladdr but its - // code is bundled with pthread_cond_wait$UNIX2003 (on - // Leopard anyway). - FindFunctionAddresses("pthread_cond_wait$UNIX2003", &gCriticalRange); -} - -static bool InCriticalRange(void* aPC) -{ - return gCriticalRange.mStart && - gCriticalRange.mStart <= aPC && aPC < gCriticalRange.mEnd; -} -#else -static void InitCriticalRanges() {} -static bool InCriticalRange(void* aPC) { return false; } -#endif - EXPORT_XPCOM_API(nsresult) NS_StackWalk(NS_WalkStackCallback aCallback, PRUint32 aSkipFrames, void *aClosure) { + MOZ_ASSERT(gCriticalAddress.mInit); // Stack walking code courtesy Kipp's "leaky". - InitCriticalRanges(); // Get the frame pointer void **bp; @@ -1506,8 +1594,8 @@ NS_StackWalk(NS_WalkStackCallback aCallback, PRUint32 aSkipFrames, #else // i386 or powerpc32 linux void *pc = *(bp+1); #endif - if (InCriticalRange(pc)) { - printf("Aborting stack trace, PC in critical range\n"); + if (IsCriticalAddress(pc)) { + printf("Aborting stack trace, PC is critical\n"); return NS_ERROR_UNEXPECTED; } if (--skip < 0) { @@ -1533,10 +1621,16 @@ static _Unwind_Reason_Code unwind_callback (struct _Unwind_Context *context, void *closure) { unwind_info *info = static_cast(closure); - if (--info->skip < 0) { - void *pc = reinterpret_cast(_Unwind_GetIP(context)); - (*info->callback)(pc, info->closure); + void *pc = reinterpret_cast(_Unwind_GetIP(context)); + if (IsCriticalAddress(pc)) { + printf("Aborting stack trace, PC is critical\n"); + /* 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->callback)(pc, info->closure); return _URC_NO_REASON; } @@ -1544,13 +1638,15 @@ EXPORT_XPCOM_API(nsresult) NS_StackWalk(NS_WalkStackCallback aCallback, PRUint32 aSkipFrames, void *aClosure) { + MOZ_ASSERT(gCriticalAddress.mInit); unwind_info info; info.callback = aCallback; info.skip = aSkipFrames + 1; info.closure = aClosure; - _Unwind_Backtrace(unwind_callback, &info); - + _Unwind_Reason_Code t = _Unwind_Backtrace(unwind_callback, &info); + if (t != _URC_END_OF_STACK) + return NS_ERROR_UNEXPECTED; return NS_OK; } @@ -1620,6 +1716,7 @@ EXPORT_XPCOM_API(nsresult) NS_StackWalk(NS_WalkStackCallback aCallback, PRUint32 aSkipFrames, void *aClosure) { + MOZ_ASSERT(gCriticalAddress.mInit); return NS_ERROR_NOT_IMPLEMENTED; } diff --git a/xpcom/base/nsStackWalkPrivate.h b/xpcom/base/nsStackWalkPrivate.h new file mode 100644 index 000000000000..7c8f11f61e90 --- /dev/null +++ b/xpcom/base/nsStackWalkPrivate.h @@ -0,0 +1,43 @@ +/* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is NS_WalkTheStack. + * + * The Initial Developer of the Original Code is the Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2007 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Mozilla Corporation (original author) + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/** + * Initialize the critical sections for this platform so that we can + * abort stack walks when needed. + */ +void +StackWalkInitCriticalAddress(void); diff --git a/xpcom/base/nsTraceRefcntImpl.cpp b/xpcom/base/nsTraceRefcntImpl.cpp index a3f47a5a4d1b..82932412c2ec 100644 --- a/xpcom/base/nsTraceRefcntImpl.cpp +++ b/xpcom/base/nsTraceRefcntImpl.cpp @@ -50,6 +50,7 @@ #include "nsCOMPtr.h" #include "nsCRT.h" #include +#include "nsStackWalkPrivate.h" #include "nsStackWalk.h" #include "nsString.h" @@ -923,6 +924,8 @@ nsTraceRefcntImpl::DemangleSymbol(const char * aSymbol, EXPORT_XPCOM_API(void) NS_LogInit() { + // FIXME: This is called multiple times, we should probably not allow that. + StackWalkInitCriticalAddress(); #ifdef NS_IMPL_REFCNT_LOGGING if (++gInitCount) nsTraceRefcntImpl::SetActivityIsLegal(true);