blob: ec388e1cc540858081c1eea467d32f34fba66074 [file] [log] [blame]
Austin Schuh745610d2015-09-06 18:19:50 -07001// -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*-
2/* Copyright (c) 2006, Google Inc.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 * * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * * Redistributions in binary form must reproduce the above
12 * copyright notice, this list of conditions and the following disclaimer
13 * in the documentation and/or other materials provided with the
14 * distribution.
15 * * Neither the name of Google Inc. nor the names of its
16 * contributors may be used to endorse or promote products derived from
17 * this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 *
31 * ---
32 * Author: Maxim Lifantsev
33 */
34
35#ifndef BASE_MEMORY_REGION_MAP_H_
36#define BASE_MEMORY_REGION_MAP_H_
37
38#include <config.h>
39
40#ifdef HAVE_PTHREAD
41#include <pthread.h>
42#endif
43#include <stddef.h>
44#include <set>
45#include "base/stl_allocator.h"
46#include "base/spinlock.h"
47#include "base/thread_annotations.h"
48#include "base/low_level_alloc.h"
49#include "heap-profile-stats.h"
50
51// TODO(maxim): add a unittest:
52// execute a bunch of mmaps and compare memory map what strace logs
53// execute a bunch of mmap/munmup and compare memory map with
54// own accounting of what those mmaps generated
55
56// Thread-safe class to collect and query the map of all memory regions
57// in a process that have been created with mmap, munmap, mremap, sbrk.
58// For each memory region, we keep track of (and provide to users)
59// the stack trace that allocated that memory region.
60// The recorded stack trace depth is bounded by
61// a user-supplied max_stack_depth parameter of Init().
62// After initialization with Init()
63// (which can happened even before global object constructor execution)
64// we collect the map by installing and monitoring MallocHook-s
65// to mmap, munmap, mremap, sbrk.
66// At any time one can query this map via provided interface.
67// For more details on the design of MemoryRegionMap
68// see the comment at the top of our .cc file.
69class MemoryRegionMap {
70 private:
71 // Max call stack recording depth supported by Init(). Set it to be
72 // high enough for all our clients. Note: we do not define storage
73 // for this (doing that requires special handling in windows), so
74 // don't take the address of it!
75 static const int kMaxStackDepth = 32;
76
77 // Size of the hash table of buckets. A structure of the bucket table is
78 // described in heap-profile-stats.h.
79 static const int kHashTableSize = 179999;
80
81 public:
82 // interface ================================================================
83
84 // Every client of MemoryRegionMap must call Init() before first use,
85 // and Shutdown() after last use. This allows us to reference count
86 // this (singleton) class properly. MemoryRegionMap assumes it's the
87 // only client of MallocHooks, so a client can only register other
88 // MallocHooks after calling Init() and must unregister them before
89 // calling Shutdown().
90
91 // Initialize this module to record memory allocation stack traces.
92 // Stack traces that have more than "max_stack_depth" frames
93 // are automatically shrunk to "max_stack_depth" when they are recorded.
94 // Init() can be called more than once w/o harm, largest max_stack_depth
95 // will be the effective one.
96 // When "use_buckets" is true, then counts of mmap and munmap sizes will be
97 // recorded with each stack trace. If Init() is called more than once, then
98 // counting will be effective after any call contained "use_buckets" of true.
99 // It will install mmap, munmap, mremap, sbrk hooks
100 // and initialize arena_ and our hook and locks, hence one can use
101 // MemoryRegionMap::Lock()/Unlock() to manage the locks.
102 // Uses Lock/Unlock inside.
103 static void Init(int max_stack_depth, bool use_buckets);
104
105 // Try to shutdown this module undoing what Init() did.
106 // Returns true iff could do full shutdown (or it was not attempted).
107 // Full shutdown is attempted when the number of Shutdown() calls equals
108 // the number of Init() calls.
109 static bool Shutdown();
110
111 // Return true if MemoryRegionMap is initialized and recording, i.e. when
112 // then number of Init() calls are more than the number of Shutdown() calls.
113 static bool IsRecordingLocked();
114
115 // Locks to protect our internal data structures.
116 // These also protect use of arena_ if our Init() has been done.
117 // The lock is recursive.
118 static void Lock() EXCLUSIVE_LOCK_FUNCTION(lock_);
119 static void Unlock() UNLOCK_FUNCTION(lock_);
120
121 // Returns true when the lock is held by this thread (for use in RAW_CHECK-s).
122 static bool LockIsHeld();
123
124 // Locker object that acquires the MemoryRegionMap::Lock
125 // for the duration of its lifetime (a C++ scope).
126 class LockHolder {
127 public:
128 LockHolder() { Lock(); }
129 ~LockHolder() { Unlock(); }
130 private:
131 DISALLOW_COPY_AND_ASSIGN(LockHolder);
132 };
133
134 // A memory region that we know about through malloc_hook-s.
135 // This is essentially an interface through which MemoryRegionMap
136 // exports the collected data to its clients. Thread-compatible.
137 struct Region {
138 uintptr_t start_addr; // region start address
139 uintptr_t end_addr; // region end address
140 int call_stack_depth; // number of caller stack frames that we saved
141 const void* call_stack[kMaxStackDepth]; // caller address stack array
142 // filled to call_stack_depth size
143 bool is_stack; // does this region contain a thread's stack:
144 // a user of MemoryRegionMap supplies this info
145
146 // Convenience accessor for call_stack[0],
147 // i.e. (the program counter of) the immediate caller
148 // of this region's allocation function,
149 // but it also returns NULL when call_stack_depth is 0,
150 // i.e whe we weren't able to get the call stack.
151 // This usually happens in recursive calls, when the stack-unwinder
152 // calls mmap() which in turn calls the stack-unwinder.
153 uintptr_t caller() const {
154 return reinterpret_cast<uintptr_t>(call_stack_depth >= 1
155 ? call_stack[0] : NULL);
156 }
157
158 // Return true iff this region overlaps region x.
159 bool Overlaps(const Region& x) const {
160 return start_addr < x.end_addr && end_addr > x.start_addr;
161 }
162
163 private: // helpers for MemoryRegionMap
164 friend class MemoryRegionMap;
165
166 // The ways we create Region-s:
167 void Create(const void* start, size_t size) {
168 start_addr = reinterpret_cast<uintptr_t>(start);
169 end_addr = start_addr + size;
170 is_stack = false; // not a stack till marked such
171 call_stack_depth = 0;
172 AssertIsConsistent();
173 }
174 void set_call_stack_depth(int depth) {
175 RAW_DCHECK(call_stack_depth == 0, ""); // only one such set is allowed
176 call_stack_depth = depth;
177 AssertIsConsistent();
178 }
179
180 // The ways we modify Region-s:
181 void set_is_stack() { is_stack = true; }
182 void set_start_addr(uintptr_t addr) {
183 start_addr = addr;
184 AssertIsConsistent();
185 }
186 void set_end_addr(uintptr_t addr) {
187 end_addr = addr;
188 AssertIsConsistent();
189 }
190
191 // Verifies that *this contains consistent data, crashes if not the case.
192 void AssertIsConsistent() const {
193 RAW_DCHECK(start_addr < end_addr, "");
194 RAW_DCHECK(call_stack_depth >= 0 &&
195 call_stack_depth <= kMaxStackDepth, "");
196 }
197
198 // Post-default construction helper to make a Region suitable
199 // for searching in RegionSet regions_.
200 void SetRegionSetKey(uintptr_t addr) {
201 // make sure *this has no usable data:
202 if (DEBUG_MODE) memset(this, 0xFF, sizeof(*this));
203 end_addr = addr;
204 }
205
206 // Note: call_stack[kMaxStackDepth] as a member lets us make Region
207 // a simple self-contained struct with correctly behaving bit-vise copying.
208 // This simplifies the code of this module but wastes some memory:
209 // in most-often use case of this module (leak checking)
210 // only one call_stack element out of kMaxStackDepth is actually needed.
211 // Making the storage for call_stack variable-sized,
212 // substantially complicates memory management for the Region-s:
213 // as they need to be created and manipulated for some time
214 // w/o any memory allocations, yet are also given out to the users.
215 };
216
217 // Find the region that covers addr and write its data into *result if found,
218 // in which case *result gets filled so that it stays fully functional
219 // even when the underlying region gets removed from MemoryRegionMap.
220 // Returns success. Uses Lock/Unlock inside.
221 static bool FindRegion(uintptr_t addr, Region* result);
222
223 // Find the region that contains stack_top, mark that region as
224 // a stack region, and write its data into *result if found,
225 // in which case *result gets filled so that it stays fully functional
226 // even when the underlying region gets removed from MemoryRegionMap.
227 // Returns success. Uses Lock/Unlock inside.
228 static bool FindAndMarkStackRegion(uintptr_t stack_top, Region* result);
229
230 // Iterate over the buckets which store mmap and munmap counts per stack
231 // trace. It calls "callback" for each bucket, and passes "arg" to it.
232 template<class Type>
233 static void IterateBuckets(void (*callback)(const HeapProfileBucket*, Type),
234 Type arg);
235
236 // Get the bucket whose caller stack trace is "key". The stack trace is
237 // used to a depth of "depth" at most. The requested bucket is created if
238 // needed.
239 // The bucket table is described in heap-profile-stats.h.
240 static HeapProfileBucket* GetBucket(int depth, const void* const key[]);
241
242 private: // our internal types ==============================================
243
244 // Region comparator for sorting with STL
245 struct RegionCmp {
246 bool operator()(const Region& x, const Region& y) const {
247 return x.end_addr < y.end_addr;
248 }
249 };
250
251 // We allocate STL objects in our own arena.
252 struct MyAllocator {
253 static void *Allocate(size_t n) {
254 return LowLevelAlloc::AllocWithArena(n, arena_);
255 }
256 static void Free(const void *p, size_t /* n */) {
257 LowLevelAlloc::Free(const_cast<void*>(p));
258 }
259 };
260
261 // Set of the memory regions
262 typedef std::set<Region, RegionCmp,
263 STL_Allocator<Region, MyAllocator> > RegionSet;
264
265 public: // more in-depth interface ==========================================
266
267 // STL iterator with values of Region
268 typedef RegionSet::const_iterator RegionIterator;
269
270 // Return the begin/end iterators to all the regions.
271 // These need Lock/Unlock protection around their whole usage (loop).
272 // Even when the same thread causes modifications during such a loop
273 // (which are permitted due to recursive locking)
274 // the loop iterator will still be valid as long as its region
275 // has not been deleted, but EndRegionLocked should be
276 // re-evaluated whenever the set of regions has changed.
277 static RegionIterator BeginRegionLocked();
278 static RegionIterator EndRegionLocked();
279
280 // Return the accumulated sizes of mapped and unmapped regions.
281 static int64 MapSize() { return map_size_; }
282 static int64 UnmapSize() { return unmap_size_; }
283
284 // Effectively private type from our .cc =================================
285 // public to let us declare global objects:
286 union RegionSetRep;
287
288 private:
289 // representation ===========================================================
290
291 // Counter of clients of this module that have called Init().
292 static int client_count_;
293
294 // Maximal number of caller stack frames to save (>= 0).
295 static int max_stack_depth_;
296
297 // Arena used for our allocations in regions_.
298 static LowLevelAlloc::Arena* arena_;
299
300 // Set of the mmap/sbrk/mremap-ed memory regions
301 // To be accessed *only* when Lock() is held.
302 // Hence we protect the non-recursive lock used inside of arena_
303 // with our recursive Lock(). This lets a user prevent deadlocks
304 // when threads are stopped by TCMalloc_ListAllProcessThreads at random spots
305 // simply by acquiring our recursive Lock() before that.
306 static RegionSet* regions_;
307
308 // Lock to protect regions_ and buckets_ variables and the data behind.
309 static SpinLock lock_;
310 // Lock to protect the recursive lock itself.
311 static SpinLock owner_lock_;
312
313 // Recursion count for the recursive lock.
314 static int recursion_count_;
315 // The thread id of the thread that's inside the recursive lock.
316 static pthread_t lock_owner_tid_;
317
318 // Total size of all mapped pages so far
319 static int64 map_size_;
320 // Total size of all unmapped pages so far
321 static int64 unmap_size_;
322
323 // Bucket hash table which is described in heap-profile-stats.h.
324 static HeapProfileBucket** bucket_table_ GUARDED_BY(lock_);
325 static int num_buckets_ GUARDED_BY(lock_);
326
327 // The following members are local to MemoryRegionMap::GetBucket()
328 // and MemoryRegionMap::HandleSavedBucketsLocked()
329 // and are file-level to ensure that they are initialized at load time.
330 //
331 // These are used as temporary storage to break the infinite cycle of mmap
332 // calling our hook which (sometimes) causes mmap. It must be a static
333 // fixed-size array. The size 20 is just an expected value for safety.
334 // The details are described in memory_region_map.cc.
335
336 // Number of unprocessed bucket inserts.
337 static int saved_buckets_count_ GUARDED_BY(lock_);
338
339 // Unprocessed inserts (must be big enough to hold all mmaps that can be
340 // caused by a GetBucket call).
341 // Bucket has no constructor, so that c-tor execution does not interfere
342 // with the any-time use of the static memory behind saved_buckets.
343 static HeapProfileBucket saved_buckets_[20] GUARDED_BY(lock_);
344
345 static const void* saved_buckets_keys_[20][kMaxStackDepth] GUARDED_BY(lock_);
346
347 // helpers ==================================================================
348
349 // Helper for FindRegion and FindAndMarkStackRegion:
350 // returns the region covering 'addr' or NULL; assumes our lock_ is held.
351 static const Region* DoFindRegionLocked(uintptr_t addr);
352
353 // Verifying wrapper around regions_->insert(region)
354 // To be called to do InsertRegionLocked's work only!
355 inline static void DoInsertRegionLocked(const Region& region);
356 // Handle regions saved by InsertRegionLocked into a tmp static array
357 // by calling insert_func on them.
358 inline static void HandleSavedRegionsLocked(
359 void (*insert_func)(const Region& region));
360
361 // Restore buckets saved in a tmp static array by GetBucket to the bucket
362 // table where all buckets eventually should be.
363 static void RestoreSavedBucketsLocked();
364
365 // Wrapper around DoInsertRegionLocked
366 // that handles the case of recursive allocator calls.
367 inline static void InsertRegionLocked(const Region& region);
368
369 // Record addition of a memory region at address "start" of size "size"
370 // (called from our mmap/mremap/sbrk hooks).
371 static void RecordRegionAddition(const void* start, size_t size);
372 // Record deletion of a memory region at address "start" of size "size"
373 // (called from our munmap/mremap/sbrk hooks).
374 static void RecordRegionRemoval(const void* start, size_t size);
375
376 // Record deletion of a memory region of size "size" in a bucket whose
377 // caller stack trace is "key". The stack trace is used to a depth of
378 // "depth" at most.
379 static void RecordRegionRemovalInBucket(int depth,
380 const void* const key[],
381 size_t size);
382
383 // Hooks for MallocHook
384 static void MmapHook(const void* result,
385 const void* start, size_t size,
386 int prot, int flags,
387 int fd, off_t offset);
388 static void MunmapHook(const void* ptr, size_t size);
389 static void MremapHook(const void* result, const void* old_addr,
390 size_t old_size, size_t new_size, int flags,
391 const void* new_addr);
392 static void SbrkHook(const void* result, ptrdiff_t increment);
393
394 // Log all memory regions; Useful for debugging only.
395 // Assumes Lock() is held
396 static void LogAllLocked();
397
398 DISALLOW_COPY_AND_ASSIGN(MemoryRegionMap);
399};
400
401template <class Type>
402void MemoryRegionMap::IterateBuckets(
403 void (*callback)(const HeapProfileBucket*, Type), Type callback_arg) {
404 for (int index = 0; index < kHashTableSize; index++) {
405 for (HeapProfileBucket* bucket = bucket_table_[index];
406 bucket != NULL;
407 bucket = bucket->next) {
408 callback(bucket, callback_arg);
409 }
410 }
411}
412
413#endif // BASE_MEMORY_REGION_MAP_H_