/* -*- 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 #include #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 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; struct StackEntry : public nsUint32HashKey { explicit StackEntry(KeyTypePointer aKey) : nsUint32HashKey(aKey) { } stack_t mStack; }; using stacks_t = nsTHashtable; static StaticAutoPtr sRecentTlsAllocationStacks; static Atomic sInitialized; static StaticMutex sMutex; static Atomic 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(aClosure); stack->AppendElement(reinterpret_cast(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 = MakeUnique(); sRecentTlsAllocationStacks->SwapElements(*stacks.get()); sRecentTlsAllocationStacks = nullptr; sTlsAllocationStacks = new nsCString(); JSONWriter output( MakeUnique(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(InterposedTlsAlloc), reinterpret_cast(&gOriginalTlsAlloc)); if (!succeeded) { return; } succeeded = sKernel32DllInterceptor.AddHook( "TlsFree", reinterpret_cast(InterposedTlsFree), reinterpret_cast(&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