]> git.proxmox.com Git - ceph.git/blame - ceph/src/seastar/scripts/addr2line.py
import quincy beta 17.1.0
[ceph.git] / ceph / src / seastar / scripts / addr2line.py
CommitLineData
20effc67
TL
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
21import argparse
22import collections
23import re
24import sys
25import subprocess
26from enum import Enum
27
28class Addr2Line:
29 def __init__(self, binary, concise=False):
30 self._binary = binary
31
32 # Print warning if binary has no debug info according to `file`.
33 # Note: no message is printed for system errors as they will be
34 # printed also by addr2line later on.
35 output = subprocess.check_output(["file", self._binary])
36 s = output.decode("utf-8")
37 if s.find('ELF') >= 0 and s.find('debug_info', len(self._binary)) < 0:
38 print('{}'.format(s))
39
40 options = f"-{'C' if not concise else ''}fpia"
41 self._input = subprocess.Popen(["addr2line", options, "-e", self._binary], stdin=subprocess.PIPE, stdout=subprocess.PIPE, universal_newlines=True)
42 if concise:
43 self._output = subprocess.Popen(["c++filt", "-p"], stdin=self._input.stdout, stdout=subprocess.PIPE, universal_newlines=True)
44 else:
45 self._output = self._input
46
47 # If a library doesn't exist in a particular path, addr2line
48 # will just exit. We need to be robust against that. We
49 # can't just wait on self._addr2line since there is no
50 # guarantee on what timeout is sufficient.
51 self._input.stdin.write('\n')
52 self._input.stdin.flush()
53 res = self._output.stdout.readline()
54 self._missing = res == ''
55
56 def _read_resolved_address(self):
57 res = self._output.stdout.readline()
58 # remove the address
59 res = res.split(': ', 1)[1]
60 dummy = '0x0000000000000000: ?? ??:0\n'
61 line = ''
62 while line != dummy:
63 res += line
64 line = self._output.stdout.readline()
65 return res
66
67 def __call__(self, address):
68 if self._missing:
69 return " ".join([self._binary, address, '\n'])
70 # print two lines to force addr2line to output a dummy
71 # line which we can look for in _read_address
72 self._input.stdin.write(address + '\n\n')
73 self._input.stdin.flush()
74 return self._read_resolved_address()
75
76class BacktraceResolver(object):
77
78 class BacktraceParser(object):
79 class Type(Enum):
80 ADDRESS = 1
81 SEPARATOR = 2
82
83 def __init__(self):
84 addr = "0x[0-9a-f]+"
85 path = "\S+"
86 token = f"(?:{path}\+)?{addr}"
87 full_addr_match = f"(?:({path})\+)?({addr})"
88 self.oneline_re = re.compile(f"^((?:.*(?:(?:at|backtrace):?|:))?(?:\s+))?({token}(?:\s+{token})*)(?:\).*|\s*)$", flags=re.IGNORECASE)
89 self.address_re = re.compile(full_addr_match, flags=re.IGNORECASE)
90 self.asan_re = re.compile(f"^(?:.*\s+)\(({full_addr_match})\)\s*$", flags=re.IGNORECASE)
91 self.separator_re = re.compile('^\W*-+\W*$')
92
93 def __call__(self, line):
94 def get_prefix(s):
95 if s is not None:
96 s = s.strip()
97 return s or None
98
99 m = re.match(self.oneline_re, line)
100 if m:
101 #print(f">>> '{line}': oneline {m.groups()}")
102 ret = {'type': self.Type.ADDRESS}
103 ret['prefix'] = get_prefix(m.group(1))
104 addresses = []
105 for obj in m.group(2).split():
106 m = re.match(self.address_re, obj)
107 #print(f" >>> '{obj}': address {m.groups()}")
108 addresses.append({'path': m.group(1), 'addr': m.group(2)})
109 ret['addresses'] = addresses
110 return ret
111
112 m = re.match(self.asan_re, line)
113 if m:
114 #print(f">>> '{line}': asan {m.groups()}")
115 ret = {'type': self.Type.ADDRESS}
116 ret['prefix'] = None
117 ret['addresses'] = [{'path': m.group(2), 'addr': m.group(3)}]
118 return ret
119
120 match = re.match(self.separator_re, line)
121 if match:
122 return {'type': self.Type.SEPARATOR}
123
124 #print(f">>> '{line}': None")
125 return None
126
127 def __init__(self, executable, before_lines=1, context_re='', verbose=False, concise=False):
128 self._executable = executable
129 self._current_backtrace = []
130 self._prefix = None
131 self._before_lines = before_lines
132 self._before_lines_queue = collections.deque(maxlen=before_lines)
133 self._i = 0
134 self._known_backtraces = {}
135 if context_re is not None:
136 self._context_re = re.compile(context_re)
137 else:
138 self._context_re = None
139 self._verbose = verbose
140 self._concise = concise
141 self._known_modules = {self._executable: Addr2Line(self._executable, concise)}
142 self.parser = self.BacktraceParser()
143
144 def _get_resolver_for_module(self, module):
145 if not module in self._known_modules:
146 self._known_modules[module] = Addr2Line(module, self._concise)
147 return self._known_modules[module]
148
149 def __enter__(self):
150 return self
151
152 def __exit__(self, type, value, tb):
153 self._print_current_backtrace()
154
155 def resolve_address(self, address, module=None, verbose=None):
156 if module is None:
157 module = self._executable
158 if verbose is None:
159 verbose = self._verbose
160 resolved_address = self._get_resolver_for_module(module)(address)
161 if verbose:
162 resolved_address = '{{{}}} {}: {}'.format(module, address, resolved_address)
163 return resolved_address
164
165 def _print_resolved_address(self, module, address):
166 sys.stdout.write(self.resolve_address(address, module))
167
168 def _backtrace_context_matches(self):
169 if self._context_re is None:
170 return True
171
172 if any(map(lambda x: self._context_re.search(x) is not None, self._before_lines_queue)):
173 return True
174
175 if (not self._prefix is None) and self._context_re.search(self._prefix):
176 return True
177
178 return False
179
180 def _print_current_backtrace(self):
181 if len(self._current_backtrace) == 0:
182 return
183
184 if not self._backtrace_context_matches():
185 self._current_backtrace = []
186 return
187
188 for line in self._before_lines_queue:
189 sys.stdout.write(line)
190
191 if not self._prefix is None:
192 print(self._prefix)
193 self._prefix = None
194
195 backtrace = "".join(map(str, self._current_backtrace))
196 if backtrace in self._known_backtraces:
197 print("[Backtrace #{}] Already seen, not resolving again.".format(self._known_backtraces[backtrace]))
198 print("") # To separate traces with an empty line
199 self._current_backtrace = []
200 return
201
202 self._known_backtraces[backtrace] = self._i
203
204 print("[Backtrace #{}]".format(self._i))
205
206 for module, addr in self._current_backtrace:
207 self._print_resolved_address(module, addr)
208
209 print("") # To separate traces with an empty line
210
211 self._current_backtrace = []
212 self._i += 1
213
214 def __call__(self, line):
215 res = self.parser(line)
216
217 if not res:
218 self._print_current_backtrace()
219 if self._before_lines > 0:
220 self._before_lines_queue.append(line)
221 elif self._before_lines < 0:
222 sys.stdout.write(line) # line already has a trailing newline
223 else:
224 pass # when == 0 no non-backtrace lines are printed
225 elif res['type'] == self.BacktraceParser.Type.SEPARATOR:
226 pass
227 elif res['type'] == self.BacktraceParser.Type.ADDRESS:
228 addresses = res['addresses']
229 if len(addresses) > 1:
230 self._print_current_backtrace()
231 if len(self._current_backtrace) == 0:
232 self._prefix = res['prefix']
233 for r in addresses:
234 if r['path']:
235 self._current_backtrace.append((r['path'], r['addr']))
236 else:
237 self._current_backtrace.append((self._executable, r['addr']))
238 if len(addresses) > 1:
239 self._print_current_backtrace()
240 else:
241 print(f"Unknown '{line}': {res}")
242 raise RuntimeError("Unknown result type {res}")
243