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 io
18import os
19from pathlib import Path
20
21from binary_cache_builder import BinaryCacheBuilder
22from simpleperf_utils import (Addr2Nearestline, AddrRange, BinaryFinder, Disassembly, Objdump,
23                              ReadElf, SourceFileSearcher, is_windows, remove)
24from . test_utils import TestBase, TestHelper
25
26
27class TestTools(TestBase):
28    def test_addr2nearestline(self):
29        self.run_addr2nearestline_test(True)
30        self.run_addr2nearestline_test(False)
31
32    def run_addr2nearestline_test(self, with_function_name):
33        test_map = {
34            '/simpleperf_runtest_two_functions_arm64': [
35                {
36                    'func_addr': 0x112c,
37                    'addr': 0x112c,
38                    'source': 'system/extras/simpleperf/runtest/two_functions.cpp:20',
39                    'function': 'main',
40                },
41                {
42                    'func_addr': 0x104c,
43                    'addr': 0x105c,
44                    'source': "system/extras/simpleperf/runtest/two_functions.cpp:7",
45                    'function': "Function1()",
46                },
47            ],
48            '/simpleperf_runtest_two_functions_arm': [
49                {
50                    'func_addr': 0x1304,
51                    'addr': 0x131a,
52                    'source': """system/extras/simpleperf/runtest/two_functions.cpp:8
53                                 system/extras/simpleperf/runtest/two_functions.cpp:22""",
54                    'function': """Function1()
55                                   main""",
56                },
57                {
58                    'func_addr': 0x1304,
59                    'addr': 0x131c,
60                    'source': """system/extras/simpleperf/runtest/two_functions.cpp:16
61                                 system/extras/simpleperf/runtest/two_functions.cpp:23""",
62                    'function': """Function2()
63                                   main""",
64                }
65            ],
66            '/simpleperf_runtest_two_functions_x86_64': [
67                {
68                    'func_addr': 0x19e0,
69                    'addr': 0x19f6,
70                    'source': """system/extras/simpleperf/runtest/two_functions.cpp:8
71                                 system/extras/simpleperf/runtest/two_functions.cpp:22""",
72                    'function': """Function1()
73                                   main""",
74                },
75                {
76                    'func_addr': 0x19e0,
77                    'addr': 0x1a19,
78                    'source': """system/extras/simpleperf/runtest/two_functions.cpp:16
79                                 system/extras/simpleperf/runtest/two_functions.cpp:23""",
80                    'function': """Function2()
81                                   main""",
82                }
83            ],
84            '/simpleperf_runtest_two_functions_x86': [
85                {
86                    'func_addr': 0x16e0,
87                    'addr': 0x16f6,
88                    'source': """system/extras/simpleperf/runtest/two_functions.cpp:8
89                                 system/extras/simpleperf/runtest/two_functions.cpp:22""",
90                    'function': """Function1()
91                                   main""",
92                },
93                {
94                    'func_addr': 0x16e0,
95                    'addr': 0x1710,
96                    'source': """system/extras/simpleperf/runtest/two_functions.cpp:16
97                                 system/extras/simpleperf/runtest/two_functions.cpp:23""",
98                    'function': """Function2()
99                                   main""",
100                }
101            ],
102        }
103
104        binary_finder = BinaryFinder(TestHelper.testdata_dir, ReadElf(TestHelper.ndk_path))
105        addr2line = Addr2Nearestline(TestHelper.ndk_path, binary_finder, with_function_name)
106        for dso_path in test_map:
107            test_addrs = test_map[dso_path]
108            for test_addr in test_addrs:
109                addr2line.add_addr(dso_path, None, test_addr['func_addr'], test_addr['addr'])
110        addr2line.convert_addrs_to_lines(4)
111        for dso_path in test_map:
112            dso = addr2line.get_dso(dso_path)
113            self.assertIsNotNone(dso, dso_path)
114            test_addrs = test_map[dso_path]
115            for test_addr in test_addrs:
116                expected_files = []
117                expected_lines = []
118                expected_functions = []
119                for line in test_addr['source'].split('\n'):
120                    items = line.split(':')
121                    expected_files.append(items[0].strip())
122                    expected_lines.append(int(items[1]))
123                for line in test_addr['function'].split('\n'):
124                    expected_functions.append(line.strip())
125                self.assertEqual(len(expected_files), len(expected_functions))
126
127                if with_function_name:
128                    expected_source = list(zip(expected_files, expected_lines, expected_functions))
129                else:
130                    expected_source = list(zip(expected_files, expected_lines))
131
132                actual_source = addr2line.get_addr_source(dso, test_addr['addr'])
133                if is_windows():
134                    self.assertIsNotNone(actual_source, 'for %s:0x%x' %
135                                         (dso_path, test_addr['addr']))
136                    for i, source in enumerate(actual_source):
137                        new_source = list(source)
138                        new_source[0] = new_source[0].replace('\\', '/')
139                        actual_source[i] = tuple(new_source)
140
141                self.assertEqual(actual_source, expected_source,
142                                 'for %s:0x%x, expected source %s, actual source %s' %
143                                 (dso_path, test_addr['addr'], expected_source, actual_source))
144
145    def test_addr2nearestline_parse_output(self):
146        output = """
1470x104c
148system/extras/simpleperf/runtest/two_functions.cpp:6:0
149
1500x1094
151system/extras/simpleperf/runtest/two_functions.cpp:9:10
152
1530x10bb
154system/extras/simpleperf/runtest/two_functions.cpp:11:1
155
1560x10bc
157system/extras/simpleperf/runtest/two_functions.cpp:13:0
158
1590x1104
160system/extras/simpleperf/runtest/two_functions.cpp:16:10
161
1620x112b
163system/extras/simpleperf/runtest/two_functions.cpp:18:1
164
1650x112c
166system/extras/simpleperf/runtest/two_functions.cpp:20:0
167
1680x113c
169system/extras/simpleperf/runtest/two_functions.cpp:22:5
170
1710x1140
172system/extras/simpleperf/runtest/two_functions.cpp:23:5
173
1740x1147
175system/extras/simpleperf/runtest/two_functions.cpp:21:3
176        """
177        dso = Addr2Nearestline.Dso(None)
178        binary_finder = BinaryFinder(TestHelper.testdata_dir, ReadElf(TestHelper.ndk_path))
179        addr2line = Addr2Nearestline(TestHelper.ndk_path, binary_finder, False)
180        addr_map = addr2line.parse_line_output(output, dso)
181        expected_addr_map = {
182            0x104c: [('system/extras/simpleperf/runtest/two_functions.cpp', 6)],
183            0x1094: [('system/extras/simpleperf/runtest/two_functions.cpp', 9)],
184            0x10bb: [('system/extras/simpleperf/runtest/two_functions.cpp', 11)],
185            0x10bc: [('system/extras/simpleperf/runtest/two_functions.cpp', 13)],
186            0x1104: [('system/extras/simpleperf/runtest/two_functions.cpp', 16)],
187            0x112b: [('system/extras/simpleperf/runtest/two_functions.cpp', 18)],
188            0x112c: [('system/extras/simpleperf/runtest/two_functions.cpp', 20)],
189            0x113c: [('system/extras/simpleperf/runtest/two_functions.cpp', 22)],
190            0x1140: [('system/extras/simpleperf/runtest/two_functions.cpp', 23)],
191            0x1147: [('system/extras/simpleperf/runtest/two_functions.cpp', 21)],
192        }
193        self.assertEqual(len(expected_addr_map), len(addr_map))
194        for addr in expected_addr_map:
195            expected_source_list = expected_addr_map[addr]
196            source_list = addr_map[addr]
197            self.assertEqual(len(expected_source_list), len(source_list))
198            for expected_source, source in zip(expected_source_list, source_list):
199                file_path = dso.file_id_to_name[source[0]]
200                self.assertEqual(file_path, expected_source[0])
201                self.assertEqual(source[1], expected_source[1])
202
203    def test_objdump(self):
204        test_map = {
205            '/simpleperf_runtest_two_functions_arm64': {
206                'start_addr': 0x112c,
207                'len': 28,
208                'expected_items': [
209                    ('main', 0),
210                    ('two_functions.cpp:20', 0),
211                    ('1134:      	add	x29, sp, #0x10', 0x1134),
212                ],
213            },
214            '/simpleperf_runtest_two_functions_arm': {
215                'start_addr': 0x1304,
216                'len': 40,
217                'expected_items': [
218                    ('main', 0),
219                    ('two_functions.cpp:20', 0),
220                    ('1318:      	bne	0x1312 <main+0xe>', 0x1318),
221                ],
222            },
223            '/simpleperf_runtest_two_functions_x86_64': {
224                'start_addr': 0x19e0,
225                'len': 151,
226                'expected_items': [
227                    ('main', 0),
228                    ('two_functions.cpp:20', 0),
229                    (r'19f0:      	movl	%eax, 0x2462(%rip)', 0x19f0),
230                ],
231            },
232            '/simpleperf_runtest_two_functions_x86': {
233                'start_addr': 0x16e0,
234                'len': 65,
235                'expected_items': [
236                    ('main', 0),
237                    ('two_functions.cpp:20', 0),
238                    (r'16f7:      	cmpl	$0x5f5e100, %ecx', 0x16f7),
239                ],
240            },
241        }
242        binary_finder = BinaryFinder(TestHelper.testdata_dir, ReadElf(TestHelper.ndk_path))
243        objdump = Objdump(TestHelper.ndk_path, binary_finder)
244        for dso_path in test_map:
245            dso = test_map[dso_path]
246            dso_info = objdump.get_dso_info(dso_path, None)
247            self.assertIsNotNone(dso_info, dso_path)
248            addr_range = AddrRange(dso['start_addr'], dso['len'])
249            disassembly = objdump.disassemble_function(dso_info, addr_range)
250            self.assertTrue(disassembly, dso_path)
251            self._check_disassembly(disassembly, dso_path, dso)
252
253            result = objdump.disassemble_functions(dso_info, [addr_range])
254            self.assertTrue(result, dso_path)
255            self.assertEqual(len(result), 1)
256            self._check_disassembly(result[0], dso_path, dso)
257
258    def _check_disassembly(self, disassembly: Disassembly, dso_path: str, dso) -> None:
259        disassemble_code = disassembly.lines
260        i = 0
261        for expected_line, expected_addr in dso['expected_items']:
262            found = False
263            while i < len(disassemble_code):
264                line, addr = disassemble_code[i]
265                if addr == expected_addr and expected_line in line:
266                    found = True
267                    i += 1
268                    break
269                i += 1
270            if not found:
271                s = '\n'.join('%s:0x%x' % item for item in disassemble_code)
272                self.fail('for %s, %s:0x%x not found in disassemble code:\n%s' %
273                          (dso_path, expected_line, expected_addr, s))
274
275    def test_objdump_parse_disassembly_for_functions(self):
276        # Parse kernel disassembly.
277        s = """
278ffffffc008000000 <_text>:
279; _text():
280; arch/arm64/kernel/head.S:60
281ffffffc008000000:      	ccmp	x18, #0x0, #0xd, pl
282ffffffc008000004:      	b	0xffffffc009b2a37c <primary_entry>
283
284ffffffc008000008 <$d.1>:
285ffffffc008000008: 00 00 00 00  	.word	0x00000000
286ffffffc0089bbb30 <readl>:
287; readl():
288; include/asm-generic/io.h:218
289ffffffc0089bbb30:      	paciasp
290ffffffc0089bbb34:      	stp	x29, x30, [sp, #-0x30]!
291        """
292        addr_ranges = [AddrRange(0xffffffc008000000, 8),
293                       AddrRange(0xffffffc008000010, 10),
294                       AddrRange(0xffffffc0089bbb30, 20)]
295        binary_finder = BinaryFinder(TestHelper.testdata_dir, ReadElf(TestHelper.ndk_path))
296        objdump = Objdump(TestHelper.ndk_path, binary_finder)
297        result = objdump._parse_disassembly_for_functions(io.StringIO(s), addr_ranges)
298        self.assertEqual(len(result), 3)
299        self.assertEqual(
300            result[0].lines,
301            [('ffffffc008000000 <_text>:', 0xffffffc008000000),
302             ('; _text():', 0),
303             ('; arch/arm64/kernel/head.S:60', 0),
304             ('ffffffc008000000:      	ccmp	x18, #0x0, #0xd, pl', 0xffffffc008000000),
305             ('ffffffc008000004:      	b	0xffffffc009b2a37c <primary_entry>',
306                0xffffffc008000004),
307             ('', 0)])
308        self.assertEqual(len(result[1].lines), 0)
309        self.assertEqual(result[2].lines, [
310            ('ffffffc0089bbb30 <readl>:', 0xffffffc0089bbb30),
311            ('; readl():', 0),
312            ('; include/asm-generic/io.h:218', 0),
313            ('ffffffc0089bbb30:      	paciasp', 0xffffffc0089bbb30),
314            ('ffffffc0089bbb34:      	stp	x29, x30, [sp, #-0x30]!', 0xffffffc0089bbb34),
315            ('', 0)])
316
317        # Parse user space library disassembly.
318        s = """
3190000000000200000 <art::gc::collector::ConcurrentCopying::ProcessMarkStack()>:
320; art::gc::collector::ConcurrentCopying::ProcessMarkStack():
321; art/runtime/gc/collector/concurrent_copying.cc:2121
322  200000:      	stp	x29, x30, [sp, #-0x20]!
323  200004:      	stp	x20, x19, [sp, #0x10]
324        """
325        addr_ranges = [AddrRange(0x200000, 8)]
326        result = objdump._parse_disassembly_for_functions(io.StringIO(s), addr_ranges)
327        self.assertEqual(len(result), 1)
328        self.assertEqual(result[0].lines, [
329            ('0000000000200000 <art::gc::collector::ConcurrentCopying::ProcessMarkStack()>:',
330                0x200000),
331            ('; art::gc::collector::ConcurrentCopying::ProcessMarkStack():', 0),
332            ('; art/runtime/gc/collector/concurrent_copying.cc:2121', 0),
333            ('  200000:      	stp	x29, x30, [sp, #-0x20]!', 0x200000),
334            ('  200004:      	stp	x20, x19, [sp, #0x10]', 0x200004),
335            ('', 0)])
336
337    def test_readelf(self):
338        test_map = {
339            'simpleperf_runtest_two_functions_arm64': {
340                'arch': 'arm64',
341                'build_id': '0xb4f1b49b0fe9e34e78fb14e5374c930c00000000',
342                'sections': ['.note.gnu.build-id', '.dynsym', '.text', '.rodata', '.eh_frame',
343                             '.eh_frame_hdr', '.debug_info',  '.debug_line', '.symtab'],
344            },
345            'simpleperf_runtest_two_functions_arm': {
346                'arch': 'arm',
347                'build_id': '0x6b5c2ee980465d306b580c5a8bc9767f00000000',
348            },
349            'simpleperf_runtest_two_functions_x86_64': {
350                'arch': 'x86_64',
351            },
352            'simpleperf_runtest_two_functions_x86': {
353                'arch': 'x86',
354            }
355        }
356        readelf = ReadElf(TestHelper.ndk_path)
357        for dso_path in test_map:
358            dso_info = test_map[dso_path]
359            path = os.path.join(TestHelper.testdata_dir, dso_path)
360            self.assertEqual(dso_info['arch'], readelf.get_arch(path))
361            if 'build_id' in dso_info:
362                self.assertEqual(dso_info['build_id'], readelf.get_build_id(path), dso_path)
363            if 'sections' in dso_info:
364                sections = readelf.get_sections(path)
365                for section in dso_info['sections']:
366                    self.assertIn(section, sections)
367        self.assertEqual(readelf.get_arch('not_exist_file'), 'unknown')
368        self.assertEqual(readelf.get_build_id('not_exist_file'), '')
369        self.assertEqual(readelf.get_sections('not_exist_file'), [])
370
371    def test_source_file_searcher(self):
372        searcher = SourceFileSearcher(
373            [TestHelper.testdata_path('SimpleperfExampleCpp'),
374             TestHelper.testdata_path('SimpleperfExampleKotlin')])
375
376        def format_path(path):
377            return os.path.join(TestHelper.testdata_dir, path.replace('/', os.sep))
378        # Find a C++ file with pure file name.
379        self.assertEqual(
380            format_path('SimpleperfExampleCpp/app/src/main/cpp/native-lib.cpp'),
381            searcher.get_real_path('native-lib.cpp'))
382        # Find a C++ file with an absolute file path.
383        self.assertEqual(
384            format_path('SimpleperfExampleCpp/app/src/main/cpp/native-lib.cpp'),
385            searcher.get_real_path('/data/native-lib.cpp'))
386        # Find a Java file.
387        self.assertEqual(
388            format_path(
389                'SimpleperfExampleCpp/app/src/main/java/simpleperf/example/cpp/MainActivity.java'),
390            searcher.get_real_path('cpp/MainActivity.java'))
391        # Find a Kotlin file.
392        self.assertEqual(
393            format_path(
394                'SimpleperfExampleKotlin/app/src/main/java/simpleperf/example/kotlin/' +
395                'MainActivity.kt'),
396            searcher.get_real_path('MainActivity.kt'))
397
398    def test_is_elf_file(self):
399        self.assertTrue(ReadElf.is_elf_file(TestHelper.testdata_path(
400            'simpleperf_runtest_two_functions_arm')))
401        with open('not_elf', 'wb') as fh:
402            fh.write(b'\x90123')
403        try:
404            self.assertFalse(ReadElf.is_elf_file('not_elf'))
405        finally:
406            remove('not_elf')
407
408    def test_binary_finder(self):
409        # Create binary_cache.
410        binary_cache_builder = BinaryCacheBuilder(TestHelper.ndk_path, False)
411        elf_name = 'simpleperf_runtest_two_functions_arm'
412        elf_path = TestHelper.testdata_path(elf_name)
413        readelf = ReadElf(TestHelper.ndk_path)
414        build_id = readelf.get_build_id(elf_path)
415        self.assertGreater(len(build_id), 0)
416        binary_cache_builder.binaries[elf_name] = build_id
417
418        filename_without_build_id = '/data/symfs_without_build_id/elf'
419        binary_cache_builder.binaries[filename_without_build_id] = ''
420
421        binary_cache_builder.copy_binaries_from_symfs_dirs([TestHelper.testdata_dir])
422        binary_cache_builder.create_build_id_list()
423
424        # Test BinaryFinder.
425        path_in_binary_cache = binary_cache_builder.find_path_in_cache(elf_name)
426        binary_finder = BinaryFinder(binary_cache_builder.binary_cache_dir, readelf)
427        # Find binary using build id.
428        path = binary_finder.find_binary('[not_exist_file]', build_id)
429        self.assertEqual(path, path_in_binary_cache)
430        # Find binary using path.
431        path = binary_finder.find_binary(filename_without_build_id, None)
432        self.assertIsNotNone(path)
433        # Find binary using absolute path.
434        path = binary_finder.find_binary(str(path_in_binary_cache), None)
435        self.assertEqual(path, path_in_binary_cache)
436
437        # The binary should has a matched build id.
438        path = binary_finder.find_binary('/' + elf_name, 'wrong_build_id')
439        self.assertIsNone(path)
440