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
252 lines
5.6 KiB
C++
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
|