Bug 818793 - Add a |aMaxFrames| parameter to NS_StackWalk. r=jlebar,glandium; sr=dbaron.

This commit is contained in:
Nicholas Nethercote 2012-12-20 21:31:57 -08:00
parent b6f32fe10b
commit 0ea79c11c5
10 changed files with 145 additions and 74 deletions

View File

@ -707,10 +707,10 @@ public:
class StackTrace
{
static const uint32_t MaxDepth = 24;
static const uint32_t MaxFrames = 24;
uint32_t mLength; // The number of PCs.
void* mPcs[MaxDepth]; // The PCs themselves.
void* mPcs[MaxFrames]; // The PCs themselves.
public:
StackTrace() : mLength(0) {}
@ -751,13 +751,9 @@ private:
static void StackWalkCallback(void* aPc, void* aSp, void* aClosure)
{
StackTrace* st = (StackTrace*) aClosure;
// Only fill to MaxDepth.
// XXX: bug 818793 will allow early bailouts.
if (st->mLength < MaxDepth) {
st->mPcs[st->mLength] = aPc;
st->mLength++;
}
MOZ_ASSERT(st->mLength < MaxFrames);
st->mPcs[st->mLength] = aPc;
st->mLength++;
}
static int QsortCmp(const void* aA, const void* aB)
@ -778,7 +774,7 @@ void
StackTrace::Print(const Writer& aWriter, LocationService* aLocService) const
{
if (mLength == 0) {
W(" (empty)\n");
W(" (empty)\n"); // StackTrace::Get() must have failed
return;
}
@ -800,17 +796,33 @@ StackTrace::Get(Thread* aT)
// https://bugzilla.mozilla.org/show_bug.cgi?id=374829#c8
// On Linux, something similar can happen; see bug 824340.
// So let's just release it on all platforms.
nsresult rv;
StackTrace tmp;
{
AutoUnlockState unlock;
// In normal operation, skip=3 gets us past various malloc wrappers into
// more interesting stuff. But in test mode we need to skip a bit less to
// sufficiently differentiate some similar stacks.
uint32_t skip = 2;
nsresult rv = NS_StackWalk(StackWalkCallback, skip, &tmp, 0, nullptr);
if (NS_FAILED(rv) || tmp.mLength == 0) {
tmp.mLength = 0;
}
uint32_t skipFrames = 2;
rv = NS_StackWalk(StackWalkCallback, skipFrames, MaxFrames, &tmp, 0,
nullptr);
}
if (rv == NS_OK) {
// Handle the common case first. All is ok. Nothing to do.
} else if (rv == NS_ERROR_NOT_IMPLEMENTED || rv == NS_ERROR_FAILURE) {
tmp.mLength = 0;
} else if (rv == NS_ERROR_UNEXPECTED) {
// XXX: This |rv| only happens on Mac, and it indicates that we're handling
// a call to malloc that happened inside a mutex-handling function. Any
// attempt to create a semaphore (which can happen in printf) could
// deadlock.
//
// However, the most complex thing DMD does after Get() returns is to put
// something in a hash table, which might call
// InfallibleAllocPolicy::malloc_. I'm not yet sure if this needs special
// handling, hence the forced abort. Sorry. If you hit this, please file
// a bug and CC nnethercote.
MOZ_CRASH();
} else {
MOZ_CRASH(); // should be impossible
}
StackTraceTable::AddPtr p = gStackTraceTable->lookupForAdd(&tmp);
@ -1670,7 +1682,8 @@ Init(const malloc_table_t* aMallocTable)
// StackWalkInitCriticalAddress() isn't exported from xpcom/, so instead we
// just call NS_StackWalk, because that calls StackWalkInitCriticalAddress().
// See the comment above StackWalkInitCriticalAddress() for more details.
(void)NS_StackWalk(NopStackWalkCallback, 0, nullptr, 0, nullptr);
(void)NS_StackWalk(NopStackWalkCallback, /* skipFrames */ 0,
/* maxFrames */ 1, nullptr, 0, nullptr);
#endif
gStateLock = InfallibleAllocPolicy::new_<Mutex>();

View File

@ -77,7 +77,8 @@ ah_crap_handler(int signum)
signum);
printf("Stack:\n");
NS_StackWalk(PrintStackFrame, 2, nullptr, 0, nullptr);
NS_StackWalk(PrintStackFrame, /* skipFrames */ 2, /* maxFrames */ 0,
nullptr, 0, nullptr);
printf("Sleeping for %d seconds.\n",_gdb_sleep_duration);
printf("Type 'gdb %s %d' to attach your debugger to this thread.\n",

View File

@ -796,10 +796,7 @@ static
void StackWalkCallback(void* aPC, void* aSP, void* aClosure)
{
PCArray* array = static_cast<PCArray*>(aClosure);
if (array->count >= array->size) {
// too many frames, ignore
return;
}
MOZ_ASSERT(array->count < array->size);
array->sp_array[array->count] = aSP;
array->array[array->count] = aPC;
array->count++;
@ -828,6 +825,7 @@ void TableTicker::doBacktrace(ThreadProfile &aProfile, TickSample* aSample)
platformData = aSample->context;
#endif
uint32_t maxFrames = array.size - array.count;
#ifdef XP_MACOSX
pthread_t pt = GetProfiledThread(platform_data());
void *stackEnd = reinterpret_cast<void*>(-1);
@ -835,9 +833,12 @@ void TableTicker::doBacktrace(ThreadProfile &aProfile, TickSample* aSample)
stackEnd = static_cast<char*>(pthread_get_stackaddr_np(pt));
nsresult rv = NS_OK;
if (aSample->fp >= aSample->sp && aSample->fp <= stackEnd)
rv = FramePointerStackWalk(StackWalkCallback, 0, &array, reinterpret_cast<void**>(aSample->fp), stackEnd);
rv = FramePointerStackWalk(StackWalkCallback, /* skipFrames */ 0,
maxFrames, &array,
reinterpret_cast<void**>(aSample->fp), stackEnd);
#else
nsresult rv = NS_StackWalk(StackWalkCallback, 0, &array, thread, platformData);
nsresult rv = NS_StackWalk(StackWalkCallback, /* skipFrames */ 0, maxFrames,
&array, thread, platformData);
#endif
if (NS_SUCCEEDED(rv)) {
aProfile.addTag(ProfileEntry('s', "(root)"));

View File

@ -914,7 +914,7 @@ stack_callback(void *pc, void *sp, void *closure)
* without doing anything (such as acquiring locks).
*/
static callsite *
backtrace(tm_thread *t, int skip, int *immediate_abort)
backtrace(tm_thread *t, int skipFrames, int *immediate_abort)
{
callsite *site;
stack_buffer_info *info = &t->backtrace_buf;
@ -929,7 +929,8 @@ backtrace(tm_thread *t, int skip, int *immediate_abort)
/* Walk the stack, even if stacks_enabled is false. We do this to
check if we must set immediate_abort. */
info->entries = 0;
rv = NS_StackWalk(stack_callback, skip, info, 0, NULL);
rv = NS_StackWalk(stack_callback, skipFrames, /* maxFrames */ 0, info,
0, NULL);
*immediate_abort = rv == NS_ERROR_UNEXPECTED;
if (rv == NS_ERROR_UNEXPECTED || info->entries == 0) {
t->suppress_tracing--;
@ -961,10 +962,14 @@ backtrace(tm_thread *t, int skip, int *immediate_abort)
* https://bugzilla.mozilla.org/show_bug.cgi?id=374829#c8
*/
/* skip == 0 means |backtrace| should show up, so don't use skip + 1 */
/* NB: this call is repeated below if the buffer is too small */
/*
* skipFrames == 0 means |backtrace| should show up, so don't use
* skipFrames + 1.
* NB: this call is repeated below if the buffer is too small.
*/
info->entries = 0;
rv = NS_StackWalk(stack_callback, skip, info, 0, NULL);
rv = NS_StackWalk(stack_callback, skipFrames, /* maxFrames */ 0, info,
0, NULL);
*immediate_abort = rv == NS_ERROR_UNEXPECTED;
if (rv == NS_ERROR_UNEXPECTED || info->entries == 0) {
t->suppress_tracing--;
@ -988,7 +993,8 @@ backtrace(tm_thread *t, int skip, int *immediate_abort)
/* and call NS_StackWalk again */
info->entries = 0;
NS_StackWalk(stack_callback, skip, info, 0, NULL);
NS_StackWalk(stack_callback, skipFrames, /* maxFrames */ 0, info,
0, NULL);
/* same stack */
PR_ASSERT(info->entries * 2 == new_stack_buffer_size);

View File

@ -15,7 +15,8 @@ namespace mozilla {
nsresult
FramePointerStackWalk(NS_WalkStackCallback aCallback, uint32_t aSkipFrames,
void *aClosure, void **bp, void *stackEnd);
uint32_t aMaxFrames, void *aClosure, void **bp,
void *stackEnd);
}

View File

@ -102,7 +102,8 @@ my_malloc_logger(uint32_t type, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3,
// 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), 0, nullptr);
NS_StackWalk(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
@ -216,6 +217,7 @@ struct WalkStackData {
void **pcs;
uint32_t pc_size;
uint32_t pc_count;
uint32_t pc_max;
void **sps;
uint32_t sp_size;
uint32_t sp_count;
@ -393,6 +395,9 @@ WalkStackMain64(struct WalkStackData* data)
data->sps[data->sp_count] = (void*)spaddr;
++data->sp_count;
if (data->pc_max != 0 && data->pc_count == data->pc_max)
break;
if (frame64.AddrReturn.Offset == 0)
break;
}
@ -462,7 +467,8 @@ WalkStackThread(void* aData)
EXPORT_XPCOM_API(nsresult)
NS_StackWalk(NS_WalkStackCallback aCallback, uint32_t aSkipFrames,
void *aClosure, uintptr_t aThread, void *aPlatformData)
uint32_t aMaxFrames, void *aClosure, uintptr_t aThread,
void *aPlatformData)
{
StackWalkInitCriticalAddress();
static HANDLE myProcess = NULL;
@ -471,7 +477,7 @@ NS_StackWalk(NS_WalkStackCallback aCallback, uint32_t aSkipFrames,
struct WalkStackData data;
if (!EnsureImageHlpInitialized())
return NS_OK;
return NS_ERROR_FAILURE;
HANDLE targetThread = ::GetCurrentThread();
data.walkCallingThread = true;
@ -517,10 +523,11 @@ NS_StackWalk(NS_WalkStackCallback aCallback, uint32_t aSkipFrames,
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_pcs);
data.sp_size = ArrayLength(local_sps);
data.platformData = aPlatformData;
if (aThread) {
@ -572,7 +579,7 @@ NS_StackWalk(NS_WalkStackCallback aCallback, uint32_t aSkipFrames,
for (uint32_t i = 0; i < data.pc_count; ++i)
(*aCallback)(data.pcs[i], data.sps[i], aClosure);
return NS_OK;
return data.pc_count == 0 ? NS_ERROR_FAILURE : NS_OK;
}
@ -898,6 +905,8 @@ struct bucket {
struct my_user_args {
NS_WalkStackCallback callback;
uint32_t skipFrames;
uint32_t maxFrames;
uint32_t numFrames;
void *closure;
};
@ -931,7 +940,7 @@ myinit()
static int
load_address(void * pc, void * arg )
load_address(void * pc, void * arg)
{
static struct bucket table[2048];
static mutex_t lock;
@ -949,15 +958,19 @@ load_address(void * pc, void * arg )
ptr = ptr->next;
}
int stop = 0;
if (ptr->next) {
mutex_unlock(&lock);
} else {
(args->callback)(pc, args->closure);
args->numFrames++;
if (args->maxFrames != 0 && args->numFrames == args->maxFrames)
stop = 1; // causes us to stop getting frames
ptr->next = newbucket(pc);
mutex_unlock(&lock);
}
return 0;
return stop;
}
@ -1020,7 +1033,8 @@ cs_operate(int (*operate_func)(void *, void *, void *), void * usrarg)
EXPORT_XPCOM_API(nsresult)
NS_StackWalk(NS_WalkStackCallback aCallback, uint32_t aSkipFrames,
void *aClosure, uintptr_t aThread, void *aPlatformData)
uint32_t aMaxFrames, void *aClosure, uintptr_t aThread,
void *aPlatformData)
{
MOZ_ASSERT(!aThread);
MOZ_ASSERT(!aPlatformData);
@ -1033,9 +1047,11 @@ NS_StackWalk(NS_WalkStackCallback aCallback, uint32_t aSkipFrames,
args.callback = aCallback;
args.skipFrames = aSkipFrames; /* XXX Not handled! */
args.maxFrames = aMaxFrames;
args.numFrames = 0;
args.closure = aClosure;
cs_operate(load_address, &args);
return NS_OK;
return args.numFrames == 0 ? NS_ERROR_FAILURE : NS_OK;
}
EXPORT_XPCOM_API(nsresult)
@ -1100,11 +1116,13 @@ extern void *__libc_stack_end; // from ld-linux.so
namespace mozilla {
nsresult
FramePointerStackWalk(NS_WalkStackCallback aCallback, uint32_t aSkipFrames,
void *aClosure, void **bp, void *aStackEnd)
uint32_t aMaxFrames, void *aClosure, void **bp,
void *aStackEnd)
{
// Stack walking code courtesy Kipp's "leaky".
int skip = aSkipFrames;
int32_t skip = aSkipFrames;
uint32_t numFrames = 0;
while (1) {
void **next = (void**)*bp;
// bp may not be a frame pointer on i386 if code was compiled with
@ -1136,10 +1154,13 @@ FramePointerStackWalk(NS_WalkStackCallback aCallback, uint32_t aSkipFrames,
// but this should be sufficient for our use the SP
// to order elements on the stack.
(*aCallback)(pc, bp, aClosure);
numFrames++;
if (aMaxFrames != 0 && numFrames == aMaxFrames)
break;
}
bp = next;
}
return NS_OK;
return numFrames == 0 ? NS_ERROR_FAILURE : NS_OK;
}
}
@ -1149,7 +1170,8 @@ FramePointerStackWalk(NS_WalkStackCallback aCallback, uint32_t aSkipFrames,
EXPORT_XPCOM_API(nsresult)
NS_StackWalk(NS_WalkStackCallback aCallback, uint32_t aSkipFrames,
void *aClosure, uintptr_t aThread, void *aPlatformData)
uint32_t aMaxFrames, void *aClosure, uintptr_t aThread,
void *aPlatformData)
{
MOZ_ASSERT(!aThread);
MOZ_ASSERT(!aPlatformData);
@ -1172,7 +1194,7 @@ NS_StackWalk(NS_WalkStackCallback aCallback, uint32_t aSkipFrames,
#else
stackEnd = reinterpret_cast<void*>(-1);
#endif
return FramePointerStackWalk(aCallback, aSkipFrames,
return FramePointerStackWalk(aCallback, aSkipFrames, aMaxFrames,
aClosure, bp, stackEnd);
}
@ -1188,6 +1210,9 @@ NS_StackWalk(NS_WalkStackCallback aCallback, uint32_t aSkipFrames,
struct unwind_info {
NS_WalkStackCallback callback;
int skip;
int maxFrames;
int numFrames;
bool isCriticalAbort;
void *closure;
};
@ -1199,19 +1224,27 @@ unwind_callback (struct _Unwind_Context *context, void *closure)
// TODO Use something like '_Unwind_GetGR()' to get the stack pointer.
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. */
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)
if (--info->skip < 0) {
(*info->callback)(pc, NULL, info->closure);
info->numFrames++;
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;
}
EXPORT_XPCOM_API(nsresult)
NS_StackWalk(NS_WalkStackCallback aCallback, uint32_t aSkipFrames,
void *aClosure, uintptr_t aThread, void *aPlatformData)
uint32_t aMaxFrames, void *aClosure, uintptr_t aThread,
void *aPlatformData)
{
MOZ_ASSERT(!aThread);
MOZ_ASSERT(!aPlatformData);
@ -1219,21 +1252,24 @@ NS_StackWalk(NS_WalkStackCallback aCallback, uint32_t aSkipFrames,
unwind_info info;
info.callback = aCallback;
info.skip = aSkipFrames + 1;
info.maxFrames = aMaxFrames;
info.numFrames = 0;
info.isCriticalAbort = false;
info.closure = aClosure;
_Unwind_Reason_Code t = _Unwind_Backtrace(unwind_callback, &info);
#if defined(ANDROID) && defined(__arm__)
// Ignore the _Unwind_Reason_Code on Android + ARM, because bionic's
// _Unwind_Backtrace usually (always?) returns _URC_FAILURE. See
// https://bugzilla.mozilla.org/show_bug.cgi?id=717853#c110.
//
// (Ideally, the #if above would be specifically for bionic, not for
// Android + ARM, but we don't have a define specifically for bionic.)
#else
if (t != _URC_END_OF_STACK)
(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 NS_ERROR_UNEXPECTED;
#endif
return NS_OK;
return info.numFrames == 0 ? NS_ERROR_FAILURE : NS_OK;
}
#endif
@ -1300,7 +1336,8 @@ NS_FormatCodeAddressDetails(void *aPC, const nsCodeAddressDetails *aDetails,
EXPORT_XPCOM_API(nsresult)
NS_StackWalk(NS_WalkStackCallback aCallback, uint32_t aSkipFrames,
void *aClosure, uintptr_t aThread, void *aPlatformData)
uint32_t aMaxFrames, void *aClosure, uintptr_t aThread,
void *aPlatformData)
{
MOZ_ASSERT(!aThread);
MOZ_ASSERT(!aPlatformData);

View File

@ -31,6 +31,7 @@ typedef void
* @param aSkipFrames Number of initial frames to skip. 0 means that
* the first callback will be for the caller of
* NS_StackWalk.
* @param aMaxFrames Maximum number of frames to trace. 0 means no limit.
* @param aClosure Caller-supplied data passed through to aCallback.
* @param aThread The thread for which the stack is to be retrieved.
* Passing null causes us to walk the stack of the
@ -42,18 +43,26 @@ typedef void
* CONTEXT on Windows and should not be passed on other
* platforms.
*
* Returns NS_ERROR_NOT_IMPLEMENTED on platforms where it is
* unimplemented.
* Returns NS_ERROR_UNEXPECTED when the stack indicates that the thread
* is in a very dangerous situation (e.g., holding sem_pool_lock in
* Mac OS X pthreads code). Callers should then bail out immediately.
* Return values:
* - NS_ERROR_NOT_IMPLEMENTED. Occurs on platforms where it is unimplemented.
*
* - NS_ERROR_UNEXPECTED. Occurs when the stack indicates that the thread
* is in a very dangerous situation (e.g., holding sem_pool_lock in Mac OS X
* pthreads code). Callers should then bail out immediately.
*
* - NS_ERROR_FAILURE. Occurs when stack walking completely failed, i.e.
* aCallback was never called.
*
* - NS_OK. Occurs when stack walking succeeded, i.e. aCallback was called at
* least once (and there was no need to exit with NS_ERROR_UNEXPECTED).
*
* May skip some stack frames due to compiler optimizations or code
* generation.
*/
XPCOM_API(nsresult)
NS_StackWalk(NS_WalkStackCallback aCallback, uint32_t aSkipFrames,
void *aClosure, uintptr_t aThread, void *aPlatformData);
uint32_t aMaxFrames, void *aClosure, uintptr_t aThread,
void *aPlatformData);
typedef struct {
/*

View File

@ -854,7 +854,8 @@ static void PrintStackFrame(void *aPC, void *aSP, void *aClosure)
void
nsTraceRefcntImpl::WalkTheStack(FILE* aStream)
{
NS_StackWalk(PrintStackFrame, 2, aStream, 0, nullptr);
NS_StackWalk(PrintStackFrame, /* skipFrames */ 2, /* maxFrames */ 0, aStream,
0, nullptr);
}
//----------------------------------------------------------------------

View File

@ -96,7 +96,8 @@ bool ValidWriteAssert(bool ok)
// concurrently from many writes, so we use multiple temporary files.
std::vector<uintptr_t> rawStack;
NS_StackWalk(RecordStackWalker, 0, reinterpret_cast<void*>(&rawStack), 0, nullptr);
NS_StackWalk(RecordStackWalker, /* skipFrames */ 0, /* maxFrames */ 0,
reinterpret_cast<void*>(&rawStack), 0, nullptr);
Telemetry::ProcessedStack stack = Telemetry::GetStackAndModules(rawStack);
nsPrintfCString nameAux("%s%s", sProfileDirectory,

View File

@ -137,7 +137,8 @@ GetChromeHangReport(Telemetry::ProcessedStack &aStack)
DWORD ret = ::SuspendThread(winMainThreadHandle);
if (ret == -1)
return;
NS_StackWalk(ChromeStackWalker, 0, reinterpret_cast<void*>(&rawStack),
NS_StackWalk(ChromeStackWalker, /* skipFrames */ 0, /* maxFrames */ 0,
reinterpret_cast<void*>(&rawStack),
reinterpret_cast<uintptr_t>(winMainThreadHandle), nullptr);
ret = ::ResumeThread(winMainThreadHandle);
if (ret == -1)