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
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["ANDROID_BUILD_TOP"]
32if not ANDROID_BUILD_TOP:
33  ANDROID_BUILD_TOP = "."
34
35def 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
45SYMBOLS_DIR = FindSymbolsDir()
46
47ARCH = 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
61class 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
113def CloseAllPipes():
114  _PIPE_ADDR2LINE_CACHE.KillAllProcesses()
115  _PIPE_CPPFILT_CACHE.KillAllProcesses()
116
117
118atexit.register(CloseAllPipes)
119
120
121def PipeTermHandler(signum, frame):
122  CloseAllPipes()
123  os._exit(0)
124
125
126for sig in (signal.SIGABRT, signal.SIGINT, signal.SIGTERM):
127  signal.signal(sig, PipeTermHandler)
128
129
130
131
132def 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
139def 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
173def 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
196def 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
244def 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
325def 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
340def 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
444def 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
461def FormatSymbolWithOffset(symbol, offset):
462  if offset == 0:
463    return symbol
464  return "%s+%d" % (symbol, offset)
465
466
467def 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
485def 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
496def 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
504def 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
540class 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
553class 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
718if __name__ == '__main__':
719    unittest.main()
720