OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "components/metrics/leak_detector/leak_detector.h" |
| 6 |
| 7 #include <gperftools/malloc_hook.h> |
| 8 #include <link.h> |
| 9 #include <stdint.h> |
| 10 #include <unistd.h> |
| 11 |
| 12 #include <new> |
| 13 |
| 14 #include "base/logging.h" |
| 15 #include "components/metrics/leak_detector/custom_allocator.h" |
| 16 #include "components/metrics/leak_detector/leak_detector_impl.h" |
| 17 |
| 18 #if defined(OS_POSIX) |
| 19 #include <pthread.h> |
| 20 #else |
| 21 #error "Non-POSIX builds not supported as they do not have pthread.h." |
| 22 #endif |
| 23 |
| 24 namespace leak_detector { |
| 25 |
| 26 namespace { |
| 27 |
| 28 // We strip out different number of stack frames in debug mode |
| 29 // because less inlining happens in that case |
| 30 #ifdef NDEBUG |
| 31 static const int kStripFrames = 2; |
| 32 #else |
| 33 static const int kStripFrames = 3; |
| 34 #endif |
| 35 |
| 36 // For storing the address range of the Chrome binary in memory. |
| 37 struct MappingInfo { |
| 38 uintptr_t addr; |
| 39 size_t size; |
| 40 } chrome_mapping; |
| 41 |
| 42 // TODO(sque): This is a temporary solution for leak detector params. Eventually |
| 43 // params should be passed in from elsewhere in Chromium, and this file should |
| 44 // be deleted. |
| 45 |
| 46 bool EnvToBool(const char* envname, const bool default_value) { |
| 47 return !getenv(envname) |
| 48 ? default_value |
| 49 : memchr("tTyY1\0", getenv(envname)[0], 6) != nullptr; |
| 50 } |
| 51 |
| 52 int EnvToInt(const char* envname, const int default_value) { |
| 53 return !getenv(envname) |
| 54 ? default_value |
| 55 : strtol(getenv(envname), nullptr, 10); |
| 56 } |
| 57 |
| 58 // Used for sampling allocs and frees. Randomly samples |g_sampling_factor|/256 |
| 59 // of the pointers being allocated and freed. |
| 60 int g_sampling_factor = EnvToInt("LEAK_DETECTOR_SAMPLING_FACTOR", 1); |
| 61 |
| 62 // The number of call stack levels to unwind when profiling allocations by call |
| 63 // stack. |
| 64 int g_stack_depth = EnvToInt("LEAK_DETECTOR_STACK_DEPTH", 4); |
| 65 |
| 66 // Dump allocation stats and check for memory leaks after this many bytes have |
| 67 // been allocated since the last dump/check. Does not get affected by sampling. |
| 68 uint64_t g_dump_interval_bytes = |
| 69 EnvToInt("LEAK_DETECTOR_DUMP_INTERVAL_KB", 32768) * 1024; |
| 70 |
| 71 // Enable verbose logging. Dump all leak analysis data, not just analysis |
| 72 // summaries and suspected leak reports. |
| 73 bool g_dump_leak_analysis = EnvToBool("LEAK_DETECTOR_VERBOSE", false); |
| 74 |
| 75 // The number of times an allocation size must be suspected as a leak before it |
| 76 // gets reported. |
| 77 int g_size_suspicion_threshold = |
| 78 EnvToInt("LEAK_DETECTOR_SIZE_SUSPICION_THRESHOLD", 4); |
| 79 |
| 80 // The number of times a call stack for a particular allocation size must be |
| 81 // suspected as a leak before it gets reported. |
| 82 int g_call_stack_suspicion_threshold = |
| 83 EnvToInt("LEAK_DETECTOR_CALL_STACK_SUSPICION_THRESHOLD", 4); |
| 84 |
| 85 // Create an object of this class to store the current new/delete hooks and |
| 86 // then remove them. When this object goes out of scope, it will automatically |
| 87 // restore the original hooks if they existed. |
| 88 // |
| 89 // If multiple instances of this class are created and there are some hooks, |
| 90 // only the first object will save and restore the hook functions. The others |
| 91 // will have no effect. |
| 92 class MallocHookDisabler { |
| 93 public: |
| 94 MallocHookDisabler() : new_hook_(MallocHook::SetNewHook(nullptr)), |
| 95 delete_hook_(MallocHook::SetDeleteHook(nullptr)) {} |
| 96 |
| 97 ~MallocHookDisabler() { |
| 98 if (new_hook_) |
| 99 MallocHook::SetNewHook(new_hook_); |
| 100 if (delete_hook_) |
| 101 MallocHook::SetDeleteHook(delete_hook_); |
| 102 } |
| 103 |
| 104 private: |
| 105 using NewHookFunc = std::add_pointer<void(const void*, size_t)>::type; |
| 106 using DeleteHookFunc = std::add_pointer<void(const void*)>::type; |
| 107 |
| 108 NewHookFunc new_hook_; |
| 109 DeleteHookFunc delete_hook_; |
| 110 |
| 111 DISALLOW_COPY_AND_ASSIGN(MallocHookDisabler); |
| 112 }; |
| 113 |
| 114 // Pass a spinlock handle to the constructor to attempt to acquire the lock. |
| 115 // The destructor will automatically release the lock when the SpinLockHolder |
| 116 // goes out of scope. |
| 117 class SpinLockHolder { |
| 118 public: |
| 119 explicit SpinLockHolder(pthread_spinlock_t* spinlock) : spinlock_(spinlock) { |
| 120 pthread_spin_lock(spinlock); |
| 121 } |
| 122 |
| 123 ~SpinLockHolder() { |
| 124 pthread_spin_unlock(spinlock_); |
| 125 } |
| 126 |
| 127 private: |
| 128 pthread_spinlock_t* spinlock_; |
| 129 |
| 130 DISALLOW_COPY_AND_ASSIGN(SpinLockHolder); |
| 131 }; |
| 132 |
| 133 // Use a spinlock for controlling access to shared resources. |
| 134 pthread_spinlock_t g_spinlock; |
| 135 |
| 136 // Points to the active instance of the leak detector. |
| 137 // Modify this only when locked. |
| 138 LeakDetectorImpl* g_leak_detector = nullptr; |
| 139 |
| 140 // Keep track of the total number of bytes allocated. |
| 141 // Modify this only when locked. |
| 142 uint64_t g_total_alloc_size = 0; |
| 143 |
| 144 // Keep track of the total alloc size when the last dump occurred. |
| 145 // Modify this only when locked. |
| 146 uint64_t g_last_alloc_dump_size = 0; |
| 147 |
| 148 // Dump allocation stats and check for leaks after |g_dump_interval_bytes| bytes |
| 149 // have been allocated since the last time that was done. Should be called with |
| 150 // a lock since it modifies the global variable |g_last_alloc_dump_size|. |
| 151 inline void MaybeDumpStatsAndCheckForLeaks() { |
| 152 if (g_total_alloc_size > g_last_alloc_dump_size + g_dump_interval_bytes) { |
| 153 g_last_alloc_dump_size = g_total_alloc_size; |
| 154 |
| 155 InternalVector<InternalLeakReport> reports; |
| 156 g_leak_detector->TestForLeaks(true /* do_logging */, &reports); |
| 157 } |
| 158 } |
| 159 |
| 160 // Convert a pointer to a hash value. Returns only the upper eight bits. |
| 161 inline uint64_t PointerToHash(const void* ptr) { |
| 162 // The input data is the pointer address, not the location in memory pointed |
| 163 // to by the pointer. |
| 164 // The multiplier is taken from Farmhash code: |
| 165 // https://github.com/google/farmhash/blob/master/src/farmhash.cc |
| 166 const uint64_t kMultiplier = 0x9ddfea08eb382d69ULL; |
| 167 uint64_t value = reinterpret_cast<uint64_t>(ptr) * kMultiplier; |
| 168 return value >> 56; |
| 169 } |
| 170 |
| 171 // Uses PointerToHash() to pseudorandomly sample |ptr|. |
| 172 inline bool ShouldSample(const void* ptr) { |
| 173 return PointerToHash(ptr) < static_cast<uint64_t>(g_sampling_factor); |
| 174 } |
| 175 |
| 176 // Allocation/deallocation hooks for MallocHook. |
| 177 void NewHook(const void* ptr, size_t size) { |
| 178 { |
| 179 SpinLockHolder lock(&g_spinlock); |
| 180 g_total_alloc_size += size; |
| 181 } |
| 182 |
| 183 if (!ShouldSample(ptr) || !ptr || !g_leak_detector) |
| 184 return; |
| 185 |
| 186 // Take the stack trace outside the critical section. |
| 187 // |g_leak_detector->ShouldGetStackTraceForSize()| is const; there is no need |
| 188 // for a lock. |
| 189 void* stack[g_stack_depth]; |
| 190 int depth = 0; |
| 191 if (g_leak_detector->ShouldGetStackTraceForSize(size)) { |
| 192 depth = MallocHook::GetCallerStackTrace( |
| 193 stack, g_stack_depth, kStripFrames + 1); |
| 194 } |
| 195 |
| 196 SpinLockHolder lock(&g_spinlock); |
| 197 g_leak_detector->RecordAlloc(ptr, size, depth, stack); |
| 198 MaybeDumpStatsAndCheckForLeaks(); |
| 199 } |
| 200 |
| 201 void DeleteHook(const void* ptr) { |
| 202 if (!ShouldSample(ptr) || !ptr || !g_leak_detector) |
| 203 return; |
| 204 |
| 205 SpinLockHolder lock(&g_spinlock); |
| 206 g_leak_detector->RecordFree(ptr); |
| 207 } |
| 208 |
| 209 // Must be called under lock. |
| 210 void* InternalAlloc(size_t size) { |
| 211 if (pthread_spin_trylock(&g_spinlock) == 0) |
| 212 LOG(FATAL) << "Must be called under lock!"; |
| 213 |
| 214 MallocHookDisabler disabler; |
| 215 return new char[size]; |
| 216 } |
| 217 |
| 218 // Must be called under lock. |
| 219 void InternalFree(void* ptr, size_t /* size */) { |
| 220 if (pthread_spin_trylock(&g_spinlock) == 0) |
| 221 LOG(FATAL) << "Must be called under lock!"; |
| 222 |
| 223 MallocHookDisabler disabler; |
| 224 delete [] reinterpret_cast<char*>(ptr); |
| 225 } |
| 226 |
| 227 // Callback for dl_iterate_phdr() to find the Chrome binary mapping. |
| 228 int IterateLoadedObjects(struct dl_phdr_info *shared_object, |
| 229 size_t /* size */, |
| 230 void *data) { |
| 231 for (int i = 0; i < shared_object->dlpi_phnum; i++) { |
| 232 // Find the ELF segment header that contains the actual code of the Chrome |
| 233 // binary. |
| 234 const ElfW(Phdr)& segment_header = shared_object->dlpi_phdr[i]; |
| 235 if (segment_header.p_type == SHT_PROGBITS && |
| 236 segment_header.p_offset == 0 && data) { |
| 237 MappingInfo* mapping = static_cast<MappingInfo*>(data); |
| 238 |
| 239 // Make sure the fields in the ELF header and MappingInfo have the |
| 240 // same size. |
| 241 static_assert(sizeof(mapping->addr) == sizeof(shared_object->dlpi_addr), |
| 242 "Integer size mismatch between MappingInfo::addr and " |
| 243 "dl_phdr_info::dlpi_addr."); |
| 244 static_assert(sizeof(mapping->size) == sizeof(segment_header.p_offset), |
| 245 "Integer size mismatch between MappingInfo::size and " |
| 246 "ElfW(Phdr)::p_memsz."); |
| 247 |
| 248 mapping->addr = shared_object->dlpi_addr + segment_header.p_offset; |
| 249 mapping->size = segment_header.p_memsz; |
| 250 if (g_dump_leak_analysis) { |
| 251 LOG(ERROR) << "Chrome mapped from " << std::hex |
| 252 << mapping->addr << " to " |
| 253 << mapping->addr + mapping->size; |
| 254 } |
| 255 return 1; |
| 256 } |
| 257 } |
| 258 return 0; |
| 259 } |
| 260 |
| 261 } // namespace |
| 262 |
| 263 void Initialize() { |
| 264 // If the sampling factor is too low, don't bother enabling the leak detector. |
| 265 if (g_sampling_factor < 1) { |
| 266 LOG(ERROR) << "Not enabling leak detector because g_sampling_factor=" |
| 267 << g_sampling_factor; |
| 268 return; |
| 269 } |
| 270 |
| 271 if (IsInitialized()) |
| 272 return; |
| 273 |
| 274 // Locate the Chrome binary mapping before doing anything else. |
| 275 dl_iterate_phdr(IterateLoadedObjects, &chrome_mapping); |
| 276 |
| 277 if (CustomAllocator::IsInitialized()) { |
| 278 LOG(ERROR) << "Custom allocator can only be initialized once!"; |
| 279 return; |
| 280 } |
| 281 CustomAllocator::Initialize(&InternalAlloc, &InternalFree); |
| 282 |
| 283 pthread_spin_init(&g_spinlock, PTHREAD_PROCESS_PRIVATE); |
| 284 |
| 285 SpinLockHolder lock(&g_spinlock); |
| 286 if (g_leak_detector) |
| 287 return; |
| 288 |
| 289 g_total_alloc_size = 0; |
| 290 g_last_alloc_dump_size = 0; |
| 291 |
| 292 LOG(ERROR) << "Starting leak detector. Sampling factor: " |
| 293 << g_sampling_factor; |
| 294 |
| 295 g_leak_detector = new(CustomAllocator::Allocate(sizeof(LeakDetectorImpl))) |
| 296 LeakDetectorImpl(chrome_mapping.addr, |
| 297 chrome_mapping.size, |
| 298 g_size_suspicion_threshold, |
| 299 g_call_stack_suspicion_threshold, |
| 300 g_dump_leak_analysis); |
| 301 |
| 302 // Now set the hooks that capture new/delete and malloc/free. Make sure |
| 303 // nothing is already set. |
| 304 CHECK(MallocHook::SetNewHook(&NewHook) == nullptr); |
| 305 CHECK(MallocHook::SetDeleteHook(&DeleteHook) == nullptr); |
| 306 } |
| 307 |
| 308 void Shutdown() { |
| 309 if (!IsInitialized()) |
| 310 return; |
| 311 |
| 312 { |
| 313 SpinLockHolder lock(&g_spinlock); |
| 314 |
| 315 // Unset our new/delete hooks, checking they were previously set. |
| 316 CHECK_EQ(MallocHook::SetNewHook(nullptr), &NewHook); |
| 317 CHECK_EQ(MallocHook::SetDeleteHook(nullptr), &DeleteHook); |
| 318 |
| 319 g_leak_detector->~LeakDetectorImpl(); |
| 320 CustomAllocator::Free(g_leak_detector, sizeof(LeakDetectorImpl)); |
| 321 g_leak_detector = nullptr; |
| 322 } |
| 323 |
| 324 pthread_spin_destroy(&g_spinlock); |
| 325 |
| 326 if (!CustomAllocator::Shutdown()) |
| 327 LOG(ERROR) << "Memory leak in LeakDetector, allocated objects remain."; |
| 328 |
| 329 LOG(ERROR) << "Stopped leak detector."; |
| 330 } |
| 331 |
| 332 bool IsInitialized() { |
| 333 return g_leak_detector; |
| 334 } |
| 335 |
| 336 } // namespace leak_detector |
OLD | NEW |