Files
tubestation/xpcom/build/TlsAllocationTracker.cpp
Cervantes Yu 3cbb9790ec Bug 1320134 - Part 1: Tracking TLS allocations for diagnosing out-of-TLS-slots crashes on Windows. r=froydnj
This tracks TlsAlloc() and TlsFree() calls on Windows for diagnosing crashes when a proces reaches
its limit (1088) for TLS slots. Tracking of TLS allocation is done by intercepting TlsAlloc() and
TlsFree() in kernel32.dll. After initialization, we start tracking the number of allocated TLS
slots. If the number of observed TLS allocations exceeds a high water mark, we record the stack
when TlsAlloc() is called, and the recorded stacks gets serialized in a JSON string ready for
crash annotation.

MozReview-Commit-ID: 5fHVr0eiMy5
2017-03-15 18:20:30 +08:00

252 lines
5.6 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "TlsAllocationTracker.h"
#include <stdint.h>
#include <windows.h>
#include "mozilla/Atomics.h"
#include "mozilla/JSONWriter.h"
#include "mozilla/Move.h"
#include "mozilla/StackWalk.h"
#include "mozilla/StaticMutex.h"
#include "mozilla/StaticPtr.h"
#include "nsDebug.h"
#include "nsHashKeys.h"
#include "nsString.h"
#include "nsTArray.h"
#include "nsTHashtable.h"
#include "nsWindowsDllInterceptor.h"
namespace mozilla {
namespace {
static StaticAutoPtr<nsCString> sTlsAllocationStacks;
struct nsCStringWriter : public JSONWriteFunc {
explicit nsCStringWriter(nsCString* aData)
: mData(aData)
{
MOZ_ASSERT(mData);
}
void Write(const char* aStr)
{
mData->AppendASCII(aStr);
}
nsCString* mData;
};
// Start recording TlsAlloc() call stacks when we observed kStartTrackingTlsAt
// allocations. We choose this value close to the maximum number of TLS indexes
// in a Windows process, which is 1088, in the hope of catching TLS leaks
// without impacting normal users.
const uint32_t kStartTrackingTlsAt = 950;
using stack_t = nsTArray<uintptr_t>;
struct StackEntry : public nsUint32HashKey {
explicit StackEntry(KeyTypePointer aKey)
: nsUint32HashKey(aKey)
{ }
stack_t mStack;
};
using stacks_t = nsTHashtable<StackEntry>;
static StaticAutoPtr<stacks_t> sRecentTlsAllocationStacks;
static Atomic<bool> sInitialized;
static StaticMutex sMutex;
static Atomic<uint32_t> sCurrentTlsSlots{0};
using TlsAllocFn = DWORD (WINAPI *)();
using TlsFreeFn = BOOL (WINAPI *)(DWORD);
TlsAllocFn gOriginalTlsAlloc;
TlsFreeFn gOriginalTlsFree;
static void
MaybeRecordCurrentStack(DWORD aTlsIndex)
{
if (sCurrentTlsSlots < kStartTrackingTlsAt) {
return;
}
stack_t rawStack;
auto callback = [](uint32_t, void* aPC, void*, void* aClosure) {
auto stack = static_cast<stack_t*>(aClosure);
stack->AppendElement(reinterpret_cast<uintptr_t>(aPC));
};
MozStackWalk(callback, /* skip 2 frames */ 2,
/* maxFrames */ 0, &rawStack, 0, nullptr);
StaticMutexAutoLock lock(sMutex);
if (!sRecentTlsAllocationStacks) {
return;
}
StackEntry* stack = sRecentTlsAllocationStacks->PutEntry(aTlsIndex);
MOZ_ASSERT(!stack->mStack.IsEmpty());
stack->mStack = Move(rawStack);
}
static void
MaybeDeleteRecordedStack(DWORD aTlsIndex)
{
StaticMutexAutoLock lock(sMutex);
if (!sRecentTlsAllocationStacks) {
return;
}
auto entry = sRecentTlsAllocationStacks->GetEntry(aTlsIndex);
if (entry) {
sRecentTlsAllocationStacks->RemoveEntry(aTlsIndex);
}
}
static void
AnnotateRecentTlsStacks()
{
StaticMutexAutoLock lock(sMutex);
if (!sRecentTlsAllocationStacks) {
// Maybe another thread steals the stack vector content and is dumping the
// stacks
return;
}
// Move the content to prevent further requests to this function.
UniquePtr<stacks_t> stacks = MakeUnique<stacks_t>();
sRecentTlsAllocationStacks->SwapElements(*stacks.get());
sRecentTlsAllocationStacks = nullptr;
sTlsAllocationStacks = new nsCString();
JSONWriter output(
MakeUnique<nsCStringWriter, nsCString*>(sTlsAllocationStacks.get()));
output.Start(JSONWriter::SingleLineStyle);
for (auto iter = stacks->Iter(); !iter.Done(); iter.Next()) {
const stack_t& stack = iter.Get()->mStack;
output.StartArrayElement();
for (auto pc : stack) {
output.IntElement(pc);
}
output.EndArray();
}
output.End();
}
DWORD WINAPI
InterposedTlsAlloc()
{
if (!sInitialized) {
// Don't interpose if we didn't fully initialize both hooks or after we
// already shutdown the tracker.
return gOriginalTlsAlloc();
}
sCurrentTlsSlots += 1;
DWORD tlsAllocRv = gOriginalTlsAlloc();
MaybeRecordCurrentStack(tlsAllocRv);
if (tlsAllocRv == TLS_OUT_OF_INDEXES) {
AnnotateRecentTlsStacks();
}
return tlsAllocRv;
}
BOOL WINAPI
InterposedTlsFree(DWORD aTlsIndex)
{
if (!sInitialized) {
// Don't interpose if we didn't fully initialize both hooks or after we
// already shutdown the tracker.
return gOriginalTlsFree(aTlsIndex);
}
sCurrentTlsSlots -= 1;
MaybeDeleteRecordedStack(aTlsIndex);
return gOriginalTlsFree(aTlsIndex);
}
} // Anonymous namespace.
void
InitTlsAllocationTracker()
{
if (sInitialized) {
return;
}
sRecentTlsAllocationStacks = new stacks_t();
// Windows DLL interceptor
static WindowsDllInterceptor sKernel32DllInterceptor{};
// Initialize dll interceptor and add hook.
sKernel32DllInterceptor.Init("kernel32.dll");
bool succeeded = sKernel32DllInterceptor.AddHook(
"TlsAlloc",
reinterpret_cast<intptr_t>(InterposedTlsAlloc),
reinterpret_cast<void**>(&gOriginalTlsAlloc));
if (!succeeded) {
return;
}
succeeded = sKernel32DllInterceptor.AddHook(
"TlsFree",
reinterpret_cast<intptr_t>(InterposedTlsFree),
reinterpret_cast<void**>(&gOriginalTlsFree));
if (!succeeded) {
return;
}
sInitialized = true;
}
const char*
GetTlsAllocationStacks()
{
StaticMutexAutoLock lock(sMutex);
if (!sTlsAllocationStacks) {
return nullptr;
}
return sTlsAllocationStacks->BeginReading();
}
void
ShutdownTlsAllocationTracker()
{
if (!sInitialized) {
return;
}
sInitialized = false;
StaticMutexAutoLock lock(sMutex);
sRecentTlsAllocationStacks = nullptr;
sTlsAllocationStacks = nullptr;
}
} // namespace mozilla