Skip to content

Commit

Permalink
Allow use of llvm-addr2line as the command
Browse files Browse the repository at this point in the history
Prior to this change the addr2line.py script always uses addr2line as the
binary to decode stack frames. This change allows the path and/or name
of the binary to be provided explicitly on the command line.

This allows the of llvm-addr2line, an alternative implementation
based on llvm-symbolizer, which in my experiments is over *200* times as
fast as addr2line in decoding some redpanda stack traces.

See https://sourceware.org/bugzilla/show_bug.cgi?id=29010 for more
on the addr2line slowness.

This change also slightly changes the 'dummy line' strategy used to
detect when addr2line has finished outputting frames for the current
address to make it compatible with llvm-addr2line.

Fixes scylladb#1035.
  • Loading branch information
travisdowns committed Apr 7, 2022
1 parent f214fb9 commit 2a9504b
Show file tree
Hide file tree
Showing 2 changed files with 33 additions and 11 deletions.
32 changes: 22 additions & 10 deletions scripts/addr2line.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,18 @@
from enum import Enum

class Addr2Line:
def __init__(self, binary, concise=False):

# Matcher for a line that appears at the end a single decoded
# address, which we force by adding a dummy 0x0 address. The
# pattern varies between binutils addr2line and llvm-addr2line
# so we match both.
dummy_pattern = re.compile(
r"(.*0x0000000000000000: \?\? \?\?:0\n)" # addr2line pattern
r"|"
r"(.*0x0: \?\? at \?\?:0\n)" # llvm-addr2line pattern
)

def __init__(self, binary, concise=False, cmd_path="addr2line"):
self._binary = binary

# Print warning if binary has no debug info according to `file`.
Expand All @@ -38,7 +49,7 @@ def __init__(self, binary, concise=False):
print('{}'.format(s))

options = f"-{'C' if not concise else ''}fpia"
self._input = subprocess.Popen(["addr2line", options, "-e", self._binary], stdin=subprocess.PIPE, stdout=subprocess.PIPE, universal_newlines=True)
self._input = subprocess.Popen([cmd_path, options, "-e", self._binary], stdin=subprocess.PIPE, stdout=subprocess.PIPE, universal_newlines=True)
if concise:
self._output = subprocess.Popen(["c++filt", "-p"], stdin=self._input.stdout, stdout=subprocess.PIPE, universal_newlines=True)
else:
Expand All @@ -57,19 +68,18 @@ def _read_resolved_address(self):
res = self._output.stdout.readline()
# remove the address
res = res.split(': ', 1)[1]
dummy = '0x0000000000000000: ?? ??:0\n'
line = ''
while line != dummy:
while Addr2Line.dummy_pattern.fullmatch(line) is None:
res += line
line = self._output.stdout.readline()
return res

def __call__(self, address):
if self._missing:
return " ".join([self._binary, address, '\n'])
# print two lines to force addr2line to output a dummy
# line which we can look for in _read_address
self._input.stdin.write(address + '\n\n')
# We print a dummy 0x0 address after the address we are interested in
# which we can look for in _read_address
self._input.stdin.write(address + '\n0x0\n')
self._input.stdin.flush()
return self._read_resolved_address()

Expand Down Expand Up @@ -124,7 +134,7 @@ def get_prefix(s):
#print(f">>> '{line}': None")
return None

def __init__(self, executable, before_lines=1, context_re='', verbose=False, concise=False):
def __init__(self, executable, before_lines=1, context_re='', verbose=False, concise=False, cmd_path='addr2line'):
self._executable = executable
self._current_backtrace = []
self._prefix = None
Expand All @@ -138,12 +148,14 @@ def __init__(self, executable, before_lines=1, context_re='', verbose=False, con
self._context_re = None
self._verbose = verbose
self._concise = concise
self._known_modules = {self._executable: Addr2Line(self._executable, concise)}
self._cmd_path = cmd_path
self._known_modules = {}
self._get_resolver_for_module(self._executable) # fail fast if there is something wrong with the exe resolver
self.parser = self.BacktraceParser()

def _get_resolver_for_module(self, module):
if not module in self._known_modules:
self._known_modules[module] = Addr2Line(module, self._concise)
self._known_modules[module] = Addr2Line(module, self._concise, self._cmd_path)
return self._known_modules[module]

def __enter__(self):
Expand Down
12 changes: 11 additions & 1 deletion scripts/seastar-addr2line
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,15 @@ cmdline_parser.add_argument(
' The amount of non-backtrace lines considered can be set with --before.'
' By default no matching is performed.')

cmdline_parser.add_argument(
'-a',
'--addr2line',
type=str,
metavar='CMD_PATH',
default='addr2line',
help='The path or name of the addr2line command, which should behave as and '
'accept the same options as binutils addr2line or llvm-addr2line.')

cmdline_parser.add_argument(
'-v',
'--verbose',
Expand Down Expand Up @@ -205,7 +214,8 @@ else:
else:
lines = sys.stdin

with BacktraceResolver(executable=args.executable, before_lines=args.before, context_re=args.match, verbose=args.verbose) as resolve:
with BacktraceResolver(executable=args.executable, before_lines=args.before, context_re=args.match,
verbose=args.verbose, cmd_path=args.addr2line) as resolve:
p = re.compile(r'\W+')
for line in lines:
resolve(line)

0 comments on commit 2a9504b

Please sign in to comment.