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