Bug 696376 - Change how we find critical ranges so that it works on 10.6 too. r=dbaron.

Currently we use dlsym on pthread_cond_wait$UNIX2003 to find a
function that indicates that new_sem_from_pool is on the stack. This
works on 10.5, but on 10.6 I could not find a single reliable
indicator that would work with dlsym.

The good news is that dladdr works with any symbol, not just exported
ones. To find the address of new_sem_from_pool, we set up a malloc logger
and force a call to new_sem_from_pool. From the logger callback we walk
the stack trying dladdr on every address.

To force a call to new_sem_from_pool, the initialization code has to be the
first to use semaphores, so it is now run from NS_LogInit.

This works on 10.6 and 10.5 (but we have to look for
"pthread_cond_wait$UNIX2003"). In 10.7 the call to malloc is gone, so we don't
have to worry about critical addresses on it anymore.

--HG--
extra : rebase_source : bba4ac9e3378c88f7037aa884511e473a57121f6
This commit is contained in:
Rafael Ávila de Espíndola 2011-12-02 19:26:04 -05:00
Родитель 8a9208c6c6
Коммит 001dce7e90
4 изменённых файлов: 204 добавлений и 61 удалений

Просмотреть файл

@ -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;

Просмотреть файл

@ -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 <dlfcn.h>
#endif
#ifdef XP_MACOSX
#include <pthread.h>
#include <errno.h>
#include <CoreServices/CoreServices.h>
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<char *>(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<char*>(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 <dlfcn.h>
#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<unwind_info *>(closure);
if (--info->skip < 0) {
void *pc = reinterpret_cast<void *>(_Unwind_GetIP(context));
(*info->callback)(pc, info->closure);
void *pc = reinterpret_cast<void *>(_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;
}

Просмотреть файл

@ -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);

Просмотреть файл

@ -50,6 +50,7 @@
#include "nsCOMPtr.h"
#include "nsCRT.h"
#include <math.h>
#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);