blob: 43c21e5ba648bc69bd4dba8ce3ba153d02915c67 [file] [log] [blame]
ssid07386852015-04-14 15:32:371// 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 "base/trace_event/malloc_dump_provider.h"
6
avibd1ed052015-12-24 04:03:447#include <stddef.h>
8
brettw1ce49f62017-04-27 19:42:329#include <unordered_map>
10
avibd1ed052015-12-24 04:03:4411#include "base/allocator/allocator_extension.h"
primianofd9072162016-03-25 02:13:2812#include "base/allocator/allocator_shim.h"
13#include "base/allocator/features.h"
dskibad4a5e9822017-06-19 21:10:1514#include "base/bind.h"
siggiba33ec02016-08-26 16:13:0715#include "base/debug/profiler.h"
primianofd9072162016-03-25 02:13:2816#include "base/trace_event/heap_profiler_allocation_context.h"
17#include "base/trace_event/heap_profiler_allocation_context_tracker.h"
dskibad4a5e9822017-06-19 21:10:1518#include "base/trace_event/heap_profiler_allocation_register.h"
19#include "base/trace_event/heap_profiler_event_writer.h"
avibd1ed052015-12-24 04:03:4420#include "base/trace_event/process_memory_dump.h"
primianofd9072162016-03-25 02:13:2821#include "base/trace_event/trace_event_argument.h"
avibd1ed052015-12-24 04:03:4422#include "build/build_config.h"
23
ssid3aa02fe2015-11-07 16:15:0724#if defined(OS_MACOSX)
25#include <malloc/malloc.h>
26#else
ssid07386852015-04-14 15:32:3727#include <malloc.h>
ssid3aa02fe2015-11-07 16:15:0728#endif
siggi7bec59a2016-08-25 20:22:2629#if defined(OS_WIN)
30#include <windows.h>
31#endif
ssid07386852015-04-14 15:32:3732
ssid07386852015-04-14 15:32:3733namespace base {
34namespace trace_event {
35
primianofd9072162016-03-25 02:13:2836namespace {
primiano73228cd2017-05-25 15:16:0937#if BUILDFLAG(USE_ALLOCATOR_SHIM)
primianofd9072162016-03-25 02:13:2838
39using allocator::AllocatorDispatch;
40
erikcheneff0ecb2017-02-20 13:04:5041void* HookAlloc(const AllocatorDispatch* self, size_t size, void* context) {
primianofd9072162016-03-25 02:13:2842 const AllocatorDispatch* const next = self->next;
erikcheneff0ecb2017-02-20 13:04:5043 void* ptr = next->alloc_function(next, size, context);
primianofd9072162016-03-25 02:13:2844 if (ptr)
45 MallocDumpProvider::GetInstance()->InsertAllocation(ptr, size);
46 return ptr;
47}
48
erikcheneff0ecb2017-02-20 13:04:5049void* HookZeroInitAlloc(const AllocatorDispatch* self,
50 size_t n,
51 size_t size,
52 void* context) {
primianofd9072162016-03-25 02:13:2853 const AllocatorDispatch* const next = self->next;
erikcheneff0ecb2017-02-20 13:04:5054 void* ptr = next->alloc_zero_initialized_function(next, n, size, context);
primianofd9072162016-03-25 02:13:2855 if (ptr)
56 MallocDumpProvider::GetInstance()->InsertAllocation(ptr, n * size);
57 return ptr;
58}
59
etiennebdc2b22eb2017-03-21 17:11:4360void* HookAllocAligned(const AllocatorDispatch* self,
61 size_t alignment,
62 size_t size,
63 void* context) {
primianofd9072162016-03-25 02:13:2864 const AllocatorDispatch* const next = self->next;
erikcheneff0ecb2017-02-20 13:04:5065 void* ptr = next->alloc_aligned_function(next, alignment, size, context);
primianofd9072162016-03-25 02:13:2866 if (ptr)
67 MallocDumpProvider::GetInstance()->InsertAllocation(ptr, size);
68 return ptr;
69}
70
erikcheneff0ecb2017-02-20 13:04:5071void* HookRealloc(const AllocatorDispatch* self,
72 void* address,
73 size_t size,
74 void* context) {
primianofd9072162016-03-25 02:13:2875 const AllocatorDispatch* const next = self->next;
erikcheneff0ecb2017-02-20 13:04:5076 void* ptr = next->realloc_function(next, address, size, context);
primianofd9072162016-03-25 02:13:2877 MallocDumpProvider::GetInstance()->RemoveAllocation(address);
78 if (size > 0) // realloc(size == 0) means free().
79 MallocDumpProvider::GetInstance()->InsertAllocation(ptr, size);
80 return ptr;
81}
82
erikcheneff0ecb2017-02-20 13:04:5083void HookFree(const AllocatorDispatch* self, void* address, void* context) {
primianofd9072162016-03-25 02:13:2884 if (address)
85 MallocDumpProvider::GetInstance()->RemoveAllocation(address);
86 const AllocatorDispatch* const next = self->next;
erikcheneff0ecb2017-02-20 13:04:5087 next->free_function(next, address, context);
primianofd9072162016-03-25 02:13:2888}
89
erikcheneff0ecb2017-02-20 13:04:5090size_t HookGetSizeEstimate(const AllocatorDispatch* self,
91 void* address,
92 void* context) {
siggi46e1b072016-09-09 16:43:3193 const AllocatorDispatch* const next = self->next;
erikcheneff0ecb2017-02-20 13:04:5094 return next->get_size_estimate_function(next, address, context);
siggi46e1b072016-09-09 16:43:3195}
96
erikchen0d0395a2017-02-02 06:16:2997unsigned HookBatchMalloc(const AllocatorDispatch* self,
98 size_t size,
99 void** results,
erikcheneff0ecb2017-02-20 13:04:50100 unsigned num_requested,
101 void* context) {
erikchen0d0395a2017-02-02 06:16:29102 const AllocatorDispatch* const next = self->next;
103 unsigned count =
erikcheneff0ecb2017-02-20 13:04:50104 next->batch_malloc_function(next, size, results, num_requested, context);
erikchen0d0395a2017-02-02 06:16:29105 for (unsigned i = 0; i < count; ++i) {
106 MallocDumpProvider::GetInstance()->InsertAllocation(results[i], size);
107 }
108 return count;
109}
110
111void HookBatchFree(const AllocatorDispatch* self,
112 void** to_be_freed,
erikcheneff0ecb2017-02-20 13:04:50113 unsigned num_to_be_freed,
114 void* context) {
erikchen0d0395a2017-02-02 06:16:29115 const AllocatorDispatch* const next = self->next;
116 for (unsigned i = 0; i < num_to_be_freed; ++i) {
117 MallocDumpProvider::GetInstance()->RemoveAllocation(to_be_freed[i]);
118 }
erikcheneff0ecb2017-02-20 13:04:50119 next->batch_free_function(next, to_be_freed, num_to_be_freed, context);
erikchen0d0395a2017-02-02 06:16:29120}
121
122void HookFreeDefiniteSize(const AllocatorDispatch* self,
123 void* ptr,
erikcheneff0ecb2017-02-20 13:04:50124 size_t size,
125 void* context) {
erikchen0d0395a2017-02-02 06:16:29126 if (ptr)
127 MallocDumpProvider::GetInstance()->RemoveAllocation(ptr);
128 const AllocatorDispatch* const next = self->next;
erikcheneff0ecb2017-02-20 13:04:50129 next->free_definite_size_function(next, ptr, size, context);
erikchen0d0395a2017-02-02 06:16:29130}
131
primianofd9072162016-03-25 02:13:28132AllocatorDispatch g_allocator_hooks = {
erikchen0d0395a2017-02-02 06:16:29133 &HookAlloc, /* alloc_function */
134 &HookZeroInitAlloc, /* alloc_zero_initialized_function */
etiennebdc2b22eb2017-03-21 17:11:43135 &HookAllocAligned, /* alloc_aligned_function */
erikchen0d0395a2017-02-02 06:16:29136 &HookRealloc, /* realloc_function */
137 &HookFree, /* free_function */
138 &HookGetSizeEstimate, /* get_size_estimate_function */
139 &HookBatchMalloc, /* batch_malloc_function */
140 &HookBatchFree, /* batch_free_function */
141 &HookFreeDefiniteSize, /* free_definite_size_function */
142 nullptr, /* next */
primianofd9072162016-03-25 02:13:28143};
primiano73228cd2017-05-25 15:16:09144#endif // BUILDFLAG(USE_ALLOCATOR_SHIM)
primianofd9072162016-03-25 02:13:28145
siggi7bec59a2016-08-25 20:22:26146#if defined(OS_WIN)
147// A structure containing some information about a given heap.
148struct WinHeapInfo {
siggi7bec59a2016-08-25 20:22:26149 size_t committed_size;
150 size_t uncommitted_size;
151 size_t allocated_size;
152 size_t block_count;
153};
154
kraynovad507292016-11-25 18:01:23155// NOTE: crbug.com/665516
156// Unfortunately, there is no safe way to collect information from secondary
157// heaps due to limitations and racy nature of this piece of WinAPI.
siggi82535f62016-12-06 22:29:03158void WinHeapMemoryDumpImpl(WinHeapInfo* crt_heap_info) {
siggi7bec59a2016-08-25 20:22:26159#if defined(SYZYASAN)
160 if (base::debug::IsBinaryInstrumented())
161 return;
162#endif
siggi82535f62016-12-06 22:29:03163
164 // Iterate through whichever heap our CRT is using.
165 HANDLE crt_heap = reinterpret_cast<HANDLE>(_get_heap_handle());
166 ::HeapLock(crt_heap);
kraynovad507292016-11-25 18:01:23167 PROCESS_HEAP_ENTRY heap_entry;
168 heap_entry.lpData = nullptr;
169 // Walk over all the entries in the main heap.
siggi82535f62016-12-06 22:29:03170 while (::HeapWalk(crt_heap, &heap_entry) != FALSE) {
kraynovad507292016-11-25 18:01:23171 if ((heap_entry.wFlags & PROCESS_HEAP_ENTRY_BUSY) != 0) {
siggi82535f62016-12-06 22:29:03172 crt_heap_info->allocated_size += heap_entry.cbData;
173 crt_heap_info->block_count++;
kraynovad507292016-11-25 18:01:23174 } else if ((heap_entry.wFlags & PROCESS_HEAP_REGION) != 0) {
siggi82535f62016-12-06 22:29:03175 crt_heap_info->committed_size += heap_entry.Region.dwCommittedSize;
176 crt_heap_info->uncommitted_size += heap_entry.Region.dwUnCommittedSize;
liamjmc56e1ffa2016-10-15 01:04:46177 }
siggi7bec59a2016-08-25 20:22:26178 }
siggi82535f62016-12-06 22:29:03179 CHECK(::HeapUnlock(crt_heap) == TRUE);
siggi7bec59a2016-08-25 20:22:26180}
181#endif // defined(OS_WIN)
182} // namespace
183
ssid07386852015-04-14 15:32:37184// static
primianofadec05e2015-06-03 16:57:32185const char MallocDumpProvider::kAllocatedObjects[] = "malloc/allocated_objects";
186
187// static
ssid07386852015-04-14 15:32:37188MallocDumpProvider* MallocDumpProvider::GetInstance() {
189 return Singleton<MallocDumpProvider,
190 LeakySingletonTraits<MallocDumpProvider>>::get();
191}
192
primianofd9072162016-03-25 02:13:28193MallocDumpProvider::MallocDumpProvider()
erikchenbd599af52017-05-22 21:15:07194 : tid_dumping_heap_(kInvalidThreadId) {}
ssid07386852015-04-14 15:32:37195
ssid3aa02fe2015-11-07 16:15:07196MallocDumpProvider::~MallocDumpProvider() {}
ssid07386852015-04-14 15:32:37197
198// Called at trace dump point time. Creates a snapshot the memory counters for
199// the current process.
ssid90694aeec2015-08-06 13:01:30200bool MallocDumpProvider::OnMemoryDump(const MemoryDumpArgs& args,
201 ProcessMemoryDump* pmd) {
ssid09434092015-10-26 23:05:04202 size_t total_virtual_size = 0;
203 size_t resident_size = 0;
204 size_t allocated_objects_size = 0;
siggi7bec59a2016-08-25 20:22:26205 size_t allocated_objects_count = 0;
ssid86f78c12015-12-21 11:45:32206#if defined(USE_TCMALLOC)
primianodda6c272015-12-07 16:51:04207 bool res =
208 allocator::GetNumericProperty("generic.heap_size", &total_virtual_size);
209 DCHECK(res);
210 res = allocator::GetNumericProperty("generic.total_physical_bytes",
211 &resident_size);
212 DCHECK(res);
213 res = allocator::GetNumericProperty("generic.current_allocated_bytes",
214 &allocated_objects_size);
215 DCHECK(res);
ssid86f78c12015-12-21 11:45:32216#elif defined(OS_MACOSX) || defined(OS_IOS)
217 malloc_statistics_t stats = {0};
218 malloc_zone_statistics(nullptr, &stats);
219 total_virtual_size = stats.size_allocated;
220 allocated_objects_size = stats.size_in_use;
221
erikchen792525b2017-03-10 18:06:15222 // Resident size is approximated pretty well by stats.max_size_in_use.
223 // However, on macOS, freed blocks are both resident and reusable, which is
224 // semantically equivalent to deallocated. The implementation of libmalloc
225 // will also only hold a fixed number of freed regions before actually
226 // starting to deallocate them, so stats.max_size_in_use is also not
227 // representative of the peak size. As a result, stats.max_size_in_use is
228 // typically somewhere between actually resident [non-reusable] pages, and
229 // peak size. This is not very useful, so we just use stats.size_in_use for
230 // resident_size, even though it's an underestimate and fails to account for
231 // fragmentation. See
232 // https://bugs.chromium.org/p/chromium/issues/detail?id=695263#c1.
233 resident_size = stats.size_in_use;
siggi7bec59a2016-08-25 20:22:26234#elif defined(OS_WIN)
kraynovad507292016-11-25 18:01:23235 WinHeapInfo main_heap_info = {};
236 WinHeapMemoryDumpImpl(&main_heap_info);
siggi7bec59a2016-08-25 20:22:26237 total_virtual_size =
kraynovad507292016-11-25 18:01:23238 main_heap_info.committed_size + main_heap_info.uncommitted_size;
siggi7bec59a2016-08-25 20:22:26239 // Resident size is approximated with committed heap size. Note that it is
240 // possible to do this with better accuracy on windows by intersecting the
241 // working set with the virtual memory ranges occuipied by the heap. It's not
242 // clear that this is worth it, as it's fairly expensive to do.
kraynovad507292016-11-25 18:01:23243 resident_size = main_heap_info.committed_size;
244 allocated_objects_size = main_heap_info.allocated_size;
245 allocated_objects_count = main_heap_info.block_count;
scottmg6ea9ff3e2017-05-19 00:08:16246#elif defined(OS_FUCHSIA)
247// TODO(fuchsia): Port, see https://crbug.com/706592.
ssid3aa02fe2015-11-07 16:15:07248#else
primianodda6c272015-12-07 16:51:04249 struct mallinfo info = mallinfo();
250 DCHECK_GE(info.arena + info.hblkhd, info.uordblks);
ssid09434092015-10-26 23:05:04251
primianodda6c272015-12-07 16:51:04252 // In case of Android's jemalloc |arena| is 0 and the outer pages size is
253 // reported by |hblkhd|. In case of dlmalloc the total is given by
254 // |arena| + |hblkhd|. For more details see link: http://goo.gl/fMR8lF.
255 total_virtual_size = info.arena + info.hblkhd;
256 resident_size = info.uordblks;
siggi7bec59a2016-08-25 20:22:26257
258 // Total allocated space is given by |uordblks|.
primianodda6c272015-12-07 16:51:04259 allocated_objects_size = info.uordblks;
ssid3aa02fe2015-11-07 16:15:07260#endif
ssid09434092015-10-26 23:05:04261
primianofadec05e2015-06-03 16:57:32262 MemoryAllocatorDump* outer_dump = pmd->CreateAllocatorDump("malloc");
ssid09434092015-10-26 23:05:04263 outer_dump->AddScalar("virtual_size", MemoryAllocatorDump::kUnitsBytes,
264 total_virtual_size);
265 outer_dump->AddScalar(MemoryAllocatorDump::kNameSize,
266 MemoryAllocatorDump::kUnitsBytes, resident_size);
ssid07386852015-04-14 15:32:37267
primianofadec05e2015-06-03 16:57:32268 MemoryAllocatorDump* inner_dump = pmd->CreateAllocatorDump(kAllocatedObjects);
269 inner_dump->AddScalar(MemoryAllocatorDump::kNameSize,
ssid09434092015-10-26 23:05:04270 MemoryAllocatorDump::kUnitsBytes,
271 allocated_objects_size);
siggi7bec59a2016-08-25 20:22:26272 if (allocated_objects_count != 0) {
siggi52114f272016-08-31 23:51:30273 inner_dump->AddScalar(MemoryAllocatorDump::kNameObjectCount,
siggi7bec59a2016-08-25 20:22:26274 MemoryAllocatorDump::kUnitsObjects,
275 allocated_objects_count);
276 }
ssid07386852015-04-14 15:32:37277
siggi52114f272016-08-31 23:51:30278 if (resident_size > allocated_objects_size) {
ssid86f78c12015-12-21 11:45:32279 // Explicitly specify why is extra memory resident. In tcmalloc it accounts
280 // for free lists and caches. In mac and ios it accounts for the
281 // fragmentation and metadata.
282 MemoryAllocatorDump* other_dump =
283 pmd->CreateAllocatorDump("malloc/metadata_fragmentation_caches");
284 other_dump->AddScalar(MemoryAllocatorDump::kNameSize,
285 MemoryAllocatorDump::kUnitsBytes,
286 resident_size - allocated_objects_size);
287 }
288
primianofd9072162016-03-25 02:13:28289 // Heap profiler dumps.
erikchenbd599af52017-05-22 21:15:07290 if (!allocation_register_.is_enabled())
primianofd9072162016-03-25 02:13:28291 return true;
292
293 // The dumps of the heap profiler should be created only when heap profiling
294 // was enabled (--enable-heap-profiling) AND a DETAILED dump is requested.
295 // However, when enabled, the overhead of the heap profiler should be always
296 // reported to avoid oscillations of the malloc total in LIGHT dumps.
297
298 tid_dumping_heap_ = PlatformThread::CurrentId();
299 // At this point the Insert/RemoveAllocation hooks will ignore this thread.
etiennebc9079412017-05-10 17:58:48300 // Enclosing all the temporary data structures in a scope, so that the heap
301 // profiler does not see unbalanced malloc/free calls from these containers.
primianofd9072162016-03-25 02:13:28302 {
erikchenbd599af52017-05-22 21:15:07303 if (args.level_of_detail == MemoryDumpLevelOfDetail::DETAILED) {
dskibad4a5e9822017-06-19 21:10:15304 struct ShimMetrics {
305 size_t size;
306 size_t count;
307 };
308 ShimMetrics shim_metrics = {0};
309 auto visit_allocation = [](ShimMetrics* metrics,
310 const AllocationRegister::Allocation& alloc) {
311 metrics->size += alloc.size;
312 metrics->count += 1;
313 };
314 allocation_register_.VisitAllocations(base::BindRepeating(
315 visit_allocation, base::Unretained(&shim_metrics)));
etiennebc9079412017-05-10 17:58:48316
erikchenbd599af52017-05-22 21:15:07317 // Aggregate data for objects allocated through the shim.
etienneb7de5d922017-05-24 17:49:09318 inner_dump->AddScalar("shim_allocated_objects_size",
319 MemoryAllocatorDump::kUnitsBytes,
320 shim_metrics.size);
321 inner_dump->AddScalar("shim_allocator_object_count",
322 MemoryAllocatorDump::kUnitsObjects,
323 shim_metrics.count);
erikchenbd599af52017-05-22 21:15:07324 }
etiennebc9079412017-05-10 17:58:48325
dskibad4a5e9822017-06-19 21:10:15326 pmd->DumpHeapUsage(allocation_register_, "malloc");
primianofd9072162016-03-25 02:13:28327 }
328 tid_dumping_heap_ = kInvalidThreadId;
329
ssid07386852015-04-14 15:32:37330 return true;
331}
332
primianofd9072162016-03-25 02:13:28333void MallocDumpProvider::OnHeapProfilingEnabled(bool enabled) {
primiano73228cd2017-05-25 15:16:09334#if BUILDFLAG(USE_ALLOCATOR_SHIM)
primianofd9072162016-03-25 02:13:28335 if (enabled) {
erikchenbd599af52017-05-22 21:15:07336 allocation_register_.SetEnabled();
primianofd9072162016-03-25 02:13:28337 allocator::InsertAllocatorDispatch(&g_allocator_hooks);
338 } else {
erikchenbd599af52017-05-22 21:15:07339 allocation_register_.SetDisabled();
primianofd9072162016-03-25 02:13:28340 }
341#endif
primianofd9072162016-03-25 02:13:28342}
343
344void MallocDumpProvider::InsertAllocation(void* address, size_t size) {
345 // CurrentId() can be a slow operation (crbug.com/497226). This apparently
346 // redundant condition short circuits the CurrentID() calls when unnecessary.
347 if (tid_dumping_heap_ != kInvalidThreadId &&
348 tid_dumping_heap_ == PlatformThread::CurrentId())
349 return;
350
351 // AllocationContextTracker will return nullptr when called re-reentrantly.
352 // This is the case of GetInstanceForCurrentThread() being called for the
353 // first time, which causes a new() inside the tracker which re-enters the
354 // heap profiler, in which case we just want to early out.
vmpstr5170bf92016-06-29 02:15:58355 auto* tracker = AllocationContextTracker::GetInstanceForCurrentThread();
primianofd9072162016-03-25 02:13:28356 if (!tracker)
357 return;
dskiba9ab14b22017-01-18 21:53:42358
359 AllocationContext context;
360 if (!tracker->GetContextSnapshot(&context))
361 return;
primianofd9072162016-03-25 02:13:28362
erikchenbd599af52017-05-22 21:15:07363 if (!allocation_register_.is_enabled())
primianofd9072162016-03-25 02:13:28364 return;
365
erikchenbd599af52017-05-22 21:15:07366 allocation_register_.Insert(address, size, context);
primianofd9072162016-03-25 02:13:28367}
368
369void MallocDumpProvider::RemoveAllocation(void* address) {
370 // No re-entrancy is expected here as none of the calls below should
371 // cause a free()-s (|allocation_register_| does its own heap management).
372 if (tid_dumping_heap_ != kInvalidThreadId &&
373 tid_dumping_heap_ == PlatformThread::CurrentId())
374 return;
erikchenbd599af52017-05-22 21:15:07375 if (!allocation_register_.is_enabled())
primianofd9072162016-03-25 02:13:28376 return;
erikchenbd599af52017-05-22 21:15:07377 allocation_register_.Remove(address);
primianofd9072162016-03-25 02:13:28378}
379
ssid07386852015-04-14 15:32:37380} // namespace trace_event
381} // namespace base