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': 0x784, 50 'addr': 0x7b0, 51 'source': """system/extras/simpleperf/runtest/two_functions.cpp:14 52 system/extras/simpleperf/runtest/two_functions.cpp:23""", 53 'function': """Function2() 54 main""", 55 }, 56 { 57 'func_addr': 0x784, 58 'addr': 0x7d0, 59 'source': """system/extras/simpleperf/runtest/two_functions.cpp:15 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': 0x840, 68 'addr': 0x840, 69 'source': 'system/extras/simpleperf/runtest/two_functions.cpp:7', 70 'function': 'Function1()', 71 }, 72 { 73 'func_addr': 0x920, 74 'addr': 0x94a, 75 'source': """system/extras/simpleperf/runtest/two_functions.cpp:7 76 system/extras/simpleperf/runtest/two_functions.cpp:22""", 77 'function': """Function1() 78 main""", 79 } 80 ], 81 '/simpleperf_runtest_two_functions_x86': [ 82 { 83 'func_addr': 0x6d0, 84 'addr': 0x6da, 85 'source': 'system/extras/simpleperf/runtest/two_functions.cpp:14', 86 'function': 'Function2()', 87 }, 88 { 89 'func_addr': 0x710, 90 'addr': 0x749, 91 'source': """system/extras/simpleperf/runtest/two_functions.cpp:8 92 system/extras/simpleperf/runtest/two_functions.cpp:22""", 93 'function': """Function1() 94 main""", 95 } 96 ], 97 } 98 binary_finder = BinaryFinder(TestHelper.testdata_dir, ReadElf(TestHelper.ndk_path)) 99 addr2line = Addr2Nearestline(TestHelper.ndk_path, binary_finder, with_function_name) 100 for dso_path in test_map: 101 test_addrs = test_map[dso_path] 102 for test_addr in test_addrs: 103 addr2line.add_addr(dso_path, None, test_addr['func_addr'], test_addr['addr']) 104 addr2line.convert_addrs_to_lines() 105 for dso_path in test_map: 106 dso = addr2line.get_dso(dso_path) 107 self.assertIsNotNone(dso, dso_path) 108 test_addrs = test_map[dso_path] 109 for test_addr in test_addrs: 110 expected_files = [] 111 expected_lines = [] 112 expected_functions = [] 113 for line in test_addr['source'].split('\n'): 114 items = line.split(':') 115 expected_files.append(items[0].strip()) 116 expected_lines.append(int(items[1])) 117 for line in test_addr['function'].split('\n'): 118 expected_functions.append(line.strip()) 119 self.assertEqual(len(expected_files), len(expected_functions)) 120 121 if with_function_name: 122 expected_source = list(zip(expected_files, expected_lines, expected_functions)) 123 else: 124 expected_source = list(zip(expected_files, expected_lines)) 125 126 actual_source = addr2line.get_addr_source(dso, test_addr['addr']) 127 if is_windows(): 128 self.assertIsNotNone(actual_source, 'for %s:0x%x' % 129 (dso_path, test_addr['addr'])) 130 for i, source in enumerate(actual_source): 131 new_source = list(source) 132 new_source[0] = new_source[0].replace('\\', '/') 133 actual_source[i] = tuple(new_source) 134 135 self.assertEqual(actual_source, expected_source, 136 'for %s:0x%x, expected source %s, actual source %s' % 137 (dso_path, test_addr['addr'], expected_source, actual_source)) 138 139 def test_objdump(self): 140 test_map = { 141 '/simpleperf_runtest_two_functions_arm64': { 142 'start_addr': 0x112c, 143 'len': 28, 144 'expected_items': [ 145 ('main():', 0), 146 ('system/extras/simpleperf/runtest/two_functions.cpp:20', 0), 147 ('1134: add x29, sp, #16', 0x1134), 148 ], 149 }, 150 '/simpleperf_runtest_two_functions_arm': { 151 'start_addr': 0x784, 152 'len': 80, 153 'expected_items': [ 154 ('main():', 0), 155 ('system/extras/simpleperf/runtest/two_functions.cpp:20', 0), 156 ('7ae: bne.n 7a6 <main+0x22>', 0x7ae), 157 ], 158 }, 159 '/simpleperf_runtest_two_functions_x86_64': { 160 'start_addr': 0x920, 161 'len': 201, 162 'expected_items': [ 163 ('main():', 0), 164 ('system/extras/simpleperf/runtest/two_functions.cpp:20', 0), 165 ('96e: movl %edx, (%rbx,%rax,4)', 0x96e), 166 ], 167 }, 168 '/simpleperf_runtest_two_functions_x86': { 169 'start_addr': 0x710, 170 'len': 98, 171 'expected_items': [ 172 ('main():', 0), 173 ('system/extras/simpleperf/runtest/two_functions.cpp:20', 0), 174 ('748: cmpl $100000000, %ebp', 0x748), 175 ], 176 }, 177 } 178 binary_finder = BinaryFinder(TestHelper.testdata_dir, ReadElf(TestHelper.ndk_path)) 179 objdump = Objdump(TestHelper.ndk_path, binary_finder) 180 for dso_path in test_map: 181 dso = test_map[dso_path] 182 dso_info = objdump.get_dso_info(dso_path, None) 183 self.assertIsNotNone(dso_info, dso_path) 184 disassemble_code = objdump.disassemble_code(dso_info, dso['start_addr'], dso['len']) 185 self.assertTrue(disassemble_code, dso_path) 186 i = 0 187 for expected_line, expected_addr in dso['expected_items']: 188 found = False 189 while i < len(disassemble_code): 190 line, addr = disassemble_code[i] 191 if addr == expected_addr and expected_line in line: 192 found = True 193 i += 1 194 break 195 i += 1 196 if not found: 197 s = '\n'.join('%s:0x%x' % item for item in disassemble_code) 198 self.fail('for %s, %s:0x%x not found in disassemble code:\n%s' % 199 (dso_path, expected_line, expected_addr, s)) 200 201 def test_readelf(self): 202 test_map = { 203 'simpleperf_runtest_two_functions_arm64': { 204 'arch': 'arm64', 205 'build_id': '0xb4f1b49b0fe9e34e78fb14e5374c930c00000000', 206 'sections': ['.note.gnu.build-id', '.dynsym', '.text', '.rodata', '.eh_frame', 207 '.eh_frame_hdr', '.debug_info', '.debug_line', '.symtab'], 208 }, 209 'simpleperf_runtest_two_functions_arm': { 210 'arch': 'arm', 211 'build_id': '0x718f5b36c4148ee1bd3f51af89ed2be600000000', 212 }, 213 'simpleperf_runtest_two_functions_x86_64': { 214 'arch': 'x86_64', 215 }, 216 'simpleperf_runtest_two_functions_x86': { 217 'arch': 'x86', 218 } 219 } 220 readelf = ReadElf(TestHelper.ndk_path) 221 for dso_path in test_map: 222 dso_info = test_map[dso_path] 223 path = os.path.join(TestHelper.testdata_dir, dso_path) 224 self.assertEqual(dso_info['arch'], readelf.get_arch(path)) 225 if 'build_id' in dso_info: 226 self.assertEqual(dso_info['build_id'], readelf.get_build_id(path), dso_path) 227 if 'sections' in dso_info: 228 sections = readelf.get_sections(path) 229 for section in dso_info['sections']: 230 self.assertIn(section, sections) 231 self.assertEqual(readelf.get_arch('not_exist_file'), 'unknown') 232 self.assertEqual(readelf.get_build_id('not_exist_file'), '') 233 self.assertEqual(readelf.get_sections('not_exist_file'), []) 234 235 def test_source_file_searcher(self): 236 searcher = SourceFileSearcher( 237 [TestHelper.testdata_path('SimpleperfExampleWithNative'), 238 TestHelper.testdata_path('SimpleperfExampleOfKotlin')]) 239 240 def format_path(path): 241 return os.path.join(TestHelper.testdata_dir, path.replace('/', os.sep)) 242 # Find a C++ file with pure file name. 243 self.assertEqual( 244 format_path('SimpleperfExampleWithNative/app/src/main/cpp/native-lib.cpp'), 245 searcher.get_real_path('native-lib.cpp')) 246 # Find a C++ file with an absolute file path. 247 self.assertEqual( 248 format_path('SimpleperfExampleWithNative/app/src/main/cpp/native-lib.cpp'), 249 searcher.get_real_path('/data/native-lib.cpp')) 250 # Find a Java file. 251 self.assertEqual( 252 format_path('SimpleperfExampleWithNative/app/src/main/java/com/example/' + 253 'simpleperf/simpleperfexamplewithnative/MainActivity.java'), 254 searcher.get_real_path('simpleperfexamplewithnative/MainActivity.java')) 255 # Find a Kotlin file. 256 self.assertEqual( 257 format_path('SimpleperfExampleOfKotlin/app/src/main/java/com/example/' + 258 'simpleperf/simpleperfexampleofkotlin/MainActivity.kt'), 259 searcher.get_real_path('MainActivity.kt')) 260 261 def test_is_elf_file(self): 262 self.assertTrue(ReadElf.is_elf_file(TestHelper.testdata_path( 263 'simpleperf_runtest_two_functions_arm'))) 264 with open('not_elf', 'wb') as fh: 265 fh.write(b'\x90123') 266 try: 267 self.assertFalse(ReadElf.is_elf_file('not_elf')) 268 finally: 269 remove('not_elf') 270 271 def test_binary_finder(self): 272 # Create binary_cache. 273 binary_cache_builder = BinaryCacheBuilder(TestHelper.ndk_path, False) 274 elf_name = 'simpleperf_runtest_two_functions_arm' 275 elf_path = TestHelper.testdata_path(elf_name) 276 readelf = ReadElf(TestHelper.ndk_path) 277 build_id = readelf.get_build_id(elf_path) 278 self.assertGreater(len(build_id), 0) 279 binary_cache_builder.binaries[elf_name] = build_id 280 binary_cache_builder.copy_binaries_from_symfs_dirs([TestHelper.testdata_dir]) 281 binary_cache_builder.create_build_id_list() 282 283 # Test BinaryFinder. 284 path_in_binary_cache = Path(binary_cache_builder.binary_cache_dir, elf_name) 285 binary_finder = BinaryFinder(binary_cache_builder.binary_cache_dir, readelf) 286 # Find binary using build id. 287 path = binary_finder.find_binary('[not_exist_file]', build_id) 288 self.assertEqual(path, path_in_binary_cache) 289 # Find binary using path. 290 path = binary_finder.find_binary('/' + elf_name, None) 291 self.assertEqual(path, path_in_binary_cache) 292 # Find binary using absolute path. 293 path = binary_finder.find_binary(str(path_in_binary_cache), None) 294 self.assertEqual(path, path_in_binary_cache) 295 296 # The binary should has a matched build id. 297 path = binary_finder.find_binary('/' + elf_name, 'wrong_build_id') 298 self.assertIsNone(path) 299