mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
Looks like flake8 enables other rules when you add something to the exclusion list. We added W291
159 lines
5.5 KiB
Python
Executable File
159 lines
5.5 KiB
Python
Executable File
#!/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 <unavailable> 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 "<unavailable>" 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 = "<unavailable>"
|
|
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()
|