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