• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "base/trace_event/malloc_dump_provider.h"
6 
7 #include <stddef.h>
8 
9 #include "base/allocator/allocator_extension.h"
10 #include "base/allocator/allocator_shim.h"
11 #include "base/allocator/features.h"
12 #include "base/debug/profiler.h"
13 #include "base/trace_event/heap_profiler_allocation_context.h"
14 #include "base/trace_event/heap_profiler_allocation_context_tracker.h"
15 #include "base/trace_event/heap_profiler_allocation_register.h"
16 #include "base/trace_event/heap_profiler_heap_dump_writer.h"
17 #include "base/trace_event/process_memory_dump.h"
18 #include "base/trace_event/trace_event_argument.h"
19 #include "build/build_config.h"
20 
21 #if defined(OS_MACOSX)
22 #include <malloc/malloc.h>
23 #else
24 #include <malloc.h>
25 #endif
26 #if defined(OS_WIN)
27 #include <windows.h>
28 #endif
29 
30 namespace base {
31 namespace trace_event {
32 
33 namespace {
34 #if BUILDFLAG(USE_EXPERIMENTAL_ALLOCATOR_SHIM)
35 
36 using allocator::AllocatorDispatch;
37 
HookAlloc(const AllocatorDispatch * self,size_t size,void * context)38 void* HookAlloc(const AllocatorDispatch* self, size_t size, void* context) {
39   const AllocatorDispatch* const next = self->next;
40   void* ptr = next->alloc_function(next, size, context);
41   if (ptr)
42     MallocDumpProvider::GetInstance()->InsertAllocation(ptr, size);
43   return ptr;
44 }
45 
HookZeroInitAlloc(const AllocatorDispatch * self,size_t n,size_t size,void * context)46 void* HookZeroInitAlloc(const AllocatorDispatch* self,
47                         size_t n,
48                         size_t size,
49                         void* context) {
50   const AllocatorDispatch* const next = self->next;
51   void* ptr = next->alloc_zero_initialized_function(next, n, size, context);
52   if (ptr)
53     MallocDumpProvider::GetInstance()->InsertAllocation(ptr, n * size);
54   return ptr;
55 }
56 
HookAllocAligned(const AllocatorDispatch * self,size_t alignment,size_t size,void * context)57 void* HookAllocAligned(const AllocatorDispatch* self,
58                        size_t alignment,
59                        size_t size,
60                        void* context) {
61   const AllocatorDispatch* const next = self->next;
62   void* ptr = next->alloc_aligned_function(next, alignment, size, context);
63   if (ptr)
64     MallocDumpProvider::GetInstance()->InsertAllocation(ptr, size);
65   return ptr;
66 }
67 
HookRealloc(const AllocatorDispatch * self,void * address,size_t size,void * context)68 void* HookRealloc(const AllocatorDispatch* self,
69                   void* address,
70                   size_t size,
71                   void* context) {
72   const AllocatorDispatch* const next = self->next;
73   void* ptr = next->realloc_function(next, address, size, context);
74   MallocDumpProvider::GetInstance()->RemoveAllocation(address);
75   if (size > 0)  // realloc(size == 0) means free().
76     MallocDumpProvider::GetInstance()->InsertAllocation(ptr, size);
77   return ptr;
78 }
79 
HookFree(const AllocatorDispatch * self,void * address,void * context)80 void HookFree(const AllocatorDispatch* self, void* address, void* context) {
81   if (address)
82     MallocDumpProvider::GetInstance()->RemoveAllocation(address);
83   const AllocatorDispatch* const next = self->next;
84   next->free_function(next, address, context);
85 }
86 
HookGetSizeEstimate(const AllocatorDispatch * self,void * address,void * context)87 size_t HookGetSizeEstimate(const AllocatorDispatch* self,
88                            void* address,
89                            void* context) {
90   const AllocatorDispatch* const next = self->next;
91   return next->get_size_estimate_function(next, address, context);
92 }
93 
HookBatchMalloc(const AllocatorDispatch * self,size_t size,void ** results,unsigned num_requested,void * context)94 unsigned HookBatchMalloc(const AllocatorDispatch* self,
95                          size_t size,
96                          void** results,
97                          unsigned num_requested,
98                          void* context) {
99   const AllocatorDispatch* const next = self->next;
100   unsigned count =
101       next->batch_malloc_function(next, size, results, num_requested, context);
102   for (unsigned i = 0; i < count; ++i) {
103     MallocDumpProvider::GetInstance()->InsertAllocation(results[i], size);
104   }
105   return count;
106 }
107 
HookBatchFree(const AllocatorDispatch * self,void ** to_be_freed,unsigned num_to_be_freed,void * context)108 void HookBatchFree(const AllocatorDispatch* self,
109                    void** to_be_freed,
110                    unsigned num_to_be_freed,
111                    void* context) {
112   const AllocatorDispatch* const next = self->next;
113   for (unsigned i = 0; i < num_to_be_freed; ++i) {
114     MallocDumpProvider::GetInstance()->RemoveAllocation(to_be_freed[i]);
115   }
116   next->batch_free_function(next, to_be_freed, num_to_be_freed, context);
117 }
118 
HookFreeDefiniteSize(const AllocatorDispatch * self,void * ptr,size_t size,void * context)119 void HookFreeDefiniteSize(const AllocatorDispatch* self,
120                           void* ptr,
121                           size_t size,
122                           void* context) {
123   if (ptr)
124     MallocDumpProvider::GetInstance()->RemoveAllocation(ptr);
125   const AllocatorDispatch* const next = self->next;
126   next->free_definite_size_function(next, ptr, size, context);
127 }
128 
129 AllocatorDispatch g_allocator_hooks = {
130     &HookAlloc,            /* alloc_function */
131     &HookZeroInitAlloc,    /* alloc_zero_initialized_function */
132     &HookAllocAligned,     /* alloc_aligned_function */
133     &HookRealloc,          /* realloc_function */
134     &HookFree,             /* free_function */
135     &HookGetSizeEstimate,  /* get_size_estimate_function */
136     &HookBatchMalloc,      /* batch_malloc_function */
137     &HookBatchFree,        /* batch_free_function */
138     &HookFreeDefiniteSize, /* free_definite_size_function */
139     nullptr,               /* next */
140 };
141 #endif  // BUILDFLAG(USE_EXPERIMENTAL_ALLOCATOR_SHIM)
142 
143 #if defined(OS_WIN)
144 // A structure containing some information about a given heap.
145 struct WinHeapInfo {
146   size_t committed_size;
147   size_t uncommitted_size;
148   size_t allocated_size;
149   size_t block_count;
150 };
151 
152 // NOTE: crbug.com/665516
153 // Unfortunately, there is no safe way to collect information from secondary
154 // heaps due to limitations and racy nature of this piece of WinAPI.
WinHeapMemoryDumpImpl(WinHeapInfo * crt_heap_info)155 void WinHeapMemoryDumpImpl(WinHeapInfo* crt_heap_info) {
156 #if defined(SYZYASAN)
157   if (base::debug::IsBinaryInstrumented())
158     return;
159 #endif
160 
161   // Iterate through whichever heap our CRT is using.
162   HANDLE crt_heap = reinterpret_cast<HANDLE>(_get_heap_handle());
163   ::HeapLock(crt_heap);
164   PROCESS_HEAP_ENTRY heap_entry;
165   heap_entry.lpData = nullptr;
166   // Walk over all the entries in the main heap.
167   while (::HeapWalk(crt_heap, &heap_entry) != FALSE) {
168     if ((heap_entry.wFlags & PROCESS_HEAP_ENTRY_BUSY) != 0) {
169       crt_heap_info->allocated_size += heap_entry.cbData;
170       crt_heap_info->block_count++;
171     } else if ((heap_entry.wFlags & PROCESS_HEAP_REGION) != 0) {
172       crt_heap_info->committed_size += heap_entry.Region.dwCommittedSize;
173       crt_heap_info->uncommitted_size += heap_entry.Region.dwUnCommittedSize;
174     }
175   }
176   CHECK(::HeapUnlock(crt_heap) == TRUE);
177 }
178 #endif  // defined(OS_WIN)
179 }  // namespace
180 
181 // static
182 const char MallocDumpProvider::kAllocatedObjects[] = "malloc/allocated_objects";
183 
184 // static
GetInstance()185 MallocDumpProvider* MallocDumpProvider::GetInstance() {
186   return Singleton<MallocDumpProvider,
187                    LeakySingletonTraits<MallocDumpProvider>>::get();
188 }
189 
MallocDumpProvider()190 MallocDumpProvider::MallocDumpProvider()
191     : heap_profiler_enabled_(false), tid_dumping_heap_(kInvalidThreadId) {}
192 
~MallocDumpProvider()193 MallocDumpProvider::~MallocDumpProvider() {}
194 
195 // Called at trace dump point time. Creates a snapshot the memory counters for
196 // the current process.
OnMemoryDump(const MemoryDumpArgs & args,ProcessMemoryDump * pmd)197 bool MallocDumpProvider::OnMemoryDump(const MemoryDumpArgs& args,
198                                       ProcessMemoryDump* pmd) {
199   size_t total_virtual_size = 0;
200   size_t resident_size = 0;
201   size_t allocated_objects_size = 0;
202   size_t allocated_objects_count = 0;
203 #if defined(USE_TCMALLOC)
204   bool res =
205       allocator::GetNumericProperty("generic.heap_size", &total_virtual_size);
206   DCHECK(res);
207   res = allocator::GetNumericProperty("generic.total_physical_bytes",
208                                       &resident_size);
209   DCHECK(res);
210   res = allocator::GetNumericProperty("generic.current_allocated_bytes",
211                                       &allocated_objects_size);
212   DCHECK(res);
213 #elif defined(OS_MACOSX) || defined(OS_IOS)
214   malloc_statistics_t stats = {0};
215   malloc_zone_statistics(nullptr, &stats);
216   total_virtual_size = stats.size_allocated;
217   allocated_objects_size = stats.size_in_use;
218 
219   // Resident size is approximated pretty well by stats.max_size_in_use.
220   // However, on macOS, freed blocks are both resident and reusable, which is
221   // semantically equivalent to deallocated. The implementation of libmalloc
222   // will also only hold a fixed number of freed regions before actually
223   // starting to deallocate them, so stats.max_size_in_use is also not
224   // representative of the peak size. As a result, stats.max_size_in_use is
225   // typically somewhere between actually resident [non-reusable] pages, and
226   // peak size. This is not very useful, so we just use stats.size_in_use for
227   // resident_size, even though it's an underestimate and fails to account for
228   // fragmentation. See
229   // https://bugs.chromium.org/p/chromium/issues/detail?id=695263#c1.
230   resident_size = stats.size_in_use;
231 #elif defined(OS_WIN)
232   WinHeapInfo main_heap_info = {};
233   WinHeapMemoryDumpImpl(&main_heap_info);
234   total_virtual_size =
235       main_heap_info.committed_size + main_heap_info.uncommitted_size;
236   // Resident size is approximated with committed heap size. Note that it is
237   // possible to do this with better accuracy on windows by intersecting the
238   // working set with the virtual memory ranges occuipied by the heap. It's not
239   // clear that this is worth it, as it's fairly expensive to do.
240   resident_size = main_heap_info.committed_size;
241   allocated_objects_size = main_heap_info.allocated_size;
242   allocated_objects_count = main_heap_info.block_count;
243 #else
244   struct mallinfo info = mallinfo();
245   DCHECK_GE(info.arena + info.hblkhd, info.uordblks);
246 
247   // In case of Android's jemalloc |arena| is 0 and the outer pages size is
248   // reported by |hblkhd|. In case of dlmalloc the total is given by
249   // |arena| + |hblkhd|. For more details see link: http://goo.gl/fMR8lF.
250   total_virtual_size = info.arena + info.hblkhd;
251   resident_size = info.uordblks;
252 
253   // Total allocated space is given by |uordblks|.
254   allocated_objects_size = info.uordblks;
255 #endif
256 
257   MemoryAllocatorDump* outer_dump = pmd->CreateAllocatorDump("malloc");
258   outer_dump->AddScalar("virtual_size", MemoryAllocatorDump::kUnitsBytes,
259                         total_virtual_size);
260   outer_dump->AddScalar(MemoryAllocatorDump::kNameSize,
261                         MemoryAllocatorDump::kUnitsBytes, resident_size);
262 
263   MemoryAllocatorDump* inner_dump = pmd->CreateAllocatorDump(kAllocatedObjects);
264   inner_dump->AddScalar(MemoryAllocatorDump::kNameSize,
265                         MemoryAllocatorDump::kUnitsBytes,
266                         allocated_objects_size);
267   if (allocated_objects_count != 0) {
268     inner_dump->AddScalar(MemoryAllocatorDump::kNameObjectCount,
269                           MemoryAllocatorDump::kUnitsObjects,
270                           allocated_objects_count);
271   }
272 
273   if (resident_size > allocated_objects_size) {
274     // Explicitly specify why is extra memory resident. In tcmalloc it accounts
275     // for free lists and caches. In mac and ios it accounts for the
276     // fragmentation and metadata.
277     MemoryAllocatorDump* other_dump =
278         pmd->CreateAllocatorDump("malloc/metadata_fragmentation_caches");
279     other_dump->AddScalar(MemoryAllocatorDump::kNameSize,
280                           MemoryAllocatorDump::kUnitsBytes,
281                           resident_size - allocated_objects_size);
282   }
283 
284   // Heap profiler dumps.
285   if (!heap_profiler_enabled_)
286     return true;
287 
288   // The dumps of the heap profiler should be created only when heap profiling
289   // was enabled (--enable-heap-profiling) AND a DETAILED dump is requested.
290   // However, when enabled, the overhead of the heap profiler should be always
291   // reported to avoid oscillations of the malloc total in LIGHT dumps.
292 
293   tid_dumping_heap_ = PlatformThread::CurrentId();
294   // At this point the Insert/RemoveAllocation hooks will ignore this thread.
295   // Enclosing all the temporariy data structures in a scope, so that the heap
296   // profiler does not see unabalanced malloc/free calls from these containers.
297   {
298     TraceEventMemoryOverhead overhead;
299     hash_map<AllocationContext, AllocationMetrics> metrics_by_context;
300     {
301       AutoLock lock(allocation_register_lock_);
302       if (allocation_register_) {
303         if (args.level_of_detail == MemoryDumpLevelOfDetail::DETAILED) {
304           for (const auto& alloc_size : *allocation_register_) {
305             AllocationMetrics& metrics = metrics_by_context[alloc_size.context];
306             metrics.size += alloc_size.size;
307             metrics.count++;
308           }
309         }
310         allocation_register_->EstimateTraceMemoryOverhead(&overhead);
311       }
312     }  // lock(allocation_register_lock_)
313     pmd->DumpHeapUsage(metrics_by_context, overhead, "malloc");
314   }
315   tid_dumping_heap_ = kInvalidThreadId;
316 
317   return true;
318 }
319 
OnHeapProfilingEnabled(bool enabled)320 void MallocDumpProvider::OnHeapProfilingEnabled(bool enabled) {
321 #if BUILDFLAG(USE_EXPERIMENTAL_ALLOCATOR_SHIM)
322   if (enabled) {
323     {
324       AutoLock lock(allocation_register_lock_);
325       allocation_register_.reset(new AllocationRegister());
326     }
327     allocator::InsertAllocatorDispatch(&g_allocator_hooks);
328   } else {
329     AutoLock lock(allocation_register_lock_);
330     allocation_register_.reset();
331     // Insert/RemoveAllocation below will no-op if the register is torn down.
332     // Once disabled, heap profiling will not re-enabled anymore for the
333     // lifetime of the process.
334   }
335 #endif
336   heap_profiler_enabled_ = enabled;
337 }
338 
InsertAllocation(void * address,size_t size)339 void MallocDumpProvider::InsertAllocation(void* address, size_t size) {
340   // CurrentId() can be a slow operation (crbug.com/497226). This apparently
341   // redundant condition short circuits the CurrentID() calls when unnecessary.
342   if (tid_dumping_heap_ != kInvalidThreadId &&
343       tid_dumping_heap_ == PlatformThread::CurrentId())
344     return;
345 
346   // AllocationContextTracker will return nullptr when called re-reentrantly.
347   // This is the case of GetInstanceForCurrentThread() being called for the
348   // first time, which causes a new() inside the tracker which re-enters the
349   // heap profiler, in which case we just want to early out.
350   auto* tracker = AllocationContextTracker::GetInstanceForCurrentThread();
351   if (!tracker)
352     return;
353 
354   AllocationContext context;
355   if (!tracker->GetContextSnapshot(&context))
356     return;
357 
358   AutoLock lock(allocation_register_lock_);
359   if (!allocation_register_)
360     return;
361 
362   allocation_register_->Insert(address, size, context);
363 }
364 
RemoveAllocation(void * address)365 void MallocDumpProvider::RemoveAllocation(void* address) {
366   // No re-entrancy is expected here as none of the calls below should
367   // cause a free()-s (|allocation_register_| does its own heap management).
368   if (tid_dumping_heap_ != kInvalidThreadId &&
369       tid_dumping_heap_ == PlatformThread::CurrentId())
370     return;
371   AutoLock lock(allocation_register_lock_);
372   if (!allocation_register_)
373     return;
374   allocation_register_->Remove(address);
375 }
376 
377 }  // namespace trace_event
378 }  // namespace base
379