| Index: third_party/tcmalloc/chromium/src/leak_detector.cc
|
| diff --git a/third_party/tcmalloc/chromium/src/leak_detector.cc b/third_party/tcmalloc/chromium/src/leak_detector.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..d496f4265d23215437ac2c3cae19c33ce8eb2a55
|
| --- /dev/null
|
| +++ b/third_party/tcmalloc/chromium/src/leak_detector.cc
|
| @@ -0,0 +1,250 @@
|
| +// Copyright (c) 2015 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +// ---
|
| +// Author: Simon Que
|
| +
|
| +#include <unistd.h>
|
| +#include <link.h>
|
| +
|
| +#include "base/basictypes.h"
|
| +#include "base/custom_allocator.h"
|
| +#include "base/googleinit.h"
|
| +#include "base/logging.h"
|
| +#include "base/spinlock.h"
|
| +#include <gperftools/malloc_extension.h>
|
| +#include <gperftools/malloc_hook.h>
|
| +#include "leak_detector_impl.h"
|
| +#include "tcmalloc_guard.h"
|
| +
|
| +DEFINE_int32(sampling_factor,
|
| + EnvToInt("LEAK_DETECTOR_SAMPLING_FACTOR", 1),
|
| + "Used for sampling allocs and frees. Randomly samples "
|
| + "|sampling_factor|/256 of the pointers being allocated and "
|
| + "freed.");
|
| +DEFINE_int32(stack_depth,
|
| + EnvToInt("LEAK_DETECTOR_STACK_DEPTH", 4),
|
| + "The number of call stack levels to unwind when profiling "
|
| + "allocations by call stack.");
|
| +DEFINE_uint64(dump_interval_bytes,
|
| + EnvToInt("LEAK_DETECTOR_DUMP_INTERVAL_KB", 32768) * 1024,
|
| + "Dump allocation stats and check for memory leaks after this "
|
| + "many bytes have been allocated since the last dump/check. Does "
|
| + "not get affected by sampling.");
|
| +DEFINE_bool(dump_leak_analysis,
|
| + EnvToInt("LEAK_DETECTOR_VERBOSE", false),
|
| + "Dump all leak analysis data, not just suspected leak reports.");
|
| +
|
| +DEFINE_int32(size_suspicion_threshold,
|
| + EnvToInt("LEAK_DETECTOR_SIZE_SUSPICION_THRESHOLD", 4),
|
| + "The number of times an allocation size must be suspected as a "
|
| + "leak before it gets reported.");
|
| +DEFINE_int32(call_stack_suspicion_threshold,
|
| + EnvToInt("LEAK_DETECTOR_CALL_STACK_SUSPICION_THRESHOLD", 4),
|
| + "The number of times a call stack for a particular allocation "
|
| + "size must be suspected as a leak before it gets reported.");
|
| +
|
| +namespace leak_detector {
|
| +
|
| +namespace {
|
| +
|
| +// We strip out different number of stack frames in debug mode
|
| +// because less inlining happens in that case
|
| +#ifdef NDEBUG
|
| +static const int kStripFrames = 2;
|
| +#else
|
| +static const int kStripFrames = 3;
|
| +#endif
|
| +
|
| +// Use a simple spinlock for locking. Don't use a mutex, which can call malloc
|
| +// and cause infinite recursion.
|
| +SpinLock heap_lock(SpinLock::LINKER_INITIALIZED);
|
| +
|
| +// For storing the address range of the Chrome binary in memory.
|
| +struct MappingInfo {
|
| + uint64 addr;
|
| + uint64 size;
|
| +} chrome_mapping;
|
| +
|
| +// For making allocations within the leak detector that it should not track.
|
| +LowLevelAlloc::Arena *leak_detector_arena;
|
| +
|
| +// Points to the active instance of the leak detector.
|
| +// Modify this only when locked.
|
| +LeakDetectorImpl* leak_detector = NULL;
|
| +
|
| +// Keep track of the total number of bytes allocated.
|
| +// TODO(sque): This might render LeakDetectorImpl::stats redundant.
|
| +// Modify this only when locked.
|
| +uint64 total_alloc_size = 0;
|
| +
|
| +// Keep track of the total alloc size when the last dump occurred.
|
| +// Modify this only when locked.
|
| +uint64 last_alloc_dump_size = 0;
|
| +
|
| +// Dump allocation stats and check for leaks after |FLAGS_dump_interval_bytes|
|
| +// have been allocated since the last time that was done. Should be called with
|
| +// a lock since it modifies the global variable |last_alloc_dump_size|.
|
| +inline void MaybeDumpStatsAndCheckForLeaks() {
|
| + if (total_alloc_size > last_alloc_dump_size + FLAGS_dump_interval_bytes) {
|
| + leak_detector->DumpStats();
|
| + last_alloc_dump_size = total_alloc_size;
|
| + leak_detector->TestForLeaks();
|
| + }
|
| +}
|
| +
|
| +// Convert a pointer to a hash value. Returns only the upper eight bits.
|
| +inline unsigned int PointerToHash(const void* ptr) {
|
| + // The input data is the pointer address, not the location in memory pointed
|
| + // to by the pointer.
|
| + const uint64_t kMultiplier = 0x9ddfea08eb382d69ULL;
|
| + uint64_t value = reinterpret_cast<uint64_t>(ptr) * kMultiplier;
|
| + return value >> 56;
|
| +}
|
| +
|
| +// Uses PointerToHash() to pseudorandomly sample |ptr|.
|
| +inline bool ShouldSample(const void* ptr) {
|
| + return PointerToHash(ptr) < FLAGS_sampling_factor;
|
| +}
|
| +
|
| +// Allocation/deallocation hooks for MallocHook.
|
| +void NewHook(const void* ptr, size_t size) {
|
| + {
|
| + SpinLockHolder l(&heap_lock);
|
| + total_alloc_size += size;
|
| + }
|
| +
|
| + if (!ShouldSample(ptr) || !ptr || !leak_detector)
|
| + return;
|
| +
|
| + // Take the stack trace outside the critical section.
|
| + // leak_detector->ShouldGetStackTraceForSize() is const; there is no need for
|
| + // a lock.
|
| + void* stack[FLAGS_stack_depth];
|
| + int depth = 0;
|
| + if (leak_detector->ShouldGetStackTraceForSize(size)) {
|
| + depth = MallocHook::GetCallerStackTrace(
|
| + stack, FLAGS_stack_depth, kStripFrames + 1);
|
| + }
|
| +
|
| + SpinLockHolder l(&heap_lock);
|
| + leak_detector->RecordAlloc(ptr, size, depth, stack);
|
| + MaybeDumpStatsAndCheckForLeaks();
|
| +}
|
| +
|
| +void DeleteHook(const void* ptr) {
|
| + if (!ShouldSample(ptr) || !ptr || !leak_detector)
|
| + return;
|
| +
|
| + SpinLockHolder l(&heap_lock);
|
| + leak_detector->RecordFree(ptr);
|
| +}
|
| +
|
| +// Callback for dl_iterate_phdr() to find the Chrome binary mapping.
|
| +int IterateLoadedObjects(struct dl_phdr_info *info,
|
| + size_t /* size */,
|
| + void *data) {
|
| + uint64 base_addr = info->dlpi_addr;
|
| + if (FLAGS_dump_leak_analysis) {
|
| + RAW_LOG(0, "%d: name=%s addr=%lx (%d segments)", getpid(),
|
| + info->dlpi_name, base_addr, info->dlpi_phnum);
|
| + }
|
| + for (int i = 0; i < info->dlpi_phnum; i++) {
|
| + const ElfW(Phdr)& header = info->dlpi_phdr[i];
|
| + RAW_LOG(0, "%d: \t\t header %2d: type=%x, offset=%lx, vaddr=%lx, "
|
| + "paddr=%lx, filesz=%lx, memsz=%lx", getpid(), i,
|
| + header.p_type,
|
| + header.p_offset,
|
| + header.p_vaddr,
|
| + header.p_paddr,
|
| + header.p_filesz,
|
| + header.p_memsz);
|
| + if (header.p_type == SHT_PROGBITS && header.p_offset == 0 && data) {
|
| + MappingInfo* mapping = static_cast<MappingInfo*>(data);
|
| + mapping->addr = info->dlpi_addr + header.p_offset;
|
| + mapping->size = header.p_memsz;
|
| + if (FLAGS_dump_leak_analysis) {
|
| + RAW_LOG(0, "%d: Chrome mapped from %lx to %lx", getpid(),
|
| + mapping->addr, mapping->addr + mapping->size);
|
| + }
|
| + return 1;
|
| + }
|
| + }
|
| + return 0;
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +//----------------------------------------------------------------------
|
| +// Starting/stopping
|
| +//----------------------------------------------------------------------
|
| +
|
| +void LeakDetectorStart() {
|
| + // If the sampling factor is too low, don't bother enabling the leak detector.
|
| + if (FLAGS_sampling_factor < 1) {
|
| + RAW_LOG(0, "%d: Not enabling leak detector because sampling_factor = %d",
|
| + getpid(), FLAGS_sampling_factor);
|
| + return;
|
| + }
|
| +
|
| + // Locate the Chrome binary mapping before doing anything else.
|
| + dl_iterate_phdr(IterateLoadedObjects, &chrome_mapping);
|
| +
|
| + SpinLockHolder l(&heap_lock);
|
| + if (leak_detector)
|
| + return;
|
| +
|
| + RAW_LOG(0, "%d: Starting leak detector. Sampling factor: %d", getpid(),
|
| + FLAGS_sampling_factor);
|
| +
|
| + // This should be done before the hooks are set up, since it should
|
| + // call new, and we want that to be accounted for correctly.
|
| + MallocExtension::Initialize();
|
| +
|
| + leak_detector_arena =
|
| + LowLevelAlloc::NewArena(0, LowLevelAlloc::DefaultArena());
|
| + CustomAllocator::SetArena(leak_detector_arena);
|
| +
|
| + leak_detector = new(CustomAllocator::Allocate(sizeof(LeakDetectorImpl)))
|
| + LeakDetectorImpl(chrome_mapping.addr, chrome_mapping.size);
|
| +
|
| + // We do not reset dump_count so if the user does a sequence of
|
| + // HeapProfilerStart/HeapProfileStop, we will get a continuous
|
| + // sequence of profiles.
|
| +
|
| + // Now set the hooks that capture new/delete and malloc/free.
|
| + RAW_CHECK(MallocHook::SetNewHook(&NewHook) == NULL, "");
|
| + RAW_CHECK(MallocHook::SetDeleteHook(&DeleteHook) == NULL, "");
|
| +}
|
| +
|
| +void LeakDetectorStop() {
|
| + SpinLockHolder l(&heap_lock);
|
| +
|
| + if (!leak_detector)
|
| + return;
|
| +
|
| + // Unset our new/delete hooks, checking they were set:
|
| + RAW_CHECK(MallocHook::SetNewHook(NULL) == &NewHook, "");
|
| + RAW_CHECK(MallocHook::SetDeleteHook(NULL) == &DeleteHook, "");
|
| +
|
| + leak_detector->~LeakDetectorImpl();
|
| + CustomAllocator::Free(leak_detector, sizeof(LeakDetectorImpl));
|
| + leak_detector = NULL;
|
| +
|
| + CustomAllocator::SetArena(NULL);
|
| + if (!LowLevelAlloc::DeleteArena(leak_detector_arena)) {
|
| + RAW_LOG(FATAL, "Memory leak in LeakDetector.");
|
| + }
|
| +}
|
| +
|
| +} // namespace leak_detector
|
| +
|
| +#if defined(ENABLE_LEAK_DETECTOR)
|
| +
|
| +// We want to make sure tcmalloc is up and running before starting the profiler
|
| +static const TCMallocGuard tcmalloc_initializer;
|
| +REGISTER_MODULE_INITIALIZER(leakdetector, leak_detector::LeakDetectorStart());
|
| +REGISTER_MODULE_DESTRUCTOR(leakdetector, leak_detector::LeakDetectorStop());
|
| +
|
| +#endif // defined(ENABLE_LEAK_DETECTOR)
|
|
|