| # Copyright 2017 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import logging |
| import os |
| import re |
| import subprocess |
| import threading |
| |
| _CHROME_SRC = os.path.join(os.path.dirname(__file__), os.pardir, os.pardir) |
| _LLVM_SYMBOLIZER_PATH = os.path.join( |
| _CHROME_SRC, 'third_party', 'llvm-build', 'Release+Asserts', 'bin', |
| 'llvm-symbolizer') |
| |
| _BINARY = re.compile(r'0b[0,1]+') |
| _HEX = re.compile(r'0x[0-9,a-e]+') |
| _OCTAL = re.compile(r'0[0-7]+') |
| |
| _UNKNOWN = '<UNKNOWN>' |
| |
| |
| def _CheckValidAddr(addr): |
| """ |
| Check whether the addr is valid input to llvm symbolizer. |
| Valid addr has to be octal, binary, or hex number. |
| |
| Args: |
| addr: addr to be entered to llvm symbolizer. |
| |
| Returns: |
| whether the addr is valid input to llvm symbolizer. |
| """ |
| return _HEX.match(addr) or _OCTAL.match(addr) or _BINARY.match(addr) |
| |
| |
| class LLVMSymbolizer(object): |
| def __init__(self): |
| """Create a LLVMSymbolizer instance that interacts with the llvm symbolizer. |
| |
| The purpose of the LLVMSymbolizer is to get function names and line |
| numbers of an address from the symbols library. |
| """ |
| self._llvm_symbolizer_subprocess = None |
| # Allow only one thread to call GetSymbolInformation at a time. |
| self._lock = threading.Lock() |
| |
| def Start(self): |
| """Start the llvm symbolizer subprocess. |
| |
| Create a subprocess of the llvm symbolizer executable, which will be used |
| to retrieve function names etc. |
| """ |
| if os.path.isfile(_LLVM_SYMBOLIZER_PATH): |
| self._llvm_symbolizer_subprocess = subprocess.Popen( |
| [_LLVM_SYMBOLIZER_PATH], stdout=subprocess.PIPE, stdin=subprocess.PIPE) |
| else: |
| logging.error('Cannot find llvm_symbolizer here: %s.' % |
| _LLVM_SYMBOLIZER_PATH) |
| self._llvm_symbolizer_subprocess = None |
| |
| def Close(self): |
| """Close the llvm symbolizer subprocess. |
| |
| Close the subprocess by closing stdin, stdout and killing the subprocess. |
| """ |
| with self._lock: |
| if self._llvm_symbolizer_subprocess: |
| self._llvm_symbolizer_subprocess.kill() |
| self._llvm_symbolizer_subprocess = None |
| |
| def __enter__(self): |
| """Start the llvm symbolizer subprocess.""" |
| self.Start() |
| return self |
| |
| def __exit__(self, exc_type, exc_val, exc_tb): |
| """Close the llvm symbolizer subprocess.""" |
| self.Close() |
| |
| def GetSymbolInformation(self, lib, addr): |
| """Return the corresponding function names and line numbers. |
| |
| Args: |
| lib: library to search for info. |
| addr: address to look for info. |
| |
| Returns: |
| A list of (function name, line numbers) tuple. |
| """ |
| if (self._llvm_symbolizer_subprocess is None or not lib |
| or not _CheckValidAddr(addr) or not os.path.isfile(lib)): |
| return [(_UNKNOWN, lib)] |
| |
| with self._lock: |
| self._llvm_symbolizer_subprocess.stdin.write('%s %s\n' % (lib, addr)) |
| self._llvm_symbolizer_subprocess.stdin.flush() |
| |
| result = [] |
| # Read till see new line, which is a symbol of end of output. |
| # One line of function name is always followed by one line of line number. |
| while True: |
| line = self._llvm_symbolizer_subprocess.stdout.readline() |
| if line != '\n': |
| line_numbers = self._llvm_symbolizer_subprocess.stdout.readline() |
| result.append( |
| (line[:-1], |
| line_numbers[:-1])) |
| else: |
| return result |