forked from swiftlang/swift
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsymbolicate-linux-fatal
executable file
·158 lines (140 loc) · 5.53 KB
/
symbolicate-linux-fatal
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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
#!/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()