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