OLD | NEW |
---|---|
(Empty) | |
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 | |
3 // found in the LICENSE file. | |
4 | |
5 #include "components/metrics/leak_detector/leak_detector.h" | |
6 | |
7 #include <link.h> | |
8 #include <stdint.h> | |
9 #include <unistd.h> | |
10 | |
11 #include <algorithm> | |
12 #include <list> | |
13 #include <new> | |
14 | |
15 #include "base/allocator/allocator_extension.h" | |
16 #include "base/logging.h" | |
17 #include "base/synchronization/lock.h" | |
18 #include "components/metrics/leak_detector/leak_detector_impl.h" | |
19 | |
20 namespace metrics { | |
21 | |
22 using LeakReport = LeakDetector::LeakReport; | |
23 using leak_detector::CustomAllocator; | |
24 using leak_detector::LeakDetectorImpl; | |
25 using InternalLeakReport = LeakDetectorImpl::LeakReport; | |
26 template <typename T> | |
27 using InternalVector = LeakDetectorImpl::InternalVector<T>; | |
28 | |
29 namespace { | |
30 | |
31 // For storing the address range of the Chrome binary in memory. | |
32 struct MappingInfo { | |
33 uintptr_t addr; | |
34 size_t size; | |
35 } chrome_mapping; | |
Simon Que
2016/02/09 22:45:54
Member of LeakDetector.
| |
36 | |
37 // Create an object of this class to store the current new/delete hooks and | |
38 // then remove them. When this object goes out of scope, it will automatically | |
39 // restore the original hooks if they existed. | |
40 // | |
41 // If multiple instances of this class are created and there are hooks | |
42 // registered, only the first object will save and restore the hook functions. | |
43 // The others will have no effect. However, all concurrent instances MUST be | |
44 // destroyed in reverse relative to their instantiation. | |
45 // | |
46 // This is useful in situations such as: | |
47 // - Calling alloc or free from within a hook function, which would otherwise | |
48 // result in recursive hook calls. | |
49 // - Calling LOG() when |g_lock| is being held, as LOG will call malloc, which | |
50 // calls NewHook(), which then attempts to acquire the lock, resulting in it | |
51 // being blocked. | |
52 class MallocHookDisabler { | |
53 public: | |
54 MallocHookDisabler() | |
55 : new_hook_(base::allocator::SetSingleAllocHook(nullptr)), | |
56 delete_hook_(base::allocator::SetSingleFreeHook(nullptr)) {} | |
57 | |
58 ~MallocHookDisabler() { | |
59 if (new_hook_) | |
60 base::allocator::SetSingleAllocHook(new_hook_); | |
61 if (delete_hook_) | |
62 base::allocator::SetSingleFreeHook(delete_hook_); | |
63 } | |
64 | |
65 private: | |
66 base::allocator::AllocHookFunc new_hook_; | |
67 base::allocator::FreeHookFunc delete_hook_; | |
68 | |
69 DISALLOW_COPY_AND_ASSIGN(MallocHookDisabler); | |
70 }; | |
71 | |
72 // Use a lock for controlling access to shared resources. | |
73 base::Lock* g_lock; | |
74 | |
75 // The currently active LeakDetector object. There must be only one created at a | |
76 // time. | |
77 LeakDetector* g_leak_detector; | |
78 | |
79 // Points to the active instance of the leak detector logic. | |
80 // Modify this only when locked. | |
81 LeakDetectorImpl* g_leak_detector_impl; | |
Simon Que
2016/02/09 22:45:53
Member of LeakDetector.
| |
82 | |
83 // Keeps track of the total number of bytes allocated, computed before sampling. | |
84 uint64_t g_total_alloc_size; | |
85 | |
86 // The value of |g_total_alloc_size| the last time there was a leak analysis, | |
87 // rounded down to the nearest multiple of |g_analysis_interval_bytes|. | |
88 uint64_t g_last_analysis_alloc_size; | |
Simon Que
2016/02/09 22:45:53
Member of LeakDetector.
| |
89 | |
90 // The sampling of allocs and frees is handled internally using integer values, | |
91 // not floating point values. This is the integer value that represents a 100% | |
92 // sampling rate. See |g_sampling_factor|. | |
93 const int kMaxSamplingFactor = 256; | |
94 | |
95 // Pseudorandomly sample a fraction of the incoming allocations and frees, about | |
96 // |g_sampling_factor| / |kMaxSamplingFactor|. Setting to 0 means no | |
97 // allocs/frees are sampled. Setting to 256 or more means all allocs/frees are | |
98 // sampled. | |
99 int g_sampling_factor; | |
100 | |
101 // The max number of call stack frames to unwind. | |
102 int g_max_stack_depth; | |
Simon Que
2016/02/09 22:45:54
Member of LeakDetector.
| |
103 | |
104 // Perform a leak analysis each time this many bytes have been allocated since | |
105 // the previous analysis. | |
106 uint64_t g_analysis_interval_bytes; | |
Simon Que
2016/02/09 22:45:53
Member of LeakDetector.
| |
107 | |
108 // A possible leak should be suspected this many times to take action on it. | |
109 // For size analysis, the action is to start profiling by call stack. | |
110 // For call stack analysis, the action is to generate a leak report. | |
111 int g_size_suspicion_threshold; | |
Simon Que
2016/02/09 22:45:53
Delete
| |
112 int g_call_stack_suspicion_threshold; | |
Simon Que
2016/02/09 22:45:53
Delete
| |
113 | |
114 // Callback for dl_iterate_phdr() to find the Chrome binary mapping. | |
115 int IterateLoadedObjects(struct dl_phdr_info* shared_object, | |
116 size_t /* size */, | |
117 void* data) { | |
118 for (int i = 0; i < shared_object->dlpi_phnum; i++) { | |
119 // Find the ELF segment header that contains the actual code of the Chrome | |
120 // binary. | |
121 const ElfW(Phdr)& segment_header = shared_object->dlpi_phdr[i]; | |
122 if (segment_header.p_type == SHT_PROGBITS && segment_header.p_offset == 0 && | |
123 data) { | |
124 MappingInfo* mapping = static_cast<MappingInfo*>(data); | |
125 | |
126 // Make sure the fields in the ELF header and MappingInfo have the | |
127 // same size. | |
128 static_assert(sizeof(mapping->addr) == sizeof(shared_object->dlpi_addr), | |
129 "Integer size mismatch between MappingInfo::addr and " | |
130 "dl_phdr_info::dlpi_addr."); | |
131 static_assert(sizeof(mapping->size) == sizeof(segment_header.p_offset), | |
132 "Integer size mismatch between MappingInfo::size and " | |
133 "ElfW(Phdr)::p_memsz."); | |
134 | |
135 mapping->addr = shared_object->dlpi_addr + segment_header.p_offset; | |
136 mapping->size = segment_header.p_memsz; | |
137 return 1; | |
138 } | |
139 } | |
140 return 0; | |
141 } | |
142 | |
143 // Convert a pointer to a hash value. Returns only the upper eight bits. | |
144 inline uint64_t PointerToHash(const void* ptr) { | |
145 // The input data is the pointer address, not the location in memory pointed | |
146 // to by the pointer. | |
147 // The multiplier is taken from Farmhash code: | |
148 // https://github.com/google/farmhash/blob/master/src/farmhash.cc | |
149 const uint64_t kMultiplier = 0x9ddfea08eb382d69ULL; | |
150 uint64_t value = reinterpret_cast<uint64_t>(ptr) * kMultiplier; | |
151 return value >> 56; | |
152 } | |
153 | |
154 // Uses PointerToHash() to pseudorandomly sample |ptr|. | |
155 inline bool ShouldSample(const void* ptr) { | |
156 return PointerToHash(ptr) < static_cast<uint64_t>(g_sampling_factor); | |
157 } | |
158 | |
159 // Disables hooks before calling new. | |
160 void* InternalAlloc(size_t size) { | |
161 MallocHookDisabler disabler; | |
162 return new char[size]; | |
163 } | |
164 | |
165 // Disables hooks before calling delete. | |
166 void InternalFree(void* ptr, size_t /* size */) { | |
167 MallocHookDisabler disabler; | |
168 delete[] reinterpret_cast<char*>(ptr); | |
169 } | |
170 | |
171 // Callback for allocator allocations. | |
172 void NewHook(const void* ptr, size_t size) { | |
173 // Must be modified under lock, although the data stored in the internal | |
174 // allocated memory needn't be. | |
175 InternalVector<void*> stack; | |
176 | |
177 { | |
178 base::AutoLock lock(*g_lock); | |
179 g_total_alloc_size += size; | |
180 | |
181 if (!ShouldSample(ptr) || !ptr || !g_leak_detector_impl) | |
182 return; | |
183 | |
184 stack.resize(g_max_stack_depth, nullptr); | |
185 } | |
186 | |
187 // Take the stack trace outside the critical section. | |
188 // |g_leak_detector_impl->ShouldGetStackTraceForSize()| is const; there is no | |
189 // need for a lock. | |
190 int depth = 0; | |
191 if (g_leak_detector_impl->ShouldGetStackTraceForSize(size)) { | |
192 depth = base::allocator::GetCallStack(stack.data(), g_max_stack_depth, 0); | |
193 } | |
194 | |
195 // This should be modified only with a lock because it uses the shared | |
196 // resources in CustomAllocator. | |
197 InternalVector<InternalLeakReport> leak_reports; | |
198 | |
199 { | |
200 base::AutoLock lock(*g_lock); | |
201 g_leak_detector_impl->RecordAlloc(ptr, size, depth, stack.data()); | |
202 | |
203 // Check for leaks after |g_analysis_interval_bytes| bytes have been | |
204 // allocated since the last time that was done. Should be called with a lock | |
205 // since it: | |
206 // - Modifies the global variable |g_last_analysis_alloc_size|. | |
207 // - Updates internals of |g_leak_detector_impl|. | |
208 // - Possibly generates a vector of LeakReports using CustomAllocator. | |
209 if (g_total_alloc_size > | |
210 g_last_analysis_alloc_size + g_analysis_interval_bytes) { | |
211 // Try to maintain regular intervals of size |g_analysis_interval_bytes|. | |
212 g_last_analysis_alloc_size = | |
213 g_total_alloc_size - g_total_alloc_size % g_analysis_interval_bytes; | |
214 g_leak_detector_impl->TestForLeaks(&leak_reports); | |
215 } | |
216 } | |
217 | |
218 std::vector<LeakReport> leak_reports_for_observers; | |
219 leak_reports_for_observers.reserve(leak_reports.size()); | |
220 for (const InternalLeakReport& report : leak_reports) { | |
221 leak_reports_for_observers.resize(leak_reports_for_observers.size() + 1); | |
222 LeakReport* new_report = &leak_reports_for_observers.back(); | |
223 new_report->alloc_size_bytes = report.alloc_size_bytes(); | |
224 if (!report.call_stack().empty()) { | |
225 new_report->call_stack.resize(report.call_stack().size()); | |
226 memcpy(new_report->call_stack.data(), report.call_stack().data(), | |
227 report.call_stack().size() * sizeof(report.call_stack()[0])); | |
228 } | |
229 } | |
230 | |
231 { | |
232 base::AutoLock lock(*g_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<InternalLeakReport> dummy_leak_reports; | |
242 leak_reports.swap(dummy_leak_reports); | |
243 | |
244 InternalVector<void*> dummy_stack; | |
245 stack.swap(dummy_stack); | |
246 } | |
247 | |
248 // Pass leak reports to observers. The observers must be called outside of the | |
249 // locked area to avoid slowdown. | |
250 g_leak_detector->NotifyObservers(leak_reports_for_observers); | |
251 } | |
252 | |
253 // Callback for allocator frees. | |
254 void DeleteHook(const void* ptr) { | |
255 if (!ShouldSample(ptr) || !ptr || !g_leak_detector_impl) | |
256 return; | |
257 | |
258 base::AutoLock lock(*g_lock); | |
259 g_leak_detector_impl->RecordFree(ptr); | |
260 } | |
261 | |
262 } // namespace | |
263 | |
264 namespace internal { | |
265 | |
266 bool Shutdown(); | |
267 | |
268 // Returns true if the internal leak detector has been initialized. | |
269 bool IsInitialized() { | |
270 return g_leak_detector_impl; | |
271 } | |
272 | |
273 // Initialize internal leak detector with the current stored parameters. Returns | |
274 // true upon success. The internal leak detector can only be initialized once. | |
275 bool Initialize() { | |
Simon Que
2016/02/09 22:45:53
Move to LeakDetector.
| |
276 if (IsInitialized()) { | |
277 MallocHookDisabler disabler; | |
278 LOG(ERROR) << "Leak detector is already initialized!"; | |
279 return false; | |
280 } | |
281 | |
282 // Locate the Chrome binary mapping info. | |
283 dl_iterate_phdr(IterateLoadedObjects, &chrome_mapping); | |
284 | |
285 // Create a new lock. | |
286 g_lock = new base::Lock; | |
287 | |
288 bool success = true; | |
289 { | |
290 // Limit the lock to a local scope because Shutdown() must be run with it | |
291 // unlocked. | |
292 base::AutoLock lock(*g_lock); | |
293 | |
294 if (CustomAllocator::IsInitialized()) { | |
295 LOG(ERROR) << "Custom allocator can only be initialized once!"; | |
296 return false; | |
297 } | |
298 g_total_alloc_size = 0; | |
299 CustomAllocator::Initialize(&InternalAlloc, &InternalFree); | |
300 | |
301 g_leak_detector_impl = | |
302 new (CustomAllocator::Allocate(sizeof(LeakDetectorImpl))) | |
303 LeakDetectorImpl(chrome_mapping.addr, chrome_mapping.size, | |
304 g_size_suspicion_threshold, | |
305 g_call_stack_suspicion_threshold); | |
306 | |
307 if (base::allocator::SetSingleAllocHook(&NewHook) != nullptr || | |
308 base::allocator::SetSingleFreeHook(&DeleteHook) != nullptr) { | |
309 MallocHookDisabler disabler; | |
310 LOG(ERROR) << "Overwrote existing callback."; | |
311 success = false; | |
312 } else if (base::allocator::GetSingleAllocHook() != &NewHook || | |
313 base::allocator::GetSingleFreeHook() != &DeleteHook) { | |
314 MallocHookDisabler disabler; | |
315 LOG(ERROR) << "Failed to register free callback."; | |
316 success = false; | |
317 } | |
318 } | |
319 | |
320 if (!success) | |
321 Shutdown(); | |
322 | |
323 return success; | |
324 } | |
325 | |
326 // Initialize internal leak detector with the given parameters, which are first | |
327 // stored in the internal parameter variables. | |
328 bool Initialize(int sampling_factor, | |
Simon Que
2016/02/09 22:45:53
Move to LeakDetector.
| |
329 int max_stack_depth, | |
330 uint64_t analysis_interval_bytes, | |
331 int size_suspicion_threshold, | |
332 int call_stack_suspicion_threshold) { | |
333 if (IsInitialized()) { | |
334 MallocHookDisabler disabler; | |
335 LOG(ERROR) << "Leak detector is already initialized!"; | |
336 return false; | |
337 } | |
338 | |
339 // Override default values. | |
340 g_sampling_factor = sampling_factor; | |
341 g_max_stack_depth = max_stack_depth; | |
342 g_analysis_interval_bytes = analysis_interval_bytes; | |
343 g_size_suspicion_threshold = size_suspicion_threshold; | |
344 g_call_stack_suspicion_threshold = call_stack_suspicion_threshold; | |
345 | |
346 return Initialize(); | |
347 } | |
348 | |
349 // Shut down the internal leak detector. | |
350 bool Shutdown() { | |
Simon Que
2016/02/09 22:45:53
Move to LeakDetector.
| |
351 if (!IsInitialized()) { | |
352 LOG(ERROR) << "Leak detector is not initialized!"; | |
353 return false; | |
354 } | |
355 | |
356 { | |
357 base::AutoLock lock(*g_lock); | |
358 | |
359 if (base::allocator::GetSingleAllocHook() == &NewHook) | |
360 base::allocator::SetSingleAllocHook(nullptr); | |
361 if (base::allocator::GetSingleFreeHook() == &DeleteHook) | |
362 base::allocator::SetSingleFreeHook(nullptr); | |
363 | |
364 g_leak_detector_impl->~LeakDetectorImpl(); | |
365 CustomAllocator::Free(g_leak_detector_impl, sizeof(LeakDetectorImpl)); | |
366 g_leak_detector_impl = nullptr; | |
367 | |
368 if (!CustomAllocator::Shutdown()) { | |
369 LOG(ERROR) << "Memory leak in leak detector, allocated objects remain."; | |
370 return false; | |
371 } | |
372 } | |
373 | |
374 // Deallocate the lock. | |
375 delete g_lock; | |
376 | |
377 return true; | |
378 } | |
379 | |
380 } // namespace internal | |
381 | |
382 LeakDetector::LeakReport::LeakReport() {} | |
383 | |
384 LeakDetector::LeakReport::~LeakReport() {} | |
385 | |
386 LeakDetector::LeakDetector() : LeakDetector(1.0f, 4, 32 * 1024 * 1024, 4, 4) {} | |
387 | |
388 LeakDetector::LeakDetector(double sampling_ratio, | |
389 int max_stack_depth, | |
390 uint64_t analysis_interval_bytes, | |
391 int size_suspicion_threshold, | |
392 int call_stack_suspicion_threshold) { | |
393 g_leak_detector = this; | |
394 int sampling_factor = | |
395 static_cast<double>(kMaxSamplingFactor) * sampling_ratio; | |
396 | |
397 CHECK(internal::Initialize(sampling_factor, max_stack_depth, | |
398 analysis_interval_bytes, size_suspicion_threshold, | |
399 call_stack_suspicion_threshold)); | |
400 } | |
401 | |
402 LeakDetector::~LeakDetector() { | |
403 CHECK(internal::Shutdown()); | |
404 g_leak_detector = nullptr; | |
405 } | |
406 | |
407 bool LeakDetector::AddObserver(Observer* observer) { | |
408 observers_.push_back(observer); | |
409 return true; | |
410 } | |
411 | |
412 bool LeakDetector::RemoveObserver(Observer* observer) { | |
413 auto iter = std::find(observers_.begin(), observers_.end(), observer); | |
414 if (iter == observers_.end()) | |
415 return false; | |
416 observers_.erase(iter); | |
417 return true; | |
418 } | |
419 | |
420 void LeakDetector::NotifyObservers(const std::vector<LeakReport>& reports) { | |
421 for (LeakDetector::Observer* observer : observers_) { | |
422 for (const LeakReport& report : reports) { | |
423 observer->OnLeakFound(report); | |
424 } | |
425 } | |
426 } | |
427 | |
428 } // namespace metrics | |
OLD | NEW |