Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "components/metrics/leak_detector/leak_detector.h" | 5 #include "components/metrics/leak_detector/leak_detector.h" |
| 6 | 6 |
| 7 #include <link.h> | |
| 8 #include <stdint.h> | |
| 9 #include <unistd.h> | |
| 10 | |
| 11 #include "base/allocator/allocator_extension.h" | |
| 12 #include "base/logging.h" | |
| 13 #include "components/metrics/leak_detector/custom_allocator.h" | |
| 7 #include "content/public/browser/browser_thread.h" | 14 #include "content/public/browser/browser_thread.h" |
| 8 | 15 |
| 9 namespace metrics { | 16 namespace metrics { |
| 10 | 17 |
| 18 using LeakReport = LeakDetector::LeakReport; | |
| 19 using leak_detector::CustomAllocator; | |
| 20 using leak_detector::LeakDetectorImpl; | |
| 21 using InternalLeakReport = LeakDetectorImpl::LeakReport; | |
| 22 template <typename T> | |
| 23 using InternalVector = LeakDetectorImpl::InternalVector<T>; | |
| 24 | |
| 25 namespace { | |
| 26 | |
| 27 // The sampling of allocs and frees is handled internally using integer values, | |
| 28 // not floating point values. This is the integer value that represents a 100% | |
| 29 // sampling rate. See |g_sampling_factor|. | |
| 30 const int kMaxSamplingFactor = 256; | |
| 31 | |
| 32 // Create an object of this class to store the current new/delete hooks and | |
| 33 // then remove them. When this object goes out of scope, it will automatically | |
| 34 // restore the original hooks if they existed. | |
| 35 // | |
| 36 // If multiple instances of this class are created and there are hooks | |
| 37 // registered, only the first object will save and restore the hook functions. | |
| 38 // The others will have no effect. However, all concurrent instances MUST be | |
| 39 // destroyed in reverse relative to their instantiation. | |
| 40 // | |
| 41 // This is useful in situations such as: | |
| 42 // - Calling alloc or free from within a hook function, which would otherwise | |
| 43 // result in recursive hook calls. | |
| 44 // - Calling LOG() when |g_lock| is being held, as LOG will call malloc, which | |
| 45 // calls NewHook(), which then attempts to acquire the lock, resulting in it | |
| 46 // being blocked. | |
| 47 class MallocHookDisabler { | |
|
Primiano Tucci (use gerrit)
2016/03/01 18:54:37
Hmm the design of this seems very prone to races.
Simon Que
2016/03/01 19:09:53
I am aware that uninstalling and reinstalling the
| |
| 48 public: | |
| 49 MallocHookDisabler() | |
| 50 : new_hook_(base::allocator::SetSingleAllocHook(nullptr)), | |
| 51 delete_hook_(base::allocator::SetSingleFreeHook(nullptr)) {} | |
| 52 | |
| 53 ~MallocHookDisabler() { | |
| 54 if (new_hook_) | |
| 55 base::allocator::SetSingleAllocHook(new_hook_); | |
| 56 if (delete_hook_) | |
| 57 base::allocator::SetSingleFreeHook(delete_hook_); | |
| 58 } | |
| 59 | |
| 60 private: | |
| 61 base::allocator::AllocHookFunc new_hook_; | |
| 62 base::allocator::FreeHookFunc delete_hook_; | |
| 63 | |
| 64 DISALLOW_COPY_AND_ASSIGN(MallocHookDisabler); | |
| 65 }; | |
| 66 | |
| 67 // For storing the address range of the Chrome binary in memory. | |
| 68 struct MappingInfo { | |
| 69 uintptr_t addr; | |
| 70 size_t size; | |
| 71 }; | |
| 72 | |
| 73 // Disables hooks before calling new. | |
| 74 void* InternalAlloc(size_t size) { | |
| 75 MallocHookDisabler disabler; | |
| 76 return new char[size]; | |
| 77 } | |
| 78 | |
| 79 // Disables hooks before calling delete. | |
| 80 void InternalFree(void* ptr, size_t /* size */) { | |
| 81 MallocHookDisabler disabler; | |
| 82 delete[] reinterpret_cast<char*>(ptr); | |
| 83 } | |
| 84 | |
| 85 // Callback for dl_iterate_phdr() to find the Chrome binary mapping. | |
| 86 int IterateLoadedObjects(struct dl_phdr_info* shared_object, | |
|
Primiano Tucci (use gerrit)
2016/03/01 18:54:37
I cannot speak for CrOs, but on other platforms th
Simon Que
2016/03/01 19:09:53
I'll get someone from CrOS to take a look. Meanwhi
Simon Que
2016/03/02 06:25:09
Done.
| |
| 87 size_t /* size */, | |
| 88 void* data) { | |
| 89 for (int i = 0; i < shared_object->dlpi_phnum; i++) { | |
| 90 // Find the ELF segment header that contains the actual code of the Chrome | |
| 91 // binary. | |
| 92 const ElfW(Phdr)& segment_header = shared_object->dlpi_phdr[i]; | |
| 93 if (segment_header.p_type == SHT_PROGBITS && segment_header.p_offset == 0 && | |
| 94 data) { | |
| 95 MappingInfo* mapping = static_cast<MappingInfo*>(data); | |
| 96 | |
| 97 // Make sure the fields in the ELF header and MappingInfo have the | |
| 98 // same size. | |
| 99 static_assert(sizeof(mapping->addr) == sizeof(shared_object->dlpi_addr), | |
| 100 "Integer size mismatch between MappingInfo::addr and " | |
| 101 "dl_phdr_info::dlpi_addr."); | |
| 102 static_assert(sizeof(mapping->size) == sizeof(segment_header.p_offset), | |
| 103 "Integer size mismatch between MappingInfo::size and " | |
| 104 "ElfW(Phdr)::p_memsz."); | |
| 105 | |
| 106 mapping->addr = shared_object->dlpi_addr + segment_header.p_offset; | |
| 107 mapping->size = segment_header.p_memsz; | |
| 108 return 1; | |
| 109 } | |
| 110 } | |
| 111 return 0; | |
| 112 } | |
| 113 | |
| 114 // Convert a pointer to a hash value. Returns only the upper eight bits. | |
| 115 inline uint64_t PointerToHash(const void* ptr) { | |
|
Primiano Tucci (use gerrit)
2016/03/01 18:54:37
just doublecheckin, do you know that we have a Sup
Simon Que
2016/03/01 19:09:53
Yes. I plan to further simplify this function by r
Primiano Tucci (use gerrit)
2016/03/01 21:10:28
ok sounds like you have a plan here.
Simon Que
2016/03/02 06:25:09
Done.
| |
| 116 // The input data is the pointer address, not the location in memory pointed | |
| 117 // to by the pointer. | |
| 118 // The multiplier is taken from Farmhash code: | |
| 119 // https://github.com/google/farmhash/blob/master/src/farmhash.cc | |
| 120 const uint64_t kMultiplier = 0x9ddfea08eb382d69ULL; | |
| 121 uint64_t value = reinterpret_cast<uint64_t>(ptr) * kMultiplier; | |
| 122 return value >> 56; | |
| 123 } | |
| 124 | |
| 125 LeakDetector* g_instance = nullptr; | |
| 126 | |
| 127 } // namespace | |
| 128 | |
| 11 LeakDetector::LeakReport::LeakReport() {} | 129 LeakDetector::LeakReport::LeakReport() {} |
| 12 | 130 |
| 13 LeakDetector::LeakReport::~LeakReport() {} | 131 LeakDetector::LeakReport::~LeakReport() {} |
| 14 | 132 |
| 15 LeakDetector::LeakDetector(float sampling_rate, | 133 LeakDetector::LeakDetector(float sampling_rate, |
| 16 size_t max_call_stack_unwind_depth, | 134 size_t max_call_stack_unwind_depth, |
| 17 uint64_t analysis_interval_bytes, | 135 uint64_t analysis_interval_bytes, |
| 18 uint32_t size_suspicion_threshold, | 136 uint32_t size_suspicion_threshold, |
| 19 uint32_t call_stack_suspicion_threshold) | 137 uint32_t call_stack_suspicion_threshold) |
| 20 : weak_factory_(this) { | 138 : total_alloc_size_(0), |
| 21 // TODO(sque): Connect this class to LeakDetectorImpl and base::allocator. | 139 last_analysis_alloc_size_(0), |
| 140 analysis_interval_bytes_(analysis_interval_bytes), | |
| 141 max_call_stack_unwind_depth_(max_call_stack_unwind_depth), | |
| 142 sampling_factor_(sampling_rate * kMaxSamplingFactor), | |
| 143 weak_factory_(this) { | |
| 144 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 145 CHECK(!g_instance) << "Cannot instantiate multiple instances of this class."; | |
| 146 | |
| 147 // Locate the Chrome binary mapping info. | |
| 148 MappingInfo mapping; | |
| 149 dl_iterate_phdr(IterateLoadedObjects, &mapping); | |
| 150 binary_mapping_addr_ = mapping.addr; | |
| 151 binary_mapping_size_ = mapping.size; | |
| 152 | |
| 153 CustomAllocator::Initialize(&InternalAlloc, &InternalFree); | |
| 154 impl_.reset(new LeakDetectorImpl(mapping.addr, mapping.size, | |
| 155 size_suspicion_threshold, | |
| 156 call_stack_suspicion_threshold)); | |
| 157 | |
| 158 // Register allocator hook functions. | |
| 159 if (base::allocator::SetSingleAllocHook(&AllocHook) != nullptr || | |
| 160 base::allocator::SetSingleFreeHook(&FreeHook) != nullptr) { | |
| 161 MallocHookDisabler disabler; | |
| 162 LOG(FATAL) << "Overwrote existing callback."; | |
| 163 } else if (base::allocator::GetSingleAllocHook() != &AllocHook || | |
| 164 base::allocator::GetSingleFreeHook() != &FreeHook) { | |
| 165 MallocHookDisabler disabler; | |
| 166 LOG(FATAL) << "Failed to register free callback."; | |
| 167 return; | |
| 168 } | |
| 169 | |
| 170 g_instance = this; | |
| 22 } | 171 } |
| 23 | 172 |
| 24 LeakDetector::~LeakDetector() {} | 173 LeakDetector::~LeakDetector() { |
| 174 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 175 g_instance = nullptr; | |
| 176 | |
| 177 // Unregister allocator hook functions. | |
| 178 if (base::allocator::GetSingleAllocHook() == &AllocHook) | |
| 179 base::allocator::SetSingleAllocHook(nullptr); | |
| 180 if (base::allocator::GetSingleFreeHook() == &FreeHook) | |
| 181 base::allocator::SetSingleFreeHook(nullptr); | |
| 182 | |
| 183 impl_.reset(); | |
| 184 if (!CustomAllocator::Shutdown()) { | |
| 185 LOG(ERROR) << "Memory leak in leak detector, allocated objects remain."; | |
| 186 } | |
| 187 } | |
| 25 | 188 |
| 26 void LeakDetector::AddObserver(Observer* observer) { | 189 void LeakDetector::AddObserver(Observer* observer) { |
| 27 DCHECK(thread_checker_.CalledOnValidThread()); | 190 DCHECK(thread_checker_.CalledOnValidThread()); |
| 28 observers_.AddObserver(observer); | 191 observers_.AddObserver(observer); |
| 29 } | 192 } |
| 30 | 193 |
| 31 void LeakDetector::RemoveObserver(Observer* observer) { | 194 void LeakDetector::RemoveObserver(Observer* observer) { |
| 32 DCHECK(thread_checker_.CalledOnValidThread()); | 195 DCHECK(thread_checker_.CalledOnValidThread()); |
| 33 observers_.RemoveObserver(observer); | 196 observers_.RemoveObserver(observer); |
| 34 } | 197 } |
| 35 | 198 |
| 199 // static | |
| 200 void LeakDetector::AllocHook(const void* ptr, size_t size) { | |
| 201 CHECK(g_instance); | |
|
Primiano Tucci (use gerrit)
2016/03/01 21:10:28
How are you preventing to hit this check when an a
Simon Que
2016/03/02 06:25:09
Done
| |
| 202 | |
| 203 { | |
| 204 base::AutoLock lock(g_instance->lock_); | |
| 205 g_instance->total_alloc_size_ += size; | |
| 206 | |
| 207 if (!ptr || !g_instance->ShouldSample(ptr)) | |
| 208 return; | |
| 209 } | |
| 210 | |
| 211 auto& impl = g_instance->impl_; | |
| 212 | |
| 213 // Must be modified under lock as it uses the shared resources of | |
| 214 // CustomAllocator. | |
| 215 InternalVector<void*> stack; | |
| 216 | |
| 217 // Take the stack trace outside the critical section. | |
| 218 // |LeakDetectorImpl::ShouldGetStackTraceForSize()| is const; there is no | |
|
Primiano Tucci (use gerrit)
2016/03/01 21:10:28
what happens if you are here while you destroy the
| |
| 219 // need for a lock. | |
| 220 int depth = 0; | |
| 221 if (impl->ShouldGetStackTraceForSize(size)) { | |
| 222 { | |
| 223 base::AutoLock lock(g_instance->lock_); | |
| 224 stack.resize(g_instance->max_call_stack_unwind_depth_); | |
| 225 } | |
| 226 depth = base::allocator::GetCallStack(stack.data(), stack.size(), 0); | |
| 227 } | |
| 228 | |
| 229 g_instance->RecordAlloc(ptr, size, depth, stack.data()); | |
|
Primiano Tucci (use gerrit)
2016/03/01 21:10:28
in the header of this class you say that this clas
| |
| 230 | |
| 231 { | |
| 232 base::AutoLock lock(g_instance->lock_); | |
| 233 | |
| 234 // InternalVectors must be cleaned up under lock, so we can't wait for them | |
| 235 // to go out of scope. | |
| 236 // std::vector::clear() still leaves reserved memory inside that will be | |
| 237 // cleaned up by the destructor when it goes out of scope. And | |
| 238 // vector::shrink_to_fit() is not allowed to be used yet. Instead swap | |
| 239 // out the contents to a local container that is cleaned up when it goes | |
| 240 // out of scope. | |
| 241 InternalVector<void*> dummy_stack; | |
| 242 stack.swap(dummy_stack); | |
| 243 } | |
| 244 } | |
| 245 | |
| 246 // static | |
| 247 void LeakDetector::FreeHook(const void* ptr) { | |
| 248 if (!ptr || !g_instance->ShouldSample(ptr)) | |
| 249 return; | |
| 250 | |
| 251 base::AutoLock lock(g_instance->lock_); | |
| 252 g_instance->impl_->RecordFree(ptr); | |
| 253 } | |
| 254 | |
| 255 inline bool LeakDetector::ShouldSample(const void* ptr) const { | |
| 256 return PointerToHash(ptr) < static_cast<uint64_t>(sampling_factor_); | |
| 257 } | |
| 258 | |
| 259 void LeakDetector::RecordAlloc(const void* ptr, | |
| 260 size_t size, | |
| 261 int depth, | |
| 262 void** call_stack) { | |
| 263 // This should be modified only with a lock because it uses the shared | |
| 264 // resources in CustomAllocator. | |
| 265 InternalVector<InternalLeakReport> leak_reports; | |
| 266 | |
| 267 { | |
| 268 base::AutoLock lock(lock_); | |
| 269 impl_->RecordAlloc(ptr, size, depth, call_stack); | |
| 270 | |
| 271 // Check for leaks after |analysis_interval_bytes_| bytes have been | |
| 272 // allocated since the last time that was done. Should be called with a lock | |
| 273 // since it: | |
| 274 // - Modifies the global variable |g_last_analysis_alloc_size|. | |
| 275 // - Updates internals of |*impl|. | |
| 276 // - Possibly generates a vector of LeakReports using CustomAllocator. | |
| 277 if (total_alloc_size_ > | |
| 278 last_analysis_alloc_size_ + analysis_interval_bytes_) { | |
| 279 // Try to maintain regular intervals of size |analysis_interval_bytes_|. | |
| 280 last_analysis_alloc_size_ = | |
| 281 total_alloc_size_ - total_alloc_size_ % analysis_interval_bytes_; | |
| 282 impl_->TestForLeaks(&leak_reports); | |
| 283 } | |
| 284 } | |
| 285 | |
| 286 std::vector<LeakReport> leak_reports_for_observers; | |
| 287 leak_reports_for_observers.reserve(leak_reports.size()); | |
| 288 for (const InternalLeakReport& report : leak_reports) { | |
| 289 leak_reports_for_observers.push_back(LeakReport()); | |
| 290 | |
| 291 LeakReport* new_report = &leak_reports_for_observers.back(); | |
| 292 new_report->alloc_size_bytes = report.alloc_size_bytes(); | |
| 293 if (!report.call_stack().empty()) { | |
| 294 new_report->call_stack.resize(report.call_stack().size()); | |
| 295 memcpy(new_report->call_stack.data(), report.call_stack().data(), | |
| 296 report.call_stack().size() * sizeof(report.call_stack()[0])); | |
| 297 } | |
| 298 } | |
| 299 | |
| 300 { | |
| 301 base::AutoLock lock(lock_); | |
| 302 InternalVector<InternalLeakReport> dummy_leak_reports; | |
| 303 leak_reports.swap(dummy_leak_reports); | |
| 304 } | |
| 305 | |
| 306 // Pass leak reports to observers. The observers must be called outside of the | |
| 307 // locked area to avoid slowdown. | |
| 308 NotifyObservers(leak_reports_for_observers); | |
| 309 } | |
| 310 | |
| 36 void LeakDetector::NotifyObservers(const std::vector<LeakReport>& reports) { | 311 void LeakDetector::NotifyObservers(const std::vector<LeakReport>& reports) { |
| 312 if (reports.empty()) | |
| 313 return; | |
| 314 | |
| 37 if (!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)) { | 315 if (!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)) { |
| 38 content::BrowserThread::PostTask( | 316 content::BrowserThread::PostTask( |
| 39 content::BrowserThread::UI, FROM_HERE, | 317 content::BrowserThread::UI, FROM_HERE, |
| 40 base::Bind(&LeakDetector::NotifyObservers, weak_factory_.GetWeakPtr(), | 318 base::Bind(&LeakDetector::NotifyObservers, weak_factory_.GetWeakPtr(), |
| 41 reports)); | 319 reports)); |
| 42 return; | 320 return; |
| 43 } | 321 } |
| 44 | 322 |
| 45 for (const LeakReport& report : reports) { | 323 for (const LeakReport& report : reports) { |
| 46 FOR_EACH_OBSERVER(Observer, observers_, OnLeakFound(report)); | 324 FOR_EACH_OBSERVER(Observer, observers_, OnLeakFound(report)); |
| 47 } | 325 } |
| 48 } | 326 } |
| 49 | 327 |
| 50 } // namespace metrics | 328 } // namespace metrics |
| OLD | NEW |