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.
4
5"""
6The test scenario is as follows:
7VA space: |0     |4k    |8k    |12k   |16k   |20k  ... |64k      |65k      |66k
8Mmaps:    [    anon 1  ][anon 2]      [anon 3]     ... [  exe1  ][  exe2  ]
9Resident: *******-------*******-------*******      (*:resident, -:not resident)
10Allocs:     <1> <2>   <         3       >
11             |   |              |
12S.Traces:    |   |              +-----------> st1[exe1 + 0, exe1 + 4]
13             |   +--------------------------> st1[exe1 + 0, exe1 + 4]
14             +------------------------------> st2[exe1 + 0, exe2 + 4, post-exe2]
15
16Furthermore, the exe2 is a file mapping with non-zero (8k) offset.
17"""
18
19import unittest
20
21from memory_inspector.core import memory_map
22from memory_inspector.core import native_heap
23from memory_inspector.core import stacktrace
24from memory_inspector.core import symbol
25
26from memory_inspector.core.memory_map import PAGE_SIZE
27
28
29class NativeHeapTest(unittest.TestCase):
30  def runTest(self):
31    nheap = native_heap.NativeHeap()
32
33    EXE_1_MM_BASE = 64 * PAGE_SIZE
34    EXE_2_MM_BASE = 65 * PAGE_SIZE
35    EXE_2_FILE_OFF = 8192
36    st1 = stacktrace.Stacktrace()
37    st1.Add(nheap.GetStackFrame(EXE_1_MM_BASE))
38    st1.Add(nheap.GetStackFrame(EXE_1_MM_BASE + 4))
39
40    st2 = stacktrace.Stacktrace()
41    st2.Add(nheap.GetStackFrame(EXE_1_MM_BASE))
42    st2.Add(nheap.GetStackFrame(EXE_2_MM_BASE + 4))
43    st2.Add(nheap.GetStackFrame(EXE_2_MM_BASE + PAGE_SIZE + 4))
44
45    # Check that GetStackFrames keeps one unique object instance per address.
46    # This is to guarantee that the symbolization logic (SymbolizeUsingSymbolDB)
47    # can cheaply iterate on distinct stack frames rather than re-processing
48    # every stack frame for each allocation (and save memory as well).
49    self.assertIs(st1[0], st2[0])
50    self.assertIsNot(st1[0], st1[1])
51    self.assertIsNot(st2[0], st2[1])
52
53    alloc1 = native_heap.Allocation(start=4, size=4, stack_trace=st1)
54    alloc2 = native_heap.Allocation(start=4090, size=8, stack_trace=st1)
55    alloc3 = native_heap.Allocation(start=8190, size=10000, stack_trace=st2)
56    nheap.Add(alloc1)
57    nheap.Add(alloc2)
58    nheap.Add(alloc3)
59
60    self.assertEqual(len(nheap.allocations), 3)
61    self.assertIn(alloc1, nheap.allocations)
62    self.assertIn(alloc2, nheap.allocations)
63    self.assertIn(alloc3, nheap.allocations)
64
65    ############################################################################
66    # Test the relativization (absolute address -> mmap + offset) logic.
67    ############################################################################
68    mmap = memory_map
69    mmap = memory_map.Map()
70    mmap.Add(memory_map.MapEntry(EXE_1_MM_BASE, EXE_1_MM_BASE + PAGE_SIZE - 1,
71        'rw--', '/d/exe1', 0))
72    mmap.Add(memory_map.MapEntry(EXE_2_MM_BASE, EXE_2_MM_BASE + PAGE_SIZE - 1,
73        'rw--', 'exe2',EXE_2_FILE_OFF))
74    # Entry for EXE_3 is deliberately missing to check the fallback behavior.
75
76    nheap.RelativizeStackFrames(mmap)
77
78    self.assertEqual(st1[0].exec_file_rel_path, '/d/exe1')
79    self.assertEqual(st1[0].exec_file_name, 'exe1')
80    self.assertEqual(st1[0].offset, 0)
81
82    self.assertEqual(st1[1].exec_file_rel_path, '/d/exe1')
83    self.assertEqual(st1[1].exec_file_name, 'exe1')
84    self.assertEqual(st1[1].offset, 4)
85
86    self.assertEqual(st2[0].exec_file_rel_path, '/d/exe1')
87    self.assertEqual(st2[0].exec_file_name, 'exe1')
88    self.assertEqual(st2[0].offset, 0)
89
90    self.assertEqual(st2[1].exec_file_rel_path, 'exe2')
91    self.assertEqual(st2[1].exec_file_name, 'exe2')
92    self.assertEqual(st2[1].offset, 4 + EXE_2_FILE_OFF)
93
94    self.assertIsNone(st2[2].exec_file_rel_path)
95    self.assertIsNone(st2[2].exec_file_name)
96    self.assertIsNone(st2[2].offset)
97
98    ############################################################################
99    # Test the symbolization logic.
100    ############################################################################
101    syms = symbol.Symbols()
102    syms.Add('/d/exe1', 0, symbol.Symbol('sym1', 'src1.c', 1))  # st1[0]
103    syms.Add('exe2', 4 + EXE_2_FILE_OFF, symbol.Symbol('sym3'))  # st2[1]
104
105    nheap.SymbolizeUsingSymbolDB(syms)
106    self.assertEqual(st1[0].symbol.name, 'sym1')
107    self.assertEqual(st1[0].symbol.source_info[0].source_file_path, 'src1.c')
108    self.assertEqual(st1[0].symbol.source_info[0].line_number, 1)
109
110    # st1[1] should have no symbol info, because we didn't provide any above.
111    self.assertIsNone(st1[1].symbol)
112
113    # st2[0] and st1[0] were the same Frame. Expect identical symbols instances.
114    self.assertIs(st2[0].symbol, st1[0].symbol)
115
116    # st2[1] should have a symbols name, but no source line info.
117    self.assertEqual(st2[1].symbol.name, 'sym3')
118    self.assertEqual(len(st2[1].symbol.source_info), 0)
119
120    # st2[2] should have no sym because we didn't even provide a mmap for exe3.
121    self.assertIsNone(st2[2].symbol)
122
123    ############################################################################
124    # Test the resident size calculation logic (intersects mmaps and allocs).
125    ############################################################################
126    mmap.Add(
127        memory_map.MapEntry(0, 8191, 'rw--', '', 0, resident_pages=[1]))
128    mmap.Add(
129        memory_map.MapEntry(8192, 12287, 'rw--', '', 0, resident_pages=[1]))
130    # [12k, 16k] is deliberately missing to check the fallback behavior.
131    mmap.Add(
132        memory_map.MapEntry(16384, 20479, 'rw--', '', 0, resident_pages=[1]))
133    nheap.CalculateResidentSize(mmap)
134
135    # alloc1 [4, 8] is fully resident because it lays in the first resident 4k.
136    self.assertEqual(alloc1.resident_size, 4)
137
138    # alloc2 [4090, 4098] should have only 6 resident bytes ([4090,4096]), but
139    # not the last two, which lay on the second page which is noijt resident.
140    self.assertEqual(alloc2.resident_size, 6)
141
142    # alloc3 [8190, 18190] is split as follows (* = resident):
143    #  [8190, 8192]: these 2 bytes are NOT resident, they lay in the 2nd page.
144    # *[8192, 12288]: the 3rd page is resident and is fully covered by alloc3.
145    #  [12288, 16384]: the 4th page is fully covered as well, but not resident.
146    # *[16384, 18190]: the 5th page is partially covered and resident.
147    self.assertEqual(alloc3.resident_size, (12288 - 8192) + (18190 - 16384))
148