15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Copyright 2014 The Chromium Authors. All rights reserved.
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# found in the LICENSE file.
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)from memory_inspector.core import memory_map
65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)from memory_inspector.core import stacktrace
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)from memory_inspector.core import symbol
85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
95d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)from memory_inspector.core.memory_map import PAGE_SIZE
105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class NativeHeap(object):
135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """A snapshot of outstanding (i.e. not freed) native allocations.
145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  This is typically obtained by calling |backends.Process|.DumpNativeHeap()
167dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  """
175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def __init__(self):
195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.allocations = []  # list of individual |Allocation|s.
203551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    self.stack_frames = {}  # absolute_address (int) -> |stacktrace.Frame|.
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def Add(self, allocation):
23c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch    assert(isinstance(allocation, Allocation))
245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.allocations += [allocation]
255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def GetStackFrame(self, absolute_addr):
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Guarantees that multiple calls with the same addr return the same obj."""
281320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    assert(isinstance(absolute_addr, (long, int)))
29c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    stack_frame = self.stack_frames.get(absolute_addr)
30c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    if not stack_frame:
311320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      stack_frame = stacktrace.Frame(absolute_addr)
321320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      self.stack_frames[absolute_addr] = stack_frame
331320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    return stack_frame
341320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
351320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  def SymbolizeUsingSymbolDB(self, symbols):
361320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    assert(isinstance(symbols, symbol.Symbols))
371320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    for stack_frame in self.stack_frames.itervalues():
381320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      if not stack_frame.exec_file_rel_path:
391320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        continue
401320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      sym = symbols.Lookup(stack_frame.exec_file_rel_path, stack_frame.offset)
411320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      if sym:
421320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        stack_frame.SetSymbolInfo(sym)
431320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
441320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  def RelativizeStackFrames(self, mmap):
451320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    """Turns stack frames' absolute addresses into mmap relative addresses.
461320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
471320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    For each absolute address, the containing mmap is looked up and the frame
481320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    is decorated with the mapped file + relative address in the file."""
491320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    assert(isinstance(mmap, memory_map.Map))
501320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    for abs_addr, stack_frame in self.stack_frames.iteritems():
51c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      assert(abs_addr == stack_frame.address)
525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      map_entry = mmap.Lookup(abs_addr)
53c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch      if not map_entry:
545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        continue
555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      stack_frame.SetExecFileInfo(map_entry.mapped_file,
565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                  map_entry.GetRelativeFileOffset(abs_addr))
57
58  def CalculateResidentSize(self, mmap):
59    """Updates the |Allocation|.|resident_size|s by looking at mmap stats.
60
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.
63    """
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:
72      #
73      # VA space:  |0    |4k   |8k   |12k  |16k  |20k  |24k  |28k  |32k  |
74      # Mmaps:     [   mm 1   ][ mm2 ]           [          map 3        ]
75      # Allocs:      <a1>  <  a2  >                       <      a3      >
76      #
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
83      mm = None
84      while cur_start < alloc.end:
85        if not mm or not mm.Contains(cur_start):
86          mm = mmap.Lookup(cur_start)
87        if mm:
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)
97
98
99class Allocation(object):
100  """Records profiling information about a native heap allocation.
101
102  Args:
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.
110  """
111
112  def __init__(self, size, stack_trace, start=0, flags=0, resident_size=0):
113    assert(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.
118    self.flags = flags
119    self.resident_size = resident_size  #  see |CalculateResidentSize|.
120
121  @property
122  def end(self):
123    return self.start + self.size - 1
124
125  def __str__(self):
126    return '%d : %s' % (self.size, self.stack_trace)
127