1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
|
# Copyright 2017 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import functools
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 = '<UNKNOWN>'
_ELF_MAGIC_HEADER_BYTES = b'\x7f\x45\x4c\x46'
@functools.lru_cache
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
@staticmethod
def IsValidTarget(path):
return IsValidLLVMSymbolizerTarget(path)
|