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