1// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
2// for details. All rights reserved. Use of this source code is governed by a
3// BSD-style license that can be found in the LICENSE file.
4
5#include "platform/globals.h"
6
7#if defined(DART_USE_TCMALLOC) && !defined(PRODUCT)
8
9#include "vm/malloc_hooks.h"
10
11#include "gperftools/malloc_hook.h"
12
13#include "platform/assert.h"
14#include "vm/hash_map.h"
15#include "vm/json_stream.h"
16#include "vm/os_thread.h"
17#include "vm/profiler.h"
18
19namespace dart {
20
21class AddressMap;
22
23// MallocHooksState contains all of the state related to the configuration of
24// the malloc hooks, allocation information, and locks.
25class MallocHooksState : public AllStatic {
26 public:
27 static void RecordAllocHook(const void* ptr, size_t size);
28 static void RecordFreeHook(const void* ptr);
29
30 static bool Active() {
31 ASSERT(malloc_hook_mutex()->IsOwnedByCurrentThread());
32 return active_;
33 }
34 static void Init();
35
36 static bool ProfilingEnabled() { return (OSThread::TryCurrent() != NULL); }
37
38 static bool stack_trace_collection_enabled() {
39 return stack_trace_collection_enabled_;
40 }
41
42 static void set_stack_trace_collection_enabled(bool enabled) {
43 stack_trace_collection_enabled_ = enabled;
44 }
45
46 static bool IsOriginalProcess() {
47 ASSERT(original_pid_ != kInvalidPid);
48 return original_pid_ == OS::ProcessId();
49 }
50
51 static Mutex* malloc_hook_mutex() { return malloc_hook_mutex_; }
52 static ThreadId* malloc_hook_mutex_owner() {
53 return &malloc_hook_mutex_owner_;
54 }
55 static bool IsLockHeldByCurrentThread() {
56 return (malloc_hook_mutex_owner_ == OSThread::GetCurrentThreadId());
57 }
58
59 static intptr_t allocation_count() { return allocation_count_; }
60
61 static intptr_t heap_allocated_memory_in_bytes() {
62 return heap_allocated_memory_in_bytes_;
63 }
64
65 static void IncrementHeapAllocatedMemoryInBytes(intptr_t size) {
66 ASSERT(malloc_hook_mutex()->IsOwnedByCurrentThread());
67 ASSERT(size >= 0);
68 heap_allocated_memory_in_bytes_ += size;
69 ++allocation_count_;
70 }
71
72 static void DecrementHeapAllocatedMemoryInBytes(intptr_t size) {
73 ASSERT(malloc_hook_mutex()->IsOwnedByCurrentThread());
74 ASSERT(size >= 0);
75 ASSERT(heap_allocated_memory_in_bytes_ >= size);
76 heap_allocated_memory_in_bytes_ -= size;
77 --allocation_count_;
78 ASSERT(allocation_count_ >= 0);
79 }
80
81 static AddressMap* address_map() { return address_map_; }
82
83 static void ResetStats();
84 static void TearDown();
85
86 private:
87 static Mutex* malloc_hook_mutex_;
88 static ThreadId malloc_hook_mutex_owner_;
89
90 // Variables protected by malloc_hook_mutex_.
91 static bool active_;
92 static bool stack_trace_collection_enabled_;
93 static intptr_t allocation_count_;
94 static intptr_t heap_allocated_memory_in_bytes_;
95 static AddressMap* address_map_;
96 // End protected variables.
97
98 static intptr_t original_pid_;
99 static const intptr_t kInvalidPid = -1;
100};
101
102// A locker-type class similar to MutexLocker which tracks which thread
103// currently holds the lock. We use this instead of MutexLocker and
104// mutex->IsOwnedByCurrentThread() since IsOwnedByCurrentThread() is only
105// enabled for debug mode.
106class MallocLocker : public ValueObject {
107 public:
108 explicit MallocLocker(Mutex* mutex, ThreadId* owner)
109 : mutex_(mutex), owner_(owner) {
110 ASSERT(owner != NULL);
111 mutex_->Lock();
112 ASSERT(*owner_ == OSThread::kInvalidThreadId);
113 *owner_ = OSThread::GetCurrentThreadId();
114 }
115
116 virtual ~MallocLocker() {
117 ASSERT(*owner_ == OSThread::GetCurrentThreadId());
118 *owner_ = OSThread::kInvalidThreadId;
119 mutex_->Unlock();
120 }
121
122 private:
123 Mutex* mutex_;
124 ThreadId* owner_;
125};
126
127// AllocationInfo contains all information related to a given allocation
128// including:
129// -Allocation size in bytes
130// -Stack trace corresponding to the location of allocation, if applicable
131class AllocationInfo {
132 public:
133 AllocationInfo(uword address, intptr_t allocation_size)
134 : sample_(NULL), address_(address), allocation_size_(allocation_size) {
135 // Stack trace collection is disabled when we are in the process of creating
136 // the first OSThread in order to prevent deadlocks.
137 if (MallocHooksState::ProfilingEnabled() &&
138 MallocHooksState::stack_trace_collection_enabled()) {
139 sample_ = Profiler::SampleNativeAllocation(kSkipCount, address,
140 allocation_size);
141 ASSERT((sample_ == NULL) ||
142 (sample_->native_allocation_address() == address_));
143 }
144 }
145
146 ~AllocationInfo() {
147 if (sample_ != NULL) {
148 Profiler::allocation_sample_buffer()->FreeAllocationSample(sample_);
149 }
150 }
151
152 Sample* sample() const { return sample_; }
153 intptr_t allocation_size() const { return allocation_size_; }
154
155 private:
156 // Note: sample_ is not owned by AllocationInfo, but by the SampleBuffer
157 // created by the profiler. As such, this is only here to track if the sample
158 // is still associated with a native allocation, and its fields are never
159 // accessed from this class.
160 Sample* sample_;
161 uword address_;
162 intptr_t allocation_size_;
163};
164
165// Custom key/value trait specifically for address/size pairs. Unlike
166// RawPointerKeyValueTrait, the default value is -1 as 0 can be a valid entry.
167class AddressKeyValueTrait : public AllStatic {
168 public:
169 typedef const void* Key;
170 typedef AllocationInfo* Value;
171
172 struct Pair {
173 Key key;
174 Value value;
175 Pair() : key(NULL), value(NULL) {}
176 Pair(const Key key, const Value& value) : key(key), value(value) {}
177 Pair(const Pair& other) : key(other.key), value(other.value) {}
178 Pair& operator=(const Pair&) = default;
179 };
180
181 static Key KeyOf(Pair kv) { return kv.key; }
182 static Value ValueOf(Pair kv) { return kv.value; }
183 static intptr_t Hashcode(Key key) { return reinterpret_cast<intptr_t>(key); }
184 static bool IsKeyEqual(Pair kv, Key key) { return kv.key == key; }
185};
186
187// Map class that will be used to store mappings between ptr -> allocation size.
188class AddressMap : public MallocDirectChainedHashMap<AddressKeyValueTrait> {
189 public:
190 typedef AddressKeyValueTrait::Key Key;
191 typedef AddressKeyValueTrait::Value Value;
192 typedef AddressKeyValueTrait::Pair Pair;
193
194 virtual ~AddressMap() { Clear(); }
195
196 void Insert(const Key& key, const Value& value) {
197 Pair pair(key, value);
198 MallocDirectChainedHashMap<AddressKeyValueTrait>::Insert(pair);
199 }
200
201 bool Lookup(const Key& key, Value* value) {
202 ASSERT(value != NULL);
203 Pair* pair = MallocDirectChainedHashMap<AddressKeyValueTrait>::Lookup(key);
204 if (pair == NULL) {
205 return false;
206 } else {
207 *value = pair->value;
208 return true;
209 }
210 }
211
212 void Clear() {
213 Iterator iter = GetIterator();
214 Pair* result = iter.Next();
215 while (result != NULL) {
216 delete result->value;
217 result->value = NULL;
218 result = iter.Next();
219 }
220 MallocDirectChainedHashMap<AddressKeyValueTrait>::Clear();
221 }
222};
223
224// MallocHooks state / locks.
225bool MallocHooksState::active_ = false;
226bool MallocHooksState::stack_trace_collection_enabled_ = false;
227intptr_t MallocHooksState::original_pid_ = MallocHooksState::kInvalidPid;
228Mutex* MallocHooksState::malloc_hook_mutex_ = new Mutex();
229ThreadId MallocHooksState::malloc_hook_mutex_owner_ =
230 OSThread::kInvalidThreadId;
231
232// Memory allocation state information.
233intptr_t MallocHooksState::allocation_count_ = 0;
234intptr_t MallocHooksState::heap_allocated_memory_in_bytes_ = 0;
235AddressMap* MallocHooksState::address_map_ = NULL;
236
237void MallocHooksState::Init() {
238 address_map_ = new AddressMap();
239 active_ = true;
240#if defined(DEBUG)
241 stack_trace_collection_enabled_ = true;
242#else
243 stack_trace_collection_enabled_ = false;
244#endif // defined(DEBUG)
245 original_pid_ = OS::ProcessId();
246}
247
248void MallocHooksState::ResetStats() {
249 ASSERT(malloc_hook_mutex()->IsOwnedByCurrentThread());
250 allocation_count_ = 0;
251 heap_allocated_memory_in_bytes_ = 0;
252 address_map_->Clear();
253}
254
255void MallocHooksState::TearDown() {
256 ASSERT(malloc_hook_mutex()->IsOwnedByCurrentThread());
257 active_ = false;
258 original_pid_ = kInvalidPid;
259 ResetStats();
260 delete address_map_;
261 address_map_ = NULL;
262}
263
264void MallocHooks::Init() {
265 if (!FLAG_profiler_native_memory || MallocHooks::Active()) {
266 return;
267 }
268 MallocLocker ml(MallocHooksState::malloc_hook_mutex(),
269 MallocHooksState::malloc_hook_mutex_owner());
270 ASSERT(!MallocHooksState::Active());
271
272 MallocHooksState::Init();
273
274 // Register malloc hooks.
275 bool success = false;
276 success = MallocHook::AddNewHook(&MallocHooksState::RecordAllocHook);
277 ASSERT(success);
278 success = MallocHook::AddDeleteHook(&MallocHooksState::RecordFreeHook);
279 ASSERT(success);
280}
281
282void MallocHooks::Cleanup() {
283 if (!FLAG_profiler_native_memory || !MallocHooks::Active()) {
284 return;
285 }
286 MallocLocker ml(MallocHooksState::malloc_hook_mutex(),
287 MallocHooksState::malloc_hook_mutex_owner());
288 ASSERT(MallocHooksState::Active());
289
290 // Remove malloc hooks.
291 bool success = false;
292 success = MallocHook::RemoveNewHook(&MallocHooksState::RecordAllocHook);
293 ASSERT(success);
294 success = MallocHook::RemoveDeleteHook(&MallocHooksState::RecordFreeHook);
295 ASSERT(success);
296
297 MallocHooksState::TearDown();
298}
299
300bool MallocHooks::ProfilingEnabled() {
301 return MallocHooksState::ProfilingEnabled();
302}
303
304bool MallocHooks::stack_trace_collection_enabled() {
305 MallocLocker ml(MallocHooksState::malloc_hook_mutex(),
306 MallocHooksState::malloc_hook_mutex_owner());
307 return MallocHooksState::stack_trace_collection_enabled();
308}
309
310void MallocHooks::set_stack_trace_collection_enabled(bool enabled) {
311 MallocLocker ml(MallocHooksState::malloc_hook_mutex(),
312 MallocHooksState::malloc_hook_mutex_owner());
313 MallocHooksState::set_stack_trace_collection_enabled(enabled);
314}
315
316void MallocHooks::ResetStats() {
317 if (!FLAG_profiler_native_memory) {
318 return;
319 }
320 MallocLocker ml(MallocHooksState::malloc_hook_mutex(),
321 MallocHooksState::malloc_hook_mutex_owner());
322 if (MallocHooksState::Active()) {
323 MallocHooksState::ResetStats();
324 }
325}
326
327bool MallocHooks::Active() {
328 if (!FLAG_profiler_native_memory) {
329 return false;
330 }
331 MallocLocker ml(MallocHooksState::malloc_hook_mutex(),
332 MallocHooksState::malloc_hook_mutex_owner());
333
334 return MallocHooksState::Active();
335}
336
337void MallocHooks::PrintToJSONObject(JSONObject* jsobj) {
338 if (!FLAG_profiler_native_memory) {
339 return;
340 }
341 intptr_t allocated_memory = 0;
342 intptr_t allocation_count = 0;
343 bool add_usage = false;
344 // AddProperty may call malloc which would result in an attempt
345 // to acquire the lock recursively so we extract the values first
346 // and then add the JSON properties.
347 {
348 MallocLocker ml(MallocHooksState::malloc_hook_mutex(),
349 MallocHooksState::malloc_hook_mutex_owner());
350 if (MallocHooksState::Active()) {
351 allocated_memory = MallocHooksState::heap_allocated_memory_in_bytes();
352 allocation_count = MallocHooksState::allocation_count();
353 add_usage = true;
354 }
355 }
356 if (add_usage) {
357 jsobj->AddProperty("_heapAllocatedMemoryUsage", allocated_memory);
358 jsobj->AddProperty("_heapAllocationCount", allocation_count);
359 }
360}
361
362intptr_t MallocHooks::allocation_count() {
363 if (!FLAG_profiler_native_memory) {
364 return 0;
365 }
366 MallocLocker ml(MallocHooksState::malloc_hook_mutex(),
367 MallocHooksState::malloc_hook_mutex_owner());
368 return MallocHooksState::allocation_count();
369}
370
371intptr_t MallocHooks::heap_allocated_memory_in_bytes() {
372 if (!FLAG_profiler_native_memory) {
373 return 0;
374 }
375 MallocLocker ml(MallocHooksState::malloc_hook_mutex(),
376 MallocHooksState::malloc_hook_mutex_owner());
377 return MallocHooksState::heap_allocated_memory_in_bytes();
378}
379
380Sample* MallocHooks::GetSample(const void* ptr) {
381 MallocLocker ml(MallocHooksState::malloc_hook_mutex(),
382 MallocHooksState::malloc_hook_mutex_owner());
383
384 ASSERT(MallocHooksState::Active());
385
386 if (ptr != NULL) {
387 AllocationInfo* allocation_info = NULL;
388 if (MallocHooksState::address_map()->Lookup(ptr, &allocation_info)) {
389 ASSERT(allocation_info != NULL);
390 return allocation_info->sample();
391 }
392 }
393 return NULL;
394}
395
396void MallocHooksState::RecordAllocHook(const void* ptr, size_t size) {
397 if (MallocHooksState::IsLockHeldByCurrentThread() ||
398 !MallocHooksState::IsOriginalProcess()) {
399 return;
400 }
401
402 MallocLocker ml(MallocHooksState::malloc_hook_mutex(),
403 MallocHooksState::malloc_hook_mutex_owner());
404 // Now that we hold the lock, check to make sure everything is still active.
405 if ((ptr != NULL) && MallocHooksState::Active()) {
406 MallocHooksState::IncrementHeapAllocatedMemoryInBytes(size);
407 MallocHooksState::address_map()->Insert(
408 ptr, new AllocationInfo(reinterpret_cast<uword>(ptr), size));
409 }
410}
411
412void MallocHooksState::RecordFreeHook(const void* ptr) {
413 if (MallocHooksState::IsLockHeldByCurrentThread() ||
414 !MallocHooksState::IsOriginalProcess()) {
415 return;
416 }
417
418 MallocLocker ml(MallocHooksState::malloc_hook_mutex(),
419 MallocHooksState::malloc_hook_mutex_owner());
420 // Now that we hold the lock, check to make sure everything is still active.
421 if ((ptr != NULL) && MallocHooksState::Active()) {
422 AllocationInfo* allocation_info = NULL;
423 if (MallocHooksState::address_map()->Lookup(ptr, &allocation_info)) {
424 MallocHooksState::DecrementHeapAllocatedMemoryInBytes(
425 allocation_info->allocation_size());
426 const bool result = MallocHooksState::address_map()->Remove(ptr);
427 ASSERT(result);
428 delete allocation_info;
429 }
430 }
431}
432
433} // namespace dart
434
435#endif // defined(DART_USE_TCMALLOC) && !defined(PRODUCT)
436