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