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