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