1 # Copyright 2014 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.
5 from memory_inspector.core import memory_map
6 from memory_inspector.core import stacktrace
7 from memory_inspector.core import symbol
9 from memory_inspector.core.memory_map import PAGE_SIZE
12 class NativeHeap(object):
13 """A snapshot of outstanding (i.e. not freed) native allocations.
15 This is typically obtained by calling |backends.Process|.DumpNativeHeap()
19 self.allocations = [] # list of individual |Allocation|s.
20 self.stack_frames = {} # absolute_address (int) -> |stacktrace.Frame|.
22 def Add(self, allocation):
23 assert(isinstance(allocation, Allocation))
24 self.allocations += [allocation]
26 def GetStackFrame(self, absolute_addr):
27 """Guarantees that multiple calls with the same addr return the same obj."""
28 assert(isinstance(absolute_addr, (long, int)))
29 stack_frame = self.stack_frames.get(absolute_addr)
31 stack_frame = stacktrace.Frame(absolute_addr)
32 self.stack_frames[absolute_addr] = stack_frame
35 def SymbolizeUsingSymbolDB(self, symbols):
36 assert(isinstance(symbols, symbol.Symbols))
37 for stack_frame in self.stack_frames.itervalues():
38 if not stack_frame.exec_file_rel_path:
40 sym = symbols.Lookup(stack_frame.exec_file_rel_path, stack_frame.offset)
42 stack_frame.SetSymbolInfo(sym)
44 def RelativizeStackFrames(self, mmap):
45 """Turns stack frames' absolute addresses into mmap relative addresses.
47 For each absolute address, the containing mmap is looked up and the frame
48 is decorated with the mapped file + relative address in the file."""
49 assert(isinstance(mmap, memory_map.Map))
50 for abs_addr, stack_frame in self.stack_frames.iteritems():
51 assert(abs_addr == stack_frame.address)
52 map_entry = mmap.Lookup(abs_addr)
55 stack_frame.SetExecFileInfo(map_entry.mapped_file,
56 map_entry.GetRelativeFileOffset(abs_addr))
58 def CalculateResidentSize(self, mmap):
59 """Updates the |Allocation|.|resident_size|s by looking at mmap stats.
61 Not all the allocated memory is always used (read: resident). This function
62 estimates the resident size of an allocation intersecting the mmaps dump.
64 assert(isinstance(mmap, memory_map.Map))
65 for alloc in self.allocations:
66 # This function loops over all the memory pages that intersect, partially
67 # or fully, with each allocation. For each of them, the allocation is
68 # attributed a resident size equal to the size of intersecting range iff
69 # the page is resident.
70 # The tricky part is that, in the general case, an allocation can span
71 # over multiple (contiguous) mmaps. See the chart below for a reference:
73 # VA space: |0 |4k |8k |12k |16k |20k |24k |28k |32k |
74 # Mmaps: [ mm 1 ][ mm2 ] [ map 3 ]
75 # Allocs: <a1> < a2 > < a3 >
77 # Note: this accounting technique is not fully correct but is generally a
78 # good tradeoff between accuracy and speed of profiling. The OS provides
79 # resident information with the page granularity (typ. 4k). Finer values
80 # would require more fancy techniques based, for instance, on run-time
81 # instrumentation tools like Valgrind or *sanitizer.
82 cur_start = alloc.start
84 while cur_start < alloc.end:
85 if not mm or not mm.Contains(cur_start):
86 mm = mmap.Lookup(cur_start)
88 page, page_off = mm.GetRelativeMMOffset(cur_start)
89 if mm.IsPageResident(page):
90 page_end = mm.start + page * PAGE_SIZE + PAGE_SIZE - 1
91 alloc_memory_in_current_page = PAGE_SIZE - page_off
92 if alloc.end < page_end:
93 alloc_memory_in_current_page -= page_end - alloc.end
94 alloc.resident_size += alloc_memory_in_current_page
95 # Move to the next page boundary.
96 cur_start = (cur_start + PAGE_SIZE) & ~(PAGE_SIZE - 1)
99 class Allocation(object):
100 """Records profiling information about a native heap allocation.
103 size: size of the allocation, in bytes.
104 stack_trace: the allocation call-site. See |stacktrace.Stacktrace|.
105 start: (Optional) Absolute start address in the process VMA. It is
106 required only for |CalculateResidentSize|.
107 flags: (Optional) More details about the call site (e.g., mmap vs malloc).
108 resident_size: this is normally obtained through |CalculateResidentSize|
109 and is part of the initializer just for deserialization purposes.
112 def __init__(self, size, stack_trace, start=0, flags=0, resident_size=0):
114 assert(isinstance(stack_trace, stacktrace.Stacktrace))
115 self.size = size # in bytes.
116 self.stack_trace = stack_trace
117 self.start = start # Optional, for using the resident size logic.
119 self.resident_size = resident_size # see |CalculateResidentSize|.
123 return self.start + self.size - 1
126 return '%d : %s' % (self.size, self.stack_trace)