#!/usr/bin/env python # symbolicate-linux-fatal - Symbolicate Linux stack traces -*- python -*- # # This source file is part of the Swift.org open source project # # Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors # Licensed under Apache License v2.0 with Runtime Library Exception # # See https://swift.org/LICENSE.txt for license information # See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors # # ---------------------------------------------------------------------------- # # Symbolicates fatalError stack traces on Linux. Takes the main binary # and a log file containing a stack trace. Non-stacktrace lines are output # unmodified. Stack trace elements are analyzed using reconstructed debug # target matching the original process in where shared libs where mapped. # # TODOs: # * verbose output # * search symbols by name for the not ones # # ---------------------------------------------------------------------------- from __future__ import print_function import argparse import subprocess import lldb def process_ldd(lddoutput): dyn_libs = {} for line in lddoutput.splitlines(): ldd_tokens = line.split() if len(ldd_tokens) >= 3: dyn_libs[ldd_tokens[0]] = ldd_tokens[2] return dyn_libs def create_lldb_target(binary, memmap): lldb_debugger = lldb.SBDebugger.Create() lldb_target = lldb_debugger.CreateTargetWithFileAndArch( binary, lldb.LLDB_ARCH_DEFAULT) module = lldb_target.GetModuleAtIndex(0) # lldb seems to treat main binary differently, slide offset must be zero lldb_target.SetModuleLoadAddress(module, 0) for dynlib_path in memmap: if binary not in dynlib_path: module = lldb_target.AddModule( dynlib_path, lldb.LLDB_ARCH_DEFAULT, None, None) lldb_target.SetModuleLoadAddress(module, memmap[dynlib_path]) return lldb_target def process_stack(binary, dyn_libs, stack): if len(stack) == 0: return memmap = {} full_stack = [] for line in stack: stack_tokens = line.split() dynlib_fname = stack_tokens[1] if dynlib_fname in dyn_libs: dynlib_path = dyn_libs[dynlib_fname] elif dynlib_fname in binary: dynlib_path = binary else: dynlib_path = None if "" in stack_tokens[3]: framePC = int(stack_tokens[2], 16) symbol_offset = int(stack_tokens[-1], 10) dynlib_baseaddr = framePC - symbol_offset if dynlib_path in memmap: if memmap[dynlib_path] != dynlib_baseaddr: error_msg = "Mismatched base address for: {0:s}, " \ "had: {1:x}, now got {2:x}" error_msg = error_msg.format( dynlib_path, memmap[dynlib_path], dynlib_baseaddr) raise Exception(error_msg) else: memmap[dynlib_path] = dynlib_baseaddr else: framePC = int(stack_tokens[2], 16) + int(stack_tokens[-1], 10) full_stack.append( {"line": line, "framePC": framePC, "dynlib_fname": dynlib_fname}) lldb_target = create_lldb_target(binary, memmap) frame_idx = 0 for frame in full_stack: use_orig_line = True frame_addr = frame["framePC"] dynlib_fname = frame["dynlib_fname"] so_addr = lldb_target.ResolveLoadAddress(frame_addr - 1) sym_ctx = so_addr.GetSymbolContext(lldb.eSymbolContextEverything) frame_fragment = "{0: <4d} {1:20s} 0x{2:016x}".format( frame_idx, dynlib_fname, frame_addr) symbol = sym_ctx.GetSymbol() if symbol.IsValid(): symbol_base = symbol.GetStartAddress().GetLoadAddress(lldb_target) symbol_fragment = "{0:s} + {1:d}".format( symbol.GetName(), frame_addr - symbol_base) use_orig_line = False else: symbol_fragment = "" line_entry = sym_ctx.GetLineEntry() if line_entry.IsValid(): line_fragment = "at {0:s}:{1:d}".format( line_entry.GetFileSpec().GetFilename(), line_entry.GetLine()) else: line_fragment = "" if use_orig_line: print(frame["line"].rstrip()) else: print("{0:s} {1:s} {2:s}".format( frame_fragment, symbol_fragment, line_fragment)) frame_idx = frame_idx + 1 def main(): parser = argparse.ArgumentParser( formatter_class=argparse.RawDescriptionHelpFormatter, description="""Symbolicates stack traces in Linux log files.""") parser.add_argument( "binary", help="Executable which produced the log file") parser.add_argument( "log", type=argparse.FileType("rU"), help="Log file containing the stack trace to symbolicate") args = parser.parse_args() binary = args.binary lddoutput = subprocess.check_output( ['ldd', binary], stderr=subprocess.STDOUT) dyn_libs = process_ldd(lddoutput) instack = False stackidx = 0 stack = [] for line in args.log: if instack and line.startswith(str(stackidx)): stack.append(line) stackidx = stackidx + 1 else: instack = False stackidx = 0 process_stack(binary, dyn_libs, stack) stack = [] print(line.rstrip()) if line.startswith("Current stack trace:"): instack = True process_stack(binary, dyn_libs, stack) if __name__ == '__main__': main()