• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2#
3# Copyright (C) 2021 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#      http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17import os
18from pathlib import Path
19
20from binary_cache_builder import BinaryCacheBuilder
21from simpleperf_utils import (Addr2Nearestline, BinaryFinder, Objdump, ReadElf,
22                              SourceFileSearcher, is_windows, remove)
23from . test_utils import TestBase, TestHelper
24
25
26class TestTools(TestBase):
27    def test_addr2nearestline(self):
28        self.run_addr2nearestline_test(True)
29        self.run_addr2nearestline_test(False)
30
31    def run_addr2nearestline_test(self, with_function_name):
32        test_map = {
33            '/simpleperf_runtest_two_functions_arm64': [
34                {
35                    'func_addr': 0x112c,
36                    'addr': 0x112c,
37                    'source': 'system/extras/simpleperf/runtest/two_functions.cpp:20',
38                    'function': 'main',
39                },
40                {
41                    'func_addr': 0x104c,
42                    'addr': 0x105c,
43                    'source': "system/extras/simpleperf/runtest/two_functions.cpp:7",
44                    'function': "Function1()",
45                },
46            ],
47            '/simpleperf_runtest_two_functions_arm': [
48                {
49                    'func_addr': 0x1304,
50                    'addr': 0x131a,
51                    'source': """system/extras/simpleperf/runtest/two_functions.cpp:8
52                                 system/extras/simpleperf/runtest/two_functions.cpp:22""",
53                    'function': """Function1()
54                                   main""",
55                },
56                {
57                    'func_addr': 0x1304,
58                    'addr': 0x131c,
59                    'source': """system/extras/simpleperf/runtest/two_functions.cpp:16
60                                 system/extras/simpleperf/runtest/two_functions.cpp:23""",
61                    'function': """Function2()
62                                   main""",
63                }
64            ],
65            '/simpleperf_runtest_two_functions_x86_64': [
66                {
67                    'func_addr': 0x19e0,
68                    'addr': 0x19f6,
69                    'source': """system/extras/simpleperf/runtest/two_functions.cpp:8
70                                 system/extras/simpleperf/runtest/two_functions.cpp:22""",
71                    'function': """Function1()
72                                   main""",
73                },
74                {
75                    'func_addr': 0x19e0,
76                    'addr': 0x1a19,
77                    'source': """system/extras/simpleperf/runtest/two_functions.cpp:16
78                                 system/extras/simpleperf/runtest/two_functions.cpp:23""",
79                    'function': """Function2()
80                                   main""",
81                }
82            ],
83            '/simpleperf_runtest_two_functions_x86': [
84                {
85                    'func_addr': 0x16e0,
86                    'addr': 0x16f6,
87                    'source': """system/extras/simpleperf/runtest/two_functions.cpp:8
88                                 system/extras/simpleperf/runtest/two_functions.cpp:22""",
89                    'function': """Function1()
90                                   main""",
91                },
92                {
93                    'func_addr': 0x16e0,
94                    'addr': 0x1710,
95                    'source': """system/extras/simpleperf/runtest/two_functions.cpp:16
96                                 system/extras/simpleperf/runtest/two_functions.cpp:23""",
97                    'function': """Function2()
98                                   main""",
99                }
100            ],
101        }
102
103        binary_finder = BinaryFinder(TestHelper.testdata_dir, ReadElf(TestHelper.ndk_path))
104        addr2line = Addr2Nearestline(TestHelper.ndk_path, binary_finder, with_function_name)
105        for dso_path in test_map:
106            test_addrs = test_map[dso_path]
107            for test_addr in test_addrs:
108                addr2line.add_addr(dso_path, None, test_addr['func_addr'], test_addr['addr'])
109        addr2line.convert_addrs_to_lines(4)
110        for dso_path in test_map:
111            dso = addr2line.get_dso(dso_path)
112            self.assertIsNotNone(dso, dso_path)
113            test_addrs = test_map[dso_path]
114            for test_addr in test_addrs:
115                expected_files = []
116                expected_lines = []
117                expected_functions = []
118                for line in test_addr['source'].split('\n'):
119                    items = line.split(':')
120                    expected_files.append(items[0].strip())
121                    expected_lines.append(int(items[1]))
122                for line in test_addr['function'].split('\n'):
123                    expected_functions.append(line.strip())
124                self.assertEqual(len(expected_files), len(expected_functions))
125
126                if with_function_name:
127                    expected_source = list(zip(expected_files, expected_lines, expected_functions))
128                else:
129                    expected_source = list(zip(expected_files, expected_lines))
130
131                actual_source = addr2line.get_addr_source(dso, test_addr['addr'])
132                if is_windows():
133                    self.assertIsNotNone(actual_source, 'for %s:0x%x' %
134                                         (dso_path, test_addr['addr']))
135                    for i, source in enumerate(actual_source):
136                        new_source = list(source)
137                        new_source[0] = new_source[0].replace('\\', '/')
138                        actual_source[i] = tuple(new_source)
139
140                self.assertEqual(actual_source, expected_source,
141                                 'for %s:0x%x, expected source %s, actual source %s' %
142                                 (dso_path, test_addr['addr'], expected_source, actual_source))
143
144    def test_addr2nearestline_parse_output(self):
145        output = """
1460x104c
147system/extras/simpleperf/runtest/two_functions.cpp:6:0
148
1490x1094
150system/extras/simpleperf/runtest/two_functions.cpp:9:10
151
1520x10bb
153system/extras/simpleperf/runtest/two_functions.cpp:11:1
154
1550x10bc
156system/extras/simpleperf/runtest/two_functions.cpp:13:0
157
1580x1104
159system/extras/simpleperf/runtest/two_functions.cpp:16:10
160
1610x112b
162system/extras/simpleperf/runtest/two_functions.cpp:18:1
163
1640x112c
165system/extras/simpleperf/runtest/two_functions.cpp:20:0
166
1670x113c
168system/extras/simpleperf/runtest/two_functions.cpp:22:5
169
1700x1140
171system/extras/simpleperf/runtest/two_functions.cpp:23:5
172
1730x1147
174system/extras/simpleperf/runtest/two_functions.cpp:21:3
175        """
176        dso = Addr2Nearestline.Dso(None)
177        binary_finder = BinaryFinder(TestHelper.testdata_dir, ReadElf(TestHelper.ndk_path))
178        addr2line = Addr2Nearestline(TestHelper.ndk_path, binary_finder, False)
179        addr_map = addr2line.parse_line_output(output, dso)
180        expected_addr_map = {
181            0x104c: [('system/extras/simpleperf/runtest/two_functions.cpp', 6)],
182            0x1094: [('system/extras/simpleperf/runtest/two_functions.cpp', 9)],
183            0x10bb: [('system/extras/simpleperf/runtest/two_functions.cpp', 11)],
184            0x10bc: [('system/extras/simpleperf/runtest/two_functions.cpp', 13)],
185            0x1104: [('system/extras/simpleperf/runtest/two_functions.cpp', 16)],
186            0x112b: [('system/extras/simpleperf/runtest/two_functions.cpp', 18)],
187            0x112c: [('system/extras/simpleperf/runtest/two_functions.cpp', 20)],
188            0x113c: [('system/extras/simpleperf/runtest/two_functions.cpp', 22)],
189            0x1140: [('system/extras/simpleperf/runtest/two_functions.cpp', 23)],
190            0x1147: [('system/extras/simpleperf/runtest/two_functions.cpp', 21)],
191        }
192        self.assertEqual(len(expected_addr_map), len(addr_map))
193        for addr in expected_addr_map:
194            expected_source_list = expected_addr_map[addr]
195            source_list = addr_map[addr]
196            self.assertEqual(len(expected_source_list), len(source_list))
197            for expected_source, source in zip(expected_source_list, source_list):
198                file_path = dso.file_id_to_name[source[0]]
199                self.assertEqual(file_path, expected_source[0])
200                self.assertEqual(source[1], expected_source[1])
201
202    def test_objdump(self):
203        test_map = {
204            '/simpleperf_runtest_two_functions_arm64': {
205                'start_addr': 0x112c,
206                'len': 28,
207                'expected_items': [
208                    ('main', 0),
209                    ('two_functions.cpp:20', 0),
210                    ('1134:      	add	x29, sp, #16', 0x1134),
211                ],
212            },
213            '/simpleperf_runtest_two_functions_arm': {
214                'start_addr': 0x1304,
215                'len': 40,
216                'expected_items': [
217                    ('main', 0),
218                    ('two_functions.cpp:20', 0),
219                    ('1318:      	bne	0x1312 <main+0xe>', 0x1318),
220                ],
221            },
222            '/simpleperf_runtest_two_functions_x86_64': {
223                'start_addr': 0x19e0,
224                'len': 151,
225                'expected_items': [
226                    ('main', 0),
227                    ('two_functions.cpp:20', 0),
228                    (r'19f0:      	movl	%eax, 9314(%rip)', 0x19f0),
229                ],
230            },
231            '/simpleperf_runtest_two_functions_x86': {
232                'start_addr': 0x16e0,
233                'len': 65,
234                'expected_items': [
235                    ('main', 0),
236                    ('two_functions.cpp:20', 0),
237                    (r'16f7:      	cmpl	$100000000, %ecx', 0x16f7),
238                ],
239            },
240        }
241        binary_finder = BinaryFinder(TestHelper.testdata_dir, ReadElf(TestHelper.ndk_path))
242        objdump = Objdump(TestHelper.ndk_path, binary_finder)
243        for dso_path in test_map:
244            dso = test_map[dso_path]
245            dso_info = objdump.get_dso_info(dso_path, None)
246            self.assertIsNotNone(dso_info, dso_path)
247            disassemble_code = objdump.disassemble_code(dso_info, dso['start_addr'], dso['len'])
248            self.assertTrue(disassemble_code, dso_path)
249            i = 0
250            for expected_line, expected_addr in dso['expected_items']:
251                found = False
252                while i < len(disassemble_code):
253                    line, addr = disassemble_code[i]
254                    if addr == expected_addr and expected_line in line:
255                        found = True
256                        i += 1
257                        break
258                    i += 1
259                if not found:
260                    s = '\n'.join('%s:0x%x' % item for item in disassemble_code)
261                    self.fail('for %s, %s:0x%x not found in disassemble code:\n%s' %
262                              (dso_path, expected_line, expected_addr, s))
263
264    def test_readelf(self):
265        test_map = {
266            'simpleperf_runtest_two_functions_arm64': {
267                'arch': 'arm64',
268                'build_id': '0xb4f1b49b0fe9e34e78fb14e5374c930c00000000',
269                'sections': ['.note.gnu.build-id', '.dynsym', '.text', '.rodata', '.eh_frame',
270                             '.eh_frame_hdr', '.debug_info',  '.debug_line', '.symtab'],
271            },
272            'simpleperf_runtest_two_functions_arm': {
273                'arch': 'arm',
274                'build_id': '0x6b5c2ee980465d306b580c5a8bc9767f00000000',
275            },
276            'simpleperf_runtest_two_functions_x86_64': {
277                'arch': 'x86_64',
278            },
279            'simpleperf_runtest_two_functions_x86': {
280                'arch': 'x86',
281            }
282        }
283        readelf = ReadElf(TestHelper.ndk_path)
284        for dso_path in test_map:
285            dso_info = test_map[dso_path]
286            path = os.path.join(TestHelper.testdata_dir, dso_path)
287            self.assertEqual(dso_info['arch'], readelf.get_arch(path))
288            if 'build_id' in dso_info:
289                self.assertEqual(dso_info['build_id'], readelf.get_build_id(path), dso_path)
290            if 'sections' in dso_info:
291                sections = readelf.get_sections(path)
292                for section in dso_info['sections']:
293                    self.assertIn(section, sections)
294        self.assertEqual(readelf.get_arch('not_exist_file'), 'unknown')
295        self.assertEqual(readelf.get_build_id('not_exist_file'), '')
296        self.assertEqual(readelf.get_sections('not_exist_file'), [])
297
298    def test_source_file_searcher(self):
299        searcher = SourceFileSearcher(
300            [TestHelper.testdata_path('SimpleperfExampleCpp'),
301             TestHelper.testdata_path('SimpleperfExampleKotlin')])
302
303        def format_path(path):
304            return os.path.join(TestHelper.testdata_dir, path.replace('/', os.sep))
305        # Find a C++ file with pure file name.
306        self.assertEqual(
307            format_path('SimpleperfExampleCpp/app/src/main/cpp/native-lib.cpp'),
308            searcher.get_real_path('native-lib.cpp'))
309        # Find a C++ file with an absolute file path.
310        self.assertEqual(
311            format_path('SimpleperfExampleCpp/app/src/main/cpp/native-lib.cpp'),
312            searcher.get_real_path('/data/native-lib.cpp'))
313        # Find a Java file.
314        self.assertEqual(
315            format_path(
316                'SimpleperfExampleCpp/app/src/main/java/simpleperf/example/cpp/MainActivity.java'),
317            searcher.get_real_path('cpp/MainActivity.java'))
318        # Find a Kotlin file.
319        self.assertEqual(
320            format_path(
321                'SimpleperfExampleKotlin/app/src/main/java/simpleperf/example/kotlin/' +
322                'MainActivity.kt'),
323            searcher.get_real_path('MainActivity.kt'))
324
325    def test_is_elf_file(self):
326        self.assertTrue(ReadElf.is_elf_file(TestHelper.testdata_path(
327            'simpleperf_runtest_two_functions_arm')))
328        with open('not_elf', 'wb') as fh:
329            fh.write(b'\x90123')
330        try:
331            self.assertFalse(ReadElf.is_elf_file('not_elf'))
332        finally:
333            remove('not_elf')
334
335    def test_binary_finder(self):
336        # Create binary_cache.
337        binary_cache_builder = BinaryCacheBuilder(TestHelper.ndk_path, False)
338        elf_name = 'simpleperf_runtest_two_functions_arm'
339        elf_path = TestHelper.testdata_path(elf_name)
340        readelf = ReadElf(TestHelper.ndk_path)
341        build_id = readelf.get_build_id(elf_path)
342        self.assertGreater(len(build_id), 0)
343        binary_cache_builder.binaries[elf_name] = build_id
344
345        filename_without_build_id = '/data/symfs_without_build_id/elf'
346        binary_cache_builder.binaries[filename_without_build_id] = ''
347
348        binary_cache_builder.copy_binaries_from_symfs_dirs([TestHelper.testdata_dir])
349        binary_cache_builder.create_build_id_list()
350
351        # Test BinaryFinder.
352        path_in_binary_cache = binary_cache_builder.find_path_in_cache(elf_name)
353        binary_finder = BinaryFinder(binary_cache_builder.binary_cache_dir, readelf)
354        # Find binary using build id.
355        path = binary_finder.find_binary('[not_exist_file]', build_id)
356        self.assertEqual(path, path_in_binary_cache)
357        # Find binary using path.
358        path = binary_finder.find_binary(filename_without_build_id, None)
359        self.assertIsNotNone(path)
360        # Find binary using absolute path.
361        path = binary_finder.find_binary(str(path_in_binary_cache), None)
362        self.assertEqual(path, path_in_binary_cache)
363
364        # The binary should has a matched build id.
365        path = binary_finder.find_binary('/' + elf_name, 'wrong_build_id')
366        self.assertIsNone(path)
367