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