]> git.proxmox.com Git - ceph.git/blob - ceph/src/seastar/scripts/seastar-addr2line
import 15.2.0 Octopus source
[ceph.git] / ceph / src / seastar / scripts / seastar-addr2line
1 #!/usr/bin/env python3
2 #
3 # This file is open source software, licensed to you under the terms
4 # of the Apache License, Version 2.0 (the "License"). See the NOTICE file
5 # distributed with this work for additional information regarding copyright
6 # ownership. You may not use this file except in compliance with the License.
7 #
8 # You may obtain a copy of the License at
9 #
10 # http://www.apache.org/licenses/LICENSE-2.0
11 #
12 # Unless required by applicable law or agreed to in writing,
13 # software distributed under the License is distributed on an
14 # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 # KIND, either express or implied. See the License for the
16 # specific language governing permissions and limitations
17 # under the License.
18 #
19 # Copyright (C) 2017 ScyllaDB
20
21 import argparse
22 import collections
23 import re
24 import sys
25 import subprocess
26
27 class Addr2Line:
28 def __init__(self, binary):
29 self._binary = binary
30
31 # Print warning if binary has no debug info according to `file`.
32 # Note: no message is printed for system errors as they will be
33 # printed also by addr2line later on.
34 output = subprocess.check_output(["file", self._binary])
35 s = output.decode("utf-8")
36 if s.find('ELF') >= 0 and s.find('debug_info', len(self._binary)) < 0:
37 print('{}'.format(s))
38
39 self._addr2line = subprocess.Popen(["addr2line", "-Cfpia", "-e", self._binary], stdin=subprocess.PIPE, stdout=subprocess.PIPE, universal_newlines=True)
40
41 # If a library doesn't exist in a particular path, addr2line
42 # will just exit. We need to be robust against that. We
43 # can't just wait on self._addr2line since there is no
44 # guarantee on what timeout is sufficient.
45 self._addr2line.stdin.write('\n')
46 self._addr2line.stdin.flush()
47 res = self._addr2line.stdout.readline()
48 self._missing = res == ''
49
50 def _read_resolved_address(self):
51 res = self._addr2line.stdout.readline()
52 # remove the address
53 res = res.split(': ', 1)[1]
54 dummy = '0x0000000000000000: ?? ??:0\n'
55 line = ''
56 while line != dummy:
57 res += line
58 line = self._addr2line.stdout.readline()
59 return res
60
61 def __call__(self, address):
62 if self._missing:
63 return " ".join([self._binary, address, '\n'])
64 # print two lines to force addr2line to output a dummy
65 # line which we can look for in _read_address
66 self._addr2line.stdin.write(address + '\n\n')
67 self._addr2line.stdin.flush()
68 return self._read_resolved_address()
69
70 class BacktraceResolver(object):
71 object_address_re = re.compile('^(.*?)\W(((/[^/]+)+)\+)?(0x[0-9a-f]+)\W*$')
72
73 def __init__(self, executable, before_lines, context_re, verbose):
74 self._executable = executable
75 self._current_backtrace = []
76 self._prefix = None
77 self._before_lines = before_lines
78 self._before_lines_queue = collections.deque(maxlen=before_lines)
79 self._i = 0
80 self._known_backtraces = {}
81 if context_re is not None:
82 self._context_re = re.compile(context_re)
83 else:
84 self._context_re = None
85 self._verbose = verbose
86 self._known_modules = {self._executable: Addr2Line(self._executable)}
87
88 def _get_resolver_for_module(self, module):
89 if not module in self._known_modules:
90 self._known_modules[module] = Addr2Line(module)
91 return self._known_modules[module]
92
93 def __enter__(self):
94 return self
95
96 def __exit__(self, type, value, tb):
97 self._print_current_backtrace()
98
99 def _print_resolved_address(self, module, address):
100 resolved_address = self._get_resolver_for_module(module)(address)
101 if self._verbose:
102 resolved_address = '{{{}}} {}: {}'.format(module, address, resolved_address)
103 sys.stdout.write(resolved_address)
104
105 def _backtrace_context_matches(self):
106 if self._context_re is None:
107 return True
108
109 if any(map(lambda x: self._context_re.search(x) is not None, self._before_lines_queue)):
110 return True
111
112 if (not self._prefix is None) and self._context_re.search(self._prefix):
113 return True
114
115 return False
116
117 def _print_current_backtrace(self):
118 if len(self._current_backtrace) == 0:
119 return
120
121 if not self._backtrace_context_matches():
122 self._current_backtrace = []
123 return
124
125 for line in self._before_lines_queue:
126 sys.stdout.write(line)
127
128 if not self._prefix is None:
129 print(self._prefix)
130 self._prefix = None
131
132 backtrace = "".join(map(str, self._current_backtrace))
133 if backtrace in self._known_backtraces:
134 print("[Backtrace #{}] Already seen, not resolving again.".format(self._known_backtraces[backtrace]))
135 print("") # To separate traces with an empty line
136 self._current_backtrace = []
137 return
138
139 self._known_backtraces[backtrace] = self._i
140
141 print("[Backtrace #{}]".format(self._i))
142
143 for module, addr in self._current_backtrace:
144 self._print_resolved_address(module, addr)
145
146 print("") # To separate traces with an empty line
147
148 self._current_backtrace = []
149 self._i += 1
150
151 def __call__(self, line):
152 match = re.match(self.object_address_re, line)
153
154 if match:
155 prefix, _, object_path, _, addr = match.groups()
156
157 if len(self._current_backtrace) == 0:
158 self._prefix = prefix;
159
160 if object_path:
161 self._current_backtrace.append((object_path, addr))
162 else:
163 self._current_backtrace.append((self._executable, addr))
164 else:
165 self._print_current_backtrace()
166 if self._before_lines > 0:
167 self._before_lines_queue.append(line)
168 elif self._before_lines < 0:
169 sys.stdout.write(line) # line already has a trailing newline
170 else:
171 pass # when == 0 no non-backtrace lines are printed
172
173
174 class StdinBacktraceIterator(object):
175 """
176 Read stdin char-by-char and stop when when user pressed Ctrl+D or the
177 Enter twice. Altough reading char-by-char is slow this won't be a
178 problem here as backtraces shouldn't be huge.
179 """
180 def __iter__(self):
181 linefeeds = 0
182 lines = []
183 line = []
184
185 while True:
186 char = sys.stdin.read(1)
187
188 if char == '\n':
189 linefeeds += 1
190
191 if len(line) > 0:
192 lines.append(''.join(line))
193 line = []
194 else:
195 line.append(char)
196 linefeeds = 0
197
198 if char == '' or linefeeds > 1:
199 break
200
201 return iter(lines)
202
203
204 description='Massage and pass addresses to the real addr2line for symbol lookup.'
205 epilog='''
206 There are three operational modes:
207 1) If -f is specified input will be read from FILE
208 2) If -f is omitted and there are ADDRESS args they will be read as input
209 3) If -f is omitted and there are no ADDRESS args input will be read from stdin
210 '''
211
212 cmdline_parser = argparse.ArgumentParser(
213 description=description,
214 epilog=epilog,
215 formatter_class=argparse.RawDescriptionHelpFormatter,
216 )
217
218 cmdline_parser.add_argument(
219 '-e',
220 '--executable',
221 type=str,
222 required=True,
223 metavar='EXECUTABLE',
224 dest='executable',
225 help='The executable where the addresses originate from')
226
227 cmdline_parser.add_argument(
228 '-f',
229 '--file',
230 type=str,
231 required=False,
232 metavar='FILE',
233 dest='file',
234 help='The file containing the addresses (one per line)')
235
236 cmdline_parser.add_argument(
237 '-b',
238 '--before',
239 type=int,
240 metavar='BEFORE',
241 default=1,
242 help='Non-backtrace lines to print before resolved backtraces for context.'
243 ' Set to 0 to print only resolved backtraces.'
244 ' Set to -1 to print all non-backtrace lines. Default is 1.')
245
246 cmdline_parser.add_argument(
247 '-m',
248 '--match',
249 type=str,
250 metavar='MATCH',
251 help='Only resolve backtraces whose non-backtrace lines match the regular-expression.'
252 ' The amount of non-backtrace lines considered can be set with --before.'
253 ' By default no matching is performed.')
254
255 cmdline_parser.add_argument(
256 '-v',
257 '--verbose',
258 action='store_true',
259 default=False,
260 help='Make resolved backtraces verbose, prepend to each line the module'
261 ' it originates from, as well as the address being resolved')
262
263 cmdline_parser.add_argument(
264 'addresses',
265 type=str,
266 metavar='ADDRESS',
267 nargs='*',
268 help='Addresses to parse')
269
270 args = cmdline_parser.parse_args()
271
272 if args.addresses and args.file:
273 print("Cannot use both -f and ADDRESS")
274 cmdline_parser.print_help()
275
276
277 if args.file:
278 lines = open(args.file, 'r')
279 elif args.addresses:
280 lines = args.addresses
281 else:
282 if sys.stdin.isatty():
283 lines = StdinBacktraceIterator()
284 else:
285 lines = sys.stdin
286
287 with BacktraceResolver(args.executable, args.before, args.match, args.verbose) as resolve:
288 for line in lines:
289 resolve(line)