# 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 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') _UNKNOWN = '' _ELF_MAGIC_HEADER_BYTES = b'\x7f\x45\x4c\x46' def IsValidLLVMSymbolizerTarget(file_path): """ Verify the passed file is a valid target for llvm-symbolization Args: file_path: Path to a file to be checked Return: True if the file exists and has the correct ELF header, False otherwise """ try: with open(file_path, 'rb') as f: header_bytes = f.read(4) return header_bytes == _ELF_MAGIC_HEADER_BYTES except IOError: return False 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 self._llvm_symbolizer_parameters = [ '--functions', '--demangle', '--inlines', ] # 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] + self._llvm_symbolizer_parameters, stdout=subprocess.PIPE, stdin=subprocess.PIPE, universal_newlines=True) 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 triplet of address, module-name and list of symbols """ if (self._llvm_symbolizer_subprocess is None): logging.error('Can\'t run llvm-symbolizer! ' + 'Subprocess for llvm-symbolizer has not been started!') return [(_UNKNOWN, lib)] if not lib: logging.error('Can\'t run llvm-symbolizer! No target is given!') return [(_UNKNOWN, lib)] if not IsValidLLVMSymbolizerTarget(lib): logging.error( 'Can\'t run llvm-symbolizer! ' + 'Given binary is not a valid target. path=%s', lib) return [(_UNKNOWN, lib)] proc = self._llvm_symbolizer_subprocess with self._lock: proc.stdin.write('%s %s\n' % (lib, hex(addr))) proc.stdin.flush() result = [] # Read until an empty line is observed, which indicates the end of the # output. Each line with a function name is always followed by one line # with the corresponding line number. while True: line = proc.stdout.readline() if line != '\n': line_numbers = proc.stdout.readline() result.append((line[:-1], line_numbers[:-1])) else: return result