1# Copyright 2017 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import logging
6import os
7import re
8import subprocess
9import threading
10
11_CHROME_SRC = os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)
12_LLVM_SYMBOLIZER_PATH = os.path.join(
13    _CHROME_SRC, 'third_party', 'llvm-build', 'Release+Asserts', 'bin',
14    'llvm-symbolizer')
15
16_BINARY = re.compile(r'0b[0,1]+')
17_HEX = re.compile(r'0x[0-9,a-e]+')
18_OCTAL = re.compile(r'0[0-7]+')
19
20_UNKNOWN = '<UNKNOWN>'
21
22
23def _CheckValidAddr(addr):
24  """
25  Check whether the addr is valid input to llvm symbolizer.
26  Valid addr has to be octal, binary, or hex number.
27
28  Args:
29    addr: addr to be entered to llvm symbolizer.
30
31  Returns:
32    whether the addr is valid input to llvm symbolizer.
33  """
34  return _HEX.match(addr) or _OCTAL.match(addr) or _BINARY.match(addr)
35
36
37class LLVMSymbolizer(object):
38  def __init__(self):
39    """Create a LLVMSymbolizer instance that interacts with the llvm symbolizer.
40
41    The purpose of the LLVMSymbolizer is to get function names and line
42    numbers of an address from the symbols library.
43    """
44    self._llvm_symbolizer_subprocess = None
45    # Allow only one thread to call GetSymbolInformation at a time.
46    self._lock = threading.Lock()
47
48  def Start(self):
49    """Start the llvm symbolizer subprocess.
50
51    Create a subprocess of the llvm symbolizer executable, which will be used
52    to retrieve function names etc.
53    """
54    if os.path.isfile(_LLVM_SYMBOLIZER_PATH):
55      self._llvm_symbolizer_subprocess = subprocess.Popen(
56        [_LLVM_SYMBOLIZER_PATH], stdout=subprocess.PIPE, stdin=subprocess.PIPE)
57    else:
58      logging.error('Cannot find llvm_symbolizer here: %s.' %
59                    _LLVM_SYMBOLIZER_PATH)
60      self._llvm_symbolizer_subprocess = None
61
62  def Close(self):
63    """Close the llvm symbolizer subprocess.
64
65    Close the subprocess by closing stdin, stdout and killing the subprocess.
66    """
67    with self._lock:
68      if self._llvm_symbolizer_subprocess:
69        self._llvm_symbolizer_subprocess.kill()
70        self._llvm_symbolizer_subprocess = None
71
72  def __enter__(self):
73    """Start the llvm symbolizer subprocess."""
74    self.Start()
75    return self
76
77  def __exit__(self, exc_type, exc_val, exc_tb):
78    """Close the llvm symbolizer subprocess."""
79    self.Close()
80
81  def GetSymbolInformation(self, lib, addr):
82    """Return the corresponding function names and line numbers.
83
84    Args:
85      lib: library to search for info.
86      addr: address to look for info.
87
88    Returns:
89      A list of (function name, line numbers) tuple.
90    """
91    if (self._llvm_symbolizer_subprocess is None or not lib
92        or not _CheckValidAddr(addr) or not os.path.isfile(lib)):
93      return [(_UNKNOWN, lib)]
94
95    with self._lock:
96      self._llvm_symbolizer_subprocess.stdin.write('%s %s\n' % (lib, addr))
97      self._llvm_symbolizer_subprocess.stdin.flush()
98
99      result = []
100      # Read till see new line, which is a symbol of end of output.
101      # One line of function name is always followed by one line of line number.
102      while True:
103        line = self._llvm_symbolizer_subprocess.stdout.readline()
104        if line != '\n':
105          line_numbers = self._llvm_symbolizer_subprocess.stdout.readline()
106          result.append(
107            (line[:-1],
108             line_numbers[:-1]))
109        else:
110          return result
111