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