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.
8 # You may obtain a copy of the License at
10 # http://www.apache.org/licenses/LICENSE-2.0
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
19 # Copyright (C) 2017 ScyllaDB
28 def __init__(self
, binary
):
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:
39 self
._addr
2line
= subprocess
.Popen(["addr2line", "-Cfpia", "-e", self
._binary
], stdin
=subprocess
.PIPE
, stdout
=subprocess
.PIPE
, universal_newlines
=True)
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
._addr
2line
.stdin
.write('\n')
46 self
._addr
2line
.stdin
.flush()
47 res
= self
._addr
2line
.stdout
.readline()
48 self
._missing
= res
== ''
50 def _read_resolved_address(self
):
51 res
= self
._addr
2line
.stdout
.readline()
53 res
= res
.split(': ', 1)[1]
54 dummy
= '0x0000000000000000: ?? ??:0\n'
58 line
= self
._addr
2line
.stdout
.readline()
61 def __call__(self
, address
):
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
._addr
2line
.stdin
.write(address
+ '\n\n')
67 self
._addr
2line
.stdin
.flush()
68 return self
._read
_resolved
_address
()
70 class BacktraceResolver(object):
71 object_address_re
= re
.compile('^(.*?)\W(((/[^/]+)+)\+)?(0x[0-9a-f]+)\W*$')
73 def __init__(self
, executable
, before_lines
, context_re
, verbose
):
74 self
._executable
= executable
75 self
._current
_backtrace
= []
77 self
._before
_lines
= before_lines
78 self
._before
_lines
_queue
= collections
.deque(maxlen
=before_lines
)
80 self
._known
_backtraces
= {}
81 if context_re
is not None:
82 self
._context
_re
= re
.compile(context_re
)
84 self
._context
_re
= None
85 self
._verbose
= verbose
86 self
._known
_modules
= {self
._executable
: Addr2Line(self
._executable
)}
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
]
96 def __exit__(self
, type, value
, tb
):
97 self
._print
_current
_backtrace
()
99 def _print_resolved_address(self
, module
, address
):
100 resolved_address
= self
._get
_resolver
_for
_module
(module
)(address
)
102 resolved_address
= '{{{}}} {}: {}'.format(module
, address
, resolved_address
)
103 sys
.stdout
.write(resolved_address
)
105 def _backtrace_context_matches(self
):
106 if self
._context
_re
is None:
109 if any(map(lambda x
: self
._context
_re
.search(x
) is not None, self
._before
_lines
_queue
)):
112 if (not self
._prefix
is None) and self
._context
_re
.search(self
._prefix
):
117 def _print_current_backtrace(self
):
118 if len(self
._current
_backtrace
) == 0:
121 if not self
._backtrace
_context
_matches
():
122 self
._current
_backtrace
= []
125 for line
in self
._before
_lines
_queue
:
126 sys
.stdout
.write(line
)
128 if not self
._prefix
is None:
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
= []
139 self
._known
_backtraces
[backtrace
] = self
._i
141 print("[Backtrace #{}]".format(self
._i
))
143 for module
, addr
in self
._current
_backtrace
:
144 self
._print
_resolved
_address
(module
, addr
)
146 print("") # To separate traces with an empty line
148 self
._current
_backtrace
= []
151 def __call__(self
, line
):
152 match
= re
.match(self
.object_address_re
, line
)
155 prefix
, _
, object_path
, _
, addr
= match
.groups()
157 if len(self
._current
_backtrace
) == 0:
158 self
._prefix
= prefix
;
161 self
._current
_backtrace
.append((object_path
, addr
))
163 self
._current
_backtrace
.append((self
._executable
, addr
))
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
171 pass # when == 0 no non-backtrace lines are printed
174 class StdinBacktraceIterator(object):
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.
186 char
= sys
.stdin
.read(1)
192 lines
.append(''.join(line
))
198 if char
== '' or linefeeds
> 1:
204 description
='Massage and pass addresses to the real addr2line for symbol lookup.'
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
212 cmdline_parser
= argparse
.ArgumentParser(
213 description
=description
,
215 formatter_class
=argparse
.RawDescriptionHelpFormatter
,
218 cmdline_parser
.add_argument(
223 metavar
='EXECUTABLE',
225 help='The executable where the addresses originate from')
227 cmdline_parser
.add_argument(
234 help='The file containing the addresses (one per line)')
236 cmdline_parser
.add_argument(
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.')
246 cmdline_parser
.add_argument(
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.')
255 cmdline_parser
.add_argument(
260 help='Make resolved backtraces verbose, prepend to each line the module'
261 ' it originates from, as well as the address being resolved')
263 cmdline_parser
.add_argument(
268 help='Addresses to parse')
270 args
= cmdline_parser
.parse_args()
272 if args
.addresses
and args
.file:
273 print("Cannot use both -f and ADDRESS")
274 cmdline_parser
.print_help()
278 lines
= open(args
.file, 'r')
280 lines
= args
.addresses
282 if sys
.stdin
.isatty():
283 lines
= StdinBacktraceIterator()
287 with
BacktraceResolver(args
.executable
, args
.before
, args
.match
, args
.verbose
) as resolve
: