1#!/usr/bin/env python3 2# 3# Copyright (C) 2013 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 17"""Module for looking up symbolic debugging information. 18 19The information can include symbol names, offsets, and source locations. 20""" 21 22import atexit 23import glob 24import os 25import platform 26import re 27import shutil 28import signal 29import subprocess 30import unittest 31 32ANDROID_BUILD_TOP = os.environ.get("ANDROID_BUILD_TOP", ".") 33 34 35def FindClangDir(): 36 get_clang_version = ANDROID_BUILD_TOP + "/build/soong/scripts/get_clang_version.py" 37 if os.path.exists(get_clang_version): 38 # We want the script to fail if get_clang_version.py exists but is unable 39 # to find the clang version. 40 version_output = subprocess.check_output(get_clang_version, text=True) 41 return ANDROID_BUILD_TOP + "/prebuilts/clang/host/linux-x86/" + version_output.strip() 42 else: 43 return None 44 45 46def FindSymbolsDir(): 47 saveddir = os.getcwd() 48 os.chdir(ANDROID_BUILD_TOP) 49 stream = None 50 try: 51 cmd = "build/soong/soong_ui.bash --dumpvar-mode --abs TARGET_OUT_UNSTRIPPED" 52 stream = subprocess.Popen(cmd, stdout=subprocess.PIPE, universal_newlines=True, shell=True).stdout 53 return str(stream.read().strip()) 54 finally: 55 if stream is not None: 56 stream.close() 57 os.chdir(saveddir) 58 59SYMBOLS_DIR = FindSymbolsDir() 60 61ARCH = None 62 63VERBOSE = False 64 65# These are private. Do not access them from other modules. 66_CACHED_TOOLCHAIN = None 67_CACHED_TOOLCHAIN_ARCH = None 68_CACHED_CXX_FILT = None 69 70# Caches for symbolized information. 71_SYMBOL_INFORMATION_ADDR2LINE_CACHE = {} 72_SYMBOL_INFORMATION_OBJDUMP_CACHE = {} 73_SYMBOL_DEMANGLING_CACHE = {} 74 75# Caches for pipes to subprocesses. 76 77class ProcessCache: 78 _cmd2pipe = {} 79 _lru = [] 80 81 # Max number of open pipes. 82 _PIPE_MAX_OPEN = 10 83 84 def GetProcess(self, cmd): 85 cmd_tuple = tuple(cmd) # Need to use a tuple as lists can't be dict keys. 86 # Pipe already available? 87 if cmd_tuple in self._cmd2pipe: 88 pipe = self._cmd2pipe[cmd_tuple] 89 # Update LRU. 90 self._lru = [(cmd_tuple, pipe)] + [i for i in self._lru if i[0] != cmd_tuple] 91 return pipe 92 93 # Not cached, yet. Open a new one. 94 95 # Check if too many are open, close the old ones. 96 while len(self._lru) >= self._PIPE_MAX_OPEN: 97 open_cmd, open_pipe = self._lru.pop() 98 del self._cmd2pipe[open_cmd] 99 self.TerminateProcess(open_pipe) 100 101 # Create and put into cache. 102 pipe = self.SpawnProcess(cmd) 103 self._cmd2pipe[cmd_tuple] = pipe 104 self._lru = [(cmd_tuple, pipe)] + self._lru 105 return pipe 106 107 def SpawnProcess(self, cmd): 108 return subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, universal_newlines=True) 109 110 def TerminateProcess(self, pipe): 111 pipe.stdin.close() 112 pipe.stdout.close() 113 pipe.terminate() 114 pipe.wait() 115 116 def KillAllProcesses(self): 117 for _, open_pipe in self._lru: 118 self.TerminateProcess(open_pipe) 119 _cmd2pipe = {} 120 _lru = [] 121 122 123_PIPE_ADDR2LINE_CACHE = ProcessCache() 124_PIPE_CPPFILT_CACHE = ProcessCache() 125 126 127# Process cache cleanup on shutdown. 128 129def CloseAllPipes(): 130 _PIPE_ADDR2LINE_CACHE.KillAllProcesses() 131 _PIPE_CPPFILT_CACHE.KillAllProcesses() 132 133 134atexit.register(CloseAllPipes) 135 136 137def PipeTermHandler(signum, frame): 138 CloseAllPipes() 139 os._exit(0) 140 141 142for sig in (signal.SIGABRT, signal.SIGINT, signal.SIGTERM): 143 signal.signal(sig, PipeTermHandler) 144 145 146 147 148def ToolPath(tool, toolchain=None): 149 """Return a fully-qualified path to the specified tool, or just the tool if it's on PATH """ 150 if shutil.which(tool) is not None: 151 return tool 152 if not toolchain: 153 toolchain = FindToolchain() 154 return os.path.join(toolchain, tool) 155 156 157def FindToolchain(): 158 """Returns the toolchain matching ARCH.""" 159 160 global _CACHED_TOOLCHAIN, _CACHED_TOOLCHAIN_ARCH 161 if _CACHED_TOOLCHAIN is not None and _CACHED_TOOLCHAIN_ARCH == ARCH: 162 return _CACHED_TOOLCHAIN 163 164 llvm_binutils_dir = ANDROID_BUILD_TOP + "/prebuilts/clang/host/linux-x86/llvm-binutils-stable/"; 165 if not os.path.exists(llvm_binutils_dir): 166 raise Exception("Could not find llvm tool chain directory %s" % (llvm_binutils_dir)) 167 168 _CACHED_TOOLCHAIN = llvm_binutils_dir 169 _CACHED_TOOLCHAIN_ARCH = ARCH 170 print("Using", _CACHED_TOOLCHAIN_ARCH, "toolchain from:", _CACHED_TOOLCHAIN) 171 return _CACHED_TOOLCHAIN 172 173 174def SymbolInformation(lib, addr): 175 """Look up symbol information about an address. 176 177 Args: 178 lib: library (or executable) pathname containing symbols 179 addr: string hexidecimal address 180 181 Returns: 182 A list of the form [(source_symbol, source_location, 183 object_symbol_with_offset)]. 184 185 If the function has been inlined then the list may contain 186 more than one element with the symbols for the most deeply 187 nested inlined location appearing first. The list is 188 always non-empty, even if no information is available. 189 190 Usually you want to display the source_location and 191 object_symbol_with_offset from the last element in the list. 192 """ 193 info = SymbolInformationForSet(lib, set([addr])) 194 return (info and info.get(addr)) or [(None, None, None)] 195 196 197def SymbolInformationForSet(lib, unique_addrs): 198 """Look up symbol information for a set of addresses from the given library. 199 200 Args: 201 lib: library (or executable) pathname containing symbols 202 unique_addrs: set of hexidecimal addresses 203 204 Returns: 205 A dictionary of the form {addr: [(source_symbol, source_location, 206 object_symbol_with_offset)]} where each address has a list of 207 associated symbols and locations. The list is always non-empty. 208 209 If the function has been inlined then the list may contain 210 more than one element with the symbols for the most deeply 211 nested inlined location appearing first. The list is 212 always non-empty, even if no information is available. 213 214 Usually you want to display the source_location and 215 object_symbol_with_offset from the last element in the list. 216 """ 217 if not lib: 218 return None 219 220 addr_to_line = CallLlvmSymbolizerForSet(lib, unique_addrs) 221 if not addr_to_line: 222 return None 223 224 addr_to_objdump = CallObjdumpForSet(lib, unique_addrs) 225 if not addr_to_objdump: 226 return None 227 228 result = {} 229 for addr in unique_addrs: 230 source_info = addr_to_line.get(addr) 231 if not source_info: 232 source_info = [(None, None)] 233 if addr in addr_to_objdump: 234 (object_symbol, object_offset) = addr_to_objdump.get(addr) 235 object_symbol_with_offset = FormatSymbolWithOffset(object_symbol, 236 object_offset) 237 else: 238 object_symbol_with_offset = None 239 result[addr] = [(source_symbol, source_location, object_symbol_with_offset) 240 for (source_symbol, source_location) in source_info] 241 242 return result 243 244 245def CallLlvmSymbolizerForSet(lib, unique_addrs): 246 """Look up line and symbol information for a set of addresses. 247 248 Args: 249 lib: library (or executable) pathname containing symbols 250 unique_addrs: set of string hexidecimal addresses look up. 251 252 Returns: 253 A dictionary of the form {addr: [(symbol, file:line)]} where 254 each address has a list of associated symbols and locations 255 or an empty list if no symbol information was found. 256 257 If the function has been inlined then the list may contain 258 more than one element with the symbols for the most deeply 259 nested inlined location appearing first. 260 """ 261 if not lib: 262 return None 263 264 result = {} 265 addrs = sorted(unique_addrs) 266 267 if lib in _SYMBOL_INFORMATION_ADDR2LINE_CACHE: 268 addr_cache = _SYMBOL_INFORMATION_ADDR2LINE_CACHE[lib] 269 270 # Go through and handle all known addresses. 271 for x in range(len(addrs)): 272 next_addr = addrs.pop(0) 273 if next_addr in addr_cache: 274 result[next_addr] = addr_cache[next_addr] 275 else: 276 # Re-add, needs to be symbolized. 277 addrs.append(next_addr) 278 279 if not addrs: 280 # Everything was cached, we're done. 281 return result 282 else: 283 addr_cache = {} 284 _SYMBOL_INFORMATION_ADDR2LINE_CACHE[lib] = addr_cache 285 286 symbols = SYMBOLS_DIR + lib 287 if not os.path.exists(symbols): 288 symbols = lib 289 if not os.path.exists(symbols): 290 return None 291 292 # Make sure the symbols path is not a directory. 293 if os.path.isdir(symbols): 294 return None 295 296 cmd = [ToolPath("llvm-symbolizer"), "--functions", "--inlines", 297 "--demangle", "--obj=" + symbols, "--output-style=GNU"] 298 child = _PIPE_ADDR2LINE_CACHE.GetProcess(cmd) 299 300 for addr in addrs: 301 try: 302 child.stdin.write("0x%s\n" % addr) 303 child.stdin.flush() 304 records = [] 305 first = True 306 while True: 307 symbol = child.stdout.readline().strip() 308 if not symbol: 309 break 310 location = child.stdout.readline().strip() 311 records.append((symbol, location)) 312 if first: 313 # Write a blank line as a sentinel so we know when to stop 314 # reading inlines from the output. 315 # The blank line will cause llvm-symbolizer to emit a blank line. 316 child.stdin.write("\n") 317 child.stdin.flush() 318 first = False 319 except IOError as e: 320 # Remove the / in front of the library name to match other output. 321 records = [(None, lib[1:] + " ***Error: " + str(e))] 322 result[addr] = records 323 addr_cache[addr] = records 324 return result 325 326 327def StripPC(addr): 328 """Strips the Thumb bit a program counter address when appropriate. 329 330 Args: 331 addr: the program counter address 332 333 Returns: 334 The stripped program counter address. 335 """ 336 global ARCH 337 if ARCH == "arm": 338 return addr & ~1 339 return addr 340 341 342def CallObjdumpForSet(lib, unique_addrs): 343 """Use objdump to find out the names of the containing functions. 344 345 Args: 346 lib: library (or executable) pathname containing symbols 347 unique_addrs: set of string hexidecimal addresses to find the functions for. 348 349 Returns: 350 A dictionary of the form {addr: (string symbol, offset)}. 351 """ 352 if not lib: 353 return None 354 355 result = {} 356 addrs = sorted(unique_addrs) 357 358 addr_cache = None 359 if lib in _SYMBOL_INFORMATION_OBJDUMP_CACHE: 360 addr_cache = _SYMBOL_INFORMATION_OBJDUMP_CACHE[lib] 361 362 # Go through and handle all known addresses. 363 for x in range(len(addrs)): 364 next_addr = addrs.pop(0) 365 if next_addr in addr_cache: 366 result[next_addr] = addr_cache[next_addr] 367 else: 368 # Re-add, needs to be symbolized. 369 addrs.append(next_addr) 370 371 if not addrs: 372 # Everything was cached, we're done. 373 return result 374 else: 375 addr_cache = {} 376 _SYMBOL_INFORMATION_OBJDUMP_CACHE[lib] = addr_cache 377 378 symbols = SYMBOLS_DIR + lib 379 if not os.path.exists(symbols): 380 symbols = lib 381 if not os.path.exists(symbols): 382 return None 383 384 start_addr_dec = str(StripPC(int(addrs[0], 16))) 385 stop_addr_dec = str(StripPC(int(addrs[-1], 16)) + 8) 386 cmd = [ToolPath("llvm-objdump"), 387 "--section=.text", 388 "--demangle", 389 "--disassemble", 390 "--start-address=" + start_addr_dec, 391 "--stop-address=" + stop_addr_dec, 392 symbols] 393 394 # Function lines look like: 395 # 000177b0 <android::IBinder::~IBinder()+0x2c>: 396 # We pull out the address and function first. Then we check for an optional 397 # offset. This is tricky due to functions that look like "operator+(..)+0x2c" 398 func_regexp = re.compile("(^[a-f0-9]*) \<(.*)\>:$") 399 offset_regexp = re.compile("(.*)\+0x([a-f0-9]*)") 400 401 # A disassembly line looks like: 402 # 177b2: b510 push {r4, lr} 403 asm_regexp = re.compile("(^[ a-f0-9]*):[ a-f0-0]*.*$") 404 405 current_symbol = None # The current function symbol in the disassembly. 406 current_symbol_addr = 0 # The address of the current function. 407 addr_index = 0 # The address that we are currently looking for. 408 409 stream = subprocess.Popen(cmd, stdout=subprocess.PIPE, universal_newlines=True).stdout 410 for line in stream: 411 # Is it a function line like: 412 # 000177b0 <android::IBinder::~IBinder()>: 413 components = func_regexp.match(line) 414 if components: 415 # This is a new function, so record the current function and its address. 416 current_symbol_addr = int(components.group(1), 16) 417 current_symbol = components.group(2) 418 419 # Does it have an optional offset like: "foo(..)+0x2c"? 420 components = offset_regexp.match(current_symbol) 421 if components: 422 current_symbol = components.group(1) 423 offset = components.group(2) 424 if offset: 425 current_symbol_addr -= int(offset, 16) 426 427 # Is it an disassembly line like: 428 # 177b2: b510 push {r4, lr} 429 components = asm_regexp.match(line) 430 if components: 431 addr = components.group(1) 432 target_addr = addrs[addr_index] 433 i_addr = int(addr, 16) 434 i_target = StripPC(int(target_addr, 16)) 435 if i_addr == i_target: 436 result[target_addr] = (current_symbol, i_target - current_symbol_addr) 437 addr_cache[target_addr] = result[target_addr] 438 addr_index += 1 439 if addr_index >= len(addrs): 440 break 441 stream.close() 442 443 return result 444 445 446def CallCppFilt(mangled_symbol): 447 if mangled_symbol in _SYMBOL_DEMANGLING_CACHE: 448 return _SYMBOL_DEMANGLING_CACHE[mangled_symbol] 449 450 global _CACHED_CXX_FILT 451 if not _CACHED_CXX_FILT: 452 toolchains = None 453 clang_dir = FindClangDir() 454 if clang_dir: 455 if os.path.exists(clang_dir + "/bin/llvm-cxxfilt"): 456 toolchains = [clang_dir + "/bin/llvm-cxxfilt"] 457 else: 458 raise Exception("bin/llvm-cxxfilt missing from " + clang_dir) 459 else: 460 # When run in CI, we don't have a way to find the clang version. But 461 # llvm-cxxfilt should be available in the following relative path. 462 toolchains = glob.glob("./clang-r*/bin/llvm-cxxfilt") 463 if toolchains and len(toolchains) != 1: 464 raise Exception("Expected one llvm-cxxfilt but found many: " + \ 465 ", ".join(toolchains)) 466 if not toolchains: 467 raise Exception("Could not find llvm-cxxfilt tool") 468 _CACHED_CXX_FILT = sorted(toolchains)[-1] 469 470 cmd = [_CACHED_CXX_FILT] 471 process = _PIPE_CPPFILT_CACHE.GetProcess(cmd) 472 process.stdin.write(mangled_symbol) 473 process.stdin.write("\n") 474 process.stdin.flush() 475 476 demangled_symbol = process.stdout.readline().strip() 477 478 _SYMBOL_DEMANGLING_CACHE[mangled_symbol] = demangled_symbol 479 480 return demangled_symbol 481 482 483def FormatSymbolWithOffset(symbol, offset): 484 if offset == 0: 485 return symbol 486 return "%s+%d" % (symbol, offset) 487 488def FormatSymbolWithoutParameters(symbol): 489 """Remove parameters from function. 490 491 Rather than trying to parse the demangled C++ signature, 492 it just removes matching top level parenthesis. 493 """ 494 if not symbol: 495 return symbol 496 497 result = symbol 498 result = result.replace(") const", ")") # Strip const keyword. 499 result = result.replace("operator<<", "operator\u00AB") # Avoid unmatched '<'. 500 result = result.replace("operator>>", "operator\u00BB") # Avoid unmatched '>'. 501 result = result.replace("operator->", "operator\u2192") # Avoid unmatched '>'. 502 503 nested = [] # Keeps tract of current nesting level of parenthesis. 504 for i in reversed(range(len(result))): # Iterate backward to make cutting easier. 505 c = result[i] 506 if c == ')' or c == '>': 507 if len(nested) == 0: 508 end = i + 1 # Mark the end of top-level pair. 509 nested.append(c) 510 if c == '(' or c == '<': 511 if len(nested) == 0 or {')':'(', '>':'<'}[nested.pop()] != c: 512 return symbol # Malformed: character does not match its pair. 513 if len(nested) == 0 and c == '(' and (end - i) > 2: 514 result = result[:i] + result[end:] # Remove substring (i, end). 515 if len(nested) > 0: 516 return symbol # Malformed: missing pair. 517 518 return result.strip() 519 520def GetAbiFromToolchain(toolchain_var, bits): 521 toolchain = os.environ.get(toolchain_var) 522 if not toolchain: 523 return None 524 525 toolchain_match = re.search("\/(aarch64|arm|mips|x86)\/", toolchain) 526 if toolchain_match: 527 abi = toolchain_match.group(1) 528 if abi == "aarch64": 529 return "arm64" 530 elif bits == 64: 531 if abi == "x86": 532 return "x86_64" 533 elif abi == "mips": 534 return "mips64" 535 return abi 536 return None 537 538def Get32BitArch(): 539 # Check for ANDROID_TOOLCHAIN_2ND_ARCH first, if set, use that. 540 # If not try ANDROID_TOOLCHAIN to find the arch. 541 # If this is not set, then default to arm. 542 arch = GetAbiFromToolchain("ANDROID_TOOLCHAIN_2ND_ARCH", 32) 543 if not arch: 544 arch = GetAbiFromToolchain("ANDROID_TOOLCHAIN", 32) 545 if not arch: 546 return "arm" 547 return arch 548 549def Get64BitArch(): 550 # Check for ANDROID_TOOLCHAIN, if it is set, we can figure out the 551 # arch this way. If this is not set, then default to arm64. 552 arch = GetAbiFromToolchain("ANDROID_TOOLCHAIN", 64) 553 if not arch: 554 return "arm64" 555 return arch 556 557def SetAbi(lines): 558 global ARCH 559 560 abi_line = re.compile("ABI: \'(.*)\'") 561 trace_line = re.compile("\#[0-9]+[ \t]+..[ \t]+([0-9a-f]{8}|[0-9a-f]{16})([ \t]+|$)") 562 asan_trace_line = re.compile("\#[0-9]+[ \t]+0x([0-9a-f]+)[ \t]+") 563 564 ARCH = None 565 for line in lines: 566 abi_match = abi_line.search(line) 567 if abi_match: 568 ARCH = abi_match.group(1) 569 break 570 trace_match = trace_line.search(line) 571 if trace_match: 572 # Try to guess the arch, we know the bitness. 573 if len(trace_match.group(1)) == 16: 574 ARCH = Get64BitArch() 575 else: 576 ARCH = Get32BitArch() 577 break 578 asan_trace_match = asan_trace_line.search(line) 579 if asan_trace_match: 580 # We might be able to guess the bitness by the length of the address. 581 if len(asan_trace_match.group(1)) > 8: 582 ARCH = Get64BitArch() 583 # We know for a fact this is 64 bit, so we are done. 584 break 585 else: 586 ARCH = Get32BitArch() 587 # This might be 32 bit, or just a small address. Keep going in this 588 # case, but if we couldn't figure anything else out, go with 32 bit. 589 if not ARCH: 590 raise Exception("Could not determine arch from input, use --arch=XXX to specify it") 591 592 593class FindToolchainTests(unittest.TestCase): 594 def assert_toolchain_found(self, abi): 595 global ARCH 596 ARCH = abi 597 FindToolchain() # Will throw on failure. 598 599 @unittest.skipIf(ANDROID_BUILD_TOP == '.', 'Test only supported in an Android tree.') 600 def test_toolchains_found(self): 601 self.assert_toolchain_found("arm") 602 self.assert_toolchain_found("arm64") 603 self.assert_toolchain_found("mips") 604 self.assert_toolchain_found("x86") 605 self.assert_toolchain_found("x86_64") 606 607class FindClangDirTests(unittest.TestCase): 608 @unittest.skipIf(ANDROID_BUILD_TOP == '.', 'Test only supported in an Android tree.') 609 def test_clang_dir_found(self): 610 self.assertIsNotNone(FindClangDir()) 611 612class SetArchTests(unittest.TestCase): 613 def test_abi_check(self): 614 global ARCH 615 616 SetAbi(["ABI: 'arm'"]) 617 self.assertEqual(ARCH, "arm") 618 SetAbi(["ABI: 'arm64'"]) 619 self.assertEqual(ARCH, "arm64") 620 621 SetAbi(["ABI: 'mips'"]) 622 self.assertEqual(ARCH, "mips") 623 SetAbi(["ABI: 'mips64'"]) 624 self.assertEqual(ARCH, "mips64") 625 626 SetAbi(["ABI: 'x86'"]) 627 self.assertEqual(ARCH, "x86") 628 SetAbi(["ABI: 'x86_64'"]) 629 self.assertEqual(ARCH, "x86_64") 630 631 def test_32bit_trace_line_toolchain(self): 632 global ARCH 633 634 os.environ.clear() 635 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/arm/arm-linux-androideabi-4.9/bin" 636 SetAbi(["#00 pc 000374e0"]) 637 self.assertEqual(ARCH, "arm") 638 639 os.environ.clear() 640 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/mips/arm-linux-androideabi-4.9/bin" 641 SetAbi(["#00 pc 000374e0"]) 642 self.assertEqual(ARCH, "mips") 643 644 os.environ.clear() 645 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/x86/arm-linux-androideabi-4.9/bin" 646 SetAbi(["#00 pc 000374e0"]) 647 self.assertEqual(ARCH, "x86") 648 649 def test_32bit_trace_line_toolchain_2nd(self): 650 global ARCH 651 652 os.environ.clear() 653 os.environ["ANDROID_TOOLCHAIN_2ND_ARCH"] = "linux-x86/arm/arm-linux-androideabi-4.9/bin" 654 os.environ["ANDROID_TOOLCHAIN_ARCH"] = "linux-x86/aarch64/aarch64-linux-android-4.9/bin" 655 SetAbi(["#00 pc 000374e0"]) 656 self.assertEqual(ARCH, "arm") 657 658 os.environ.clear() 659 os.environ["ANDROID_TOOLCHAIN_2ND_ARCH"] = "linux-x86/mips/mips-linux-androideabi-4.9/bin" 660 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/unknown/unknown-linux-androideabi-4.9/bin" 661 SetAbi(["#00 pc 000374e0"]) 662 self.assertEqual(ARCH, "mips") 663 664 os.environ.clear() 665 os.environ["ANDROID_TOOLCHAIN_2ND_ARCH"] = "linux-x86/x86/x86-linux-androideabi-4.9/bin" 666 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/unknown/unknown-linux-androideabi-4.9/bin" 667 SetAbi(["#00 pc 000374e0"]) 668 self.assertEqual(ARCH, "x86") 669 670 def test_64bit_trace_line_toolchain(self): 671 global ARCH 672 673 os.environ.clear() 674 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/aarch/aarch-linux-androideabi-4.9/bin" 675 SetAbi(["#00 pc 00000000000374e0"]) 676 self.assertEqual(ARCH, "arm64") 677 678 os.environ.clear() 679 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/mips/arm-linux-androideabi-4.9/bin" 680 SetAbi(["#00 pc 00000000000374e0"]) 681 self.assertEqual(ARCH, "mips64") 682 683 os.environ.clear() 684 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/x86/arm-linux-androideabi-4.9/bin" 685 SetAbi(["#00 pc 00000000000374e0"]) 686 self.assertEqual(ARCH, "x86_64") 687 688 def test_trace_default_abis(self): 689 global ARCH 690 691 os.environ.clear() 692 SetAbi(["#00 pc 000374e0"]) 693 self.assertEqual(ARCH, "arm") 694 SetAbi(["#00 pc 00000000000374e0"]) 695 self.assertEqual(ARCH, "arm64") 696 697 def test_32bit_asan_trace_line_toolchain(self): 698 global ARCH 699 700 os.environ.clear() 701 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/arm/arm-linux-androideabi-4.9/bin" 702 SetAbi(["#10 0xb5eeba5d (/system/vendor/lib/egl/libGLESv1_CM_adreno.so+0xfa5d)"]) 703 self.assertEqual(ARCH, "arm") 704 705 os.environ.clear() 706 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/mips/arm-linux-androideabi-4.9/bin" 707 SetAbi(["#10 0xb5eeba5d (/system/vendor/lib/egl/libGLESv1_CM_adreno.so+0xfa5d)"]) 708 self.assertEqual(ARCH, "mips") 709 710 os.environ.clear() 711 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/x86/arm-linux-androideabi-4.9/bin" 712 SetAbi(["#10 0xb5eeba5d (/system/vendor/lib/egl/libGLESv1_CM_adreno.so+0xfa5d)"]) 713 self.assertEqual(ARCH, "x86") 714 715 def test_32bit_asan_trace_line_toolchain_2nd(self): 716 global ARCH 717 718 os.environ.clear() 719 os.environ["ANDROID_TOOLCHAIN_2ND_ARCH"] = "linux-x86/arm/arm-linux-androideabi-4.9/bin" 720 os.environ["ANDROID_TOOLCHAIN_ARCH"] = "linux-x86/aarch64/aarch64-linux-android-4.9/bin" 721 SetAbi(["#3 0xae1725b5 (/system/vendor/lib/libllvm-glnext.so+0x6435b5)"]) 722 self.assertEqual(ARCH, "arm") 723 724 os.environ.clear() 725 os.environ["ANDROID_TOOLCHAIN_2ND_ARCH"] = "linux-x86/mips/mips-linux-androideabi-4.9/bin" 726 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/unknown/unknown-linux-androideabi-4.9/bin" 727 SetAbi(["#3 0xae1725b5 (/system/vendor/lib/libllvm-glnext.so+0x6435b5)"]) 728 self.assertEqual(ARCH, "mips") 729 730 os.environ.clear() 731 os.environ["ANDROID_TOOLCHAIN_2ND_ARCH"] = "linux-x86/x86/x86-linux-androideabi-4.9/bin" 732 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/unknown/unknown-linux-androideabi-4.9/bin" 733 SetAbi(["#3 0xae1725b5 (/system/vendor/lib/libllvm-glnext.so+0x6435b5)"]) 734 self.assertEqual(ARCH, "x86") 735 736 def test_64bit_asan_trace_line_toolchain(self): 737 global ARCH 738 739 os.environ.clear() 740 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/aarch/aarch-linux-androideabi-4.9/bin" 741 SetAbi(["#0 0x11b35d33bf (/system/lib/libclang_rt.asan-arm-android.so+0x823bf)"]) 742 self.assertEqual(ARCH, "arm64") 743 744 os.environ.clear() 745 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/mips/arm-linux-androideabi-4.9/bin" 746 SetAbi(["#1 0x11b35d33bf (/system/lib/libclang_rt.asan-arm-android.so+0x823bf)"]) 747 self.assertEqual(ARCH, "mips64") 748 749 os.environ.clear() 750 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/x86/arm-linux-androideabi-4.9/bin" 751 SetAbi(["#12 0x11b35d33bf (/system/lib/libclang_rt.asan-arm-android.so+0x823bf)"]) 752 self.assertEqual(ARCH, "x86_64") 753 754 # Verify that if an address that might be 32 bit comes first, that 755 # encountering a 64 bit address returns a 64 bit abi. 756 ARCH = None 757 os.environ.clear() 758 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/x86/arm-linux-androideabi-4.9/bin" 759 SetAbi(["#12 0x5d33bf (/system/lib/libclang_rt.asan-arm-android.so+0x823bf)", 760 "#12 0x11b35d33bf (/system/lib/libclang_rt.asan-arm-android.so+0x823bf)"]) 761 self.assertEqual(ARCH, "x86_64") 762 763 def test_asan_trace_default_abis(self): 764 global ARCH 765 766 os.environ.clear() 767 SetAbi(["#4 0x1234349ab (/system/vendor/lib/libllvm-glnext.so+0x64fc4f)"]) 768 self.assertEqual(ARCH, "arm64") 769 SetAbi(["#1 0xae17ec4f (/system/vendor/lib/libllvm-glnext.so+0x64fc4f)"]) 770 self.assertEqual(ARCH, "arm") 771 772 def test_no_abi(self): 773 global ARCH 774 775 # Python2 vs Python3 compatibility: Python3 warns on Regexp deprecation, but Regex 776 # does not provide that name. 777 if not hasattr(unittest.TestCase, 'assertRaisesRegex'): 778 unittest.TestCase.assertRaisesRegex = getattr(unittest.TestCase, 'assertRaisesRegexp') 779 self.assertRaisesRegex(Exception, 780 "Could not determine arch from input, use --arch=XXX to specify it", 781 SetAbi, []) 782 783class FormatSymbolWithoutParametersTests(unittest.TestCase): 784 def test_c(self): 785 self.assertEqual(FormatSymbolWithoutParameters("foo"), "foo") 786 self.assertEqual(FormatSymbolWithoutParameters("foo+42"), "foo+42") 787 788 def test_simple(self): 789 self.assertEqual(FormatSymbolWithoutParameters("foo(int i)"), "foo") 790 self.assertEqual(FormatSymbolWithoutParameters("foo(int i)+42"), "foo+42") 791 self.assertEqual(FormatSymbolWithoutParameters("bar::foo(int i)+42"), "bar::foo+42") 792 self.assertEqual(FormatSymbolWithoutParameters("operator()"), "operator()") 793 794 def test_templates(self): 795 self.assertEqual(FormatSymbolWithoutParameters("bar::foo<T>(vector<T>& v)"), "bar::foo<T>") 796 self.assertEqual(FormatSymbolWithoutParameters("bar<T>::foo(vector<T>& v)"), "bar<T>::foo") 797 self.assertEqual(FormatSymbolWithoutParameters("bar::foo<T>(vector<T<U>>& v)"), "bar::foo<T>") 798 self.assertEqual(FormatSymbolWithoutParameters("bar::foo<(EnumType)0>(vector<(EnumType)0>& v)"), 799 "bar::foo<(EnumType)0>") 800 801 def test_nested(self): 802 self.assertEqual(FormatSymbolWithoutParameters("foo(int i)::bar(int j)"), "foo::bar") 803 804 def test_unballanced(self): 805 self.assertEqual(FormatSymbolWithoutParameters("foo(bar(int i)"), "foo(bar(int i)") 806 self.assertEqual(FormatSymbolWithoutParameters("foo)bar(int i)"), "foo)bar(int i)") 807 self.assertEqual(FormatSymbolWithoutParameters("foo<bar(int i)"), "foo<bar(int i)") 808 self.assertEqual(FormatSymbolWithoutParameters("foo>bar(int i)"), "foo>bar(int i)") 809 810if __name__ == '__main__': 811 unittest.main(verbosity=2) 812