зеркало из https://github.com/mozilla/gecko-dev.git
252 строки
5.6 KiB
C++
252 строки
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
|