Index: lib/asan/scripts/asan_symbolize.py =================================================================== --- lib/asan/scripts/asan_symbolize.py +++ lib/asan/scripts/asan_symbolize.py @@ -8,8 +8,9 @@ # #===------------------------------------------------------------------------===# import argparse +import array +import binascii import bisect -import getopt import os import pty import re @@ -38,6 +39,164 @@ def sysroot_path_filter(binary_name): return sysroot_path + binary_name +def use_binutils_prefix(tool_name): + if binutils_prefix: + tool_name = binutils_prefix + tool_name + return tool_name + +def print_error_message(message): + print >> sys.stderr, 'Error occured during symbolizisation: ' + message + +class DebugInfoHandler(object): + def __init__(self, binary_name_filter=None): + self.binary_name_filter = binary_name_filter + self.global_debug_directory = "/usr/lib/debug" + + def use_name_filter(self, binary_name): + if self.binary_name_filter: + binary_name = self.binary_name_filter(binary_name) + return binary_name + + def calc_crc32(self, filename): + buf = open(filename,'rb').read() + buf = (binascii.crc32(buf, 0) & 0xFFFFFFFF) + return "%08X" % buf + + def readelf_binary(self, options, binary): + cmd = [use_binutils_prefix('readelf'), options, binary] + try: + process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=open(os.devnull, 'w')) + except: + print_error_message('the following command failed:\n''"' + ' '.join(cmd) + '"') + return + (readelf_out, _) = process.communicate() + if process.returncode == 0: + return readelf_out + + def has_debuginfo(self, binary): + readelf_out = self.readelf_binary('-S', binary) + return readelf_out and (".debug_" in readelf_out) + + def get_buildid(self, binary): + readelf_out = self.readelf_binary('-nw', binary) + ''' + Build ID is 40-length hex value following after "Build ID:". + e.g.: + Notes at offset 0x00000274 with length 0x00000024: + Owner Data size Description + GNU 0x00000014 NT_GNU_BUILD_ID (unique build ID bitstring) + Build ID: 977b1d1375ba6791d5f6dd38d8d55b95f9fca33a + ''' + if readelf_out: + readelf_lines = readelf_out.split("\n"); + buildid_lines = filter(re.compile('Build ID:').search, readelf_lines) + for line in buildid_lines: + match = re.search('[a-f0-9]{40}', line) + if match: + return match.group(0) + else: + print_error_message('failed to read Build ID value from ' + binary) + + def get_debuglink_name(self, binary): + readelf_out = self.readelf_binary('--string-dump=.gnu_debuglink', binary) + ''' + "debug link" is the last word in the first line of dump. + e.g.: + String dump of section '.gnu_debuglink': + [ 0] HeapOutOfBounds.out.debug + ''' + if readelf_out: + readelf_lines = filter(None, readelf_out.split("\n")); + headline ="String dump of section '.gnu_debuglink':" + try: + debuglink_line_idx = readelf_lines.index(headline) + 1 + except ValueError: + # There is no gnu_debuglink section in this binary + return + if len(readelf_lines) > debuglink_line_idx: + return readelf_lines[debuglink_line_idx].split()[-1] + else: + print_error_message('failed to read debuglink value from ' + binary) + + def get_debuglink_crc(self, binary): + readelf_out = self.readelf_binary('--hex-dump=.gnu_debuglink', binary) + ''' + crc is the last hex group (before string representation) in the last line of dump. + e.g. (crc is f89f21c2) : + Hex dump of section '.gnu_debuglink': + 0x00000000 48656170 4f75744f 66426f75 6e64732e HeapOutOfBounds. + 0x00000010 6f75742e 64656275 67000000 f89f21c2 out.debug.....!. + ''' + if readelf_out: + # get last non-empty line + crc_line = filter(None, readelf_out.split("\n"))[-1] + # remove last 16 characters (string dump) from line + crc_line = crc_line[:-16] + # crc is last word in string + crc = crc_line.split()[-1] + match = re.match('[a-f0-9]{8}', crc) + if match: + if sys.byteorder == 'little': + crc = array.array('i', binascii.unhexlify(crc) ) + crc.byteswap() + crc = binascii.hexlify(crc) + return crc + else: + print_error_message('failed to get crc checksum from debuglink in ' + binary) + + def lookfor_debuginfo(self, debugfile, orig_binary_name, debuginfo_locate_fun): + # Get debuginfo file accoding to debuginfo_location function + debuginfo = debuginfo_locate_fun(debugfile, orig_binary_name) + if debuginfo: + debuginfo = self.use_name_filter(debuginfo) + # Check if such file exists + if os.path.isfile(debuginfo): + return debuginfo + + def locate_in_orig_dir(self, debuginfo, orig_binary_name): + return os.path.join(os.path.dirname(orig_binary_name), debuginfo) + + def locate_in_debug_subdir(self, debuginfo, orig_binary_name): + return os.path.join(os.path.dirname(orig_binary_name), '.debug', debuginfo) + + def locate_in_global_debug_dir(self, debuginfo, orig_binary_name): + return self.global_debug_directory + self.locate_in_orig_dir(debuginfo, orig_binary_name) + + def locate_in_buildid_dir(self, debuginfo, orig_binary_name): + dir_name = debuginfo[0:2] + file_name = debuginfo[2:] + ".debug" + return os.path.join(self.global_debug_directory, ".build-id", dir_name, file_name) + + def get_debuginfo(self,binary_name): + orig_binary_name = binary_name + # First apply sysroot path if defined to get real binary + real_binary = self.use_name_filter(binary_name) + if self.has_debuginfo(real_binary): + return real_binary + # Look for debuginfo file according to GDB rules + # 1) "build-id" method + buildid_name = self.get_buildid(real_binary) + if buildid_name: + binary_name = self.lookfor_debuginfo(buildid_name, orig_binary_name, self.locate_in_buildid_dir) + if binary_name: + return binary_name + # 2) "debug link" method + debuglink_name = self.get_debuglink_name(real_binary) + debuglink_crc = self.get_debuglink_crc(real_binary) + if debuglink_name and debuglink_crc: + debuglink_locate_list = [ + self.locate_in_orig_dir, + self.locate_in_debug_subdir, + self.locate_in_global_debug_dir ] + for debuglink_locate_function in debuglink_locate_list: + binary_name = self.lookfor_debuginfo(debuglink_name, orig_binary_name, debuglink_locate_function) + if binary_name: + binary_crc = self.calc_crc32(binary_name) + if int(debuglink_crc,16) == int(binary_crc, 16): + return binary_name + print_error_message('No debuginfo found for ' + orig_binary_name) + return real_binary + def guess_arch(addr): # Guess which arch we're running. 10 = len('0x') + 8 hex digits. if len(addr) > 10: @@ -133,10 +292,7 @@ self.pipe = self.open_addr2line() def open_addr2line(self): - addr2line_tool = 'addr2line' - if binutils_prefix: - addr2line_tool = binutils_prefix + addr2line_tool - cmd = [addr2line_tool, '-f'] + cmd = [use_binutils_prefix('addr2line'), '-f'] if demangle: cmd += ['--demangle'] cmd += ['-e', self.binary] @@ -437,5 +593,8 @@ logfile = args.logfile else: logfile = sys.stdin + if os.uname()[0] == 'Linux': + debug_info_handler = DebugInfoHandler(binary_name_filter) + binary_name_filter = debug_info_handler.get_debuginfo loop = SymbolizationLoop(binary_name_filter) loop.process_logfile()