Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(44)

Side by Side Diff: components/metrics/leak_detector/leak_detector.cc

Issue 1665553002: metrics: Connect leak detector to allocator (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Add missing include Created 4 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698