Remove parameters from symbolized stack traces.

The expanded parameters take a lot horizontal space
and almost always push the file:line of the screen,
or text-wrap generating multiple lines per frame.

This CL tries to make the output less cluttered by
removing parameters from the unmangled method names.

It is possible to add the parameters back using
the --verbose command line argument.

Test: Add unit tests, investigate crashes from logcat
Change-Id: I42d1e26dbc2fa9db8b7bd95ce449cb2bd93f93f8
This commit is contained in:
David Srbecky
2021-11-01 21:59:59 +00:00
parent fe9a8b11e7
commit 80547ae39d
3 changed files with 68 additions and 6 deletions

View File

@@ -32,6 +32,7 @@ def main():
group = parser.add_mutually_exclusive_group() group = parser.add_mutually_exclusive_group()
group.add_argument('--symbols-dir', '--syms', '--symdir', help='the symbols directory') group.add_argument('--symbols-dir', '--syms', '--symdir', help='the symbols directory')
group.add_argument('--symbols-zip', help='the symbols.zip file from a build') group.add_argument('--symbols-zip', help='the symbols.zip file from a build')
parser.add_argument('-v', '--verbose', action='store_true', help="include function parameters")
parser.add_argument('file', parser.add_argument('file',
metavar='FILE', metavar='FILE',
default='-', default='-',
@@ -52,6 +53,7 @@ def main():
with zipfile.ZipFile(args.symbols_zip) as zf: with zipfile.ZipFile(args.symbols_zip) as zf:
zf.extractall(tmp.name) zf.extractall(tmp.name)
symbol.SYMBOLS_DIR = glob.glob("%s/out/target/product/*/symbols" % tmp.name)[0] symbol.SYMBOLS_DIR = glob.glob("%s/out/target/product/*/symbols" % tmp.name)[0]
symbol.VERBOSE = args.verbose
if args.file == '-': if args.file == '-':
print("Reading native crash info from stdin") print("Reading native crash info from stdin")
f = sys.stdin f = sys.stdin

View File

@@ -498,12 +498,15 @@ class TraceConverter:
# display "a -> b -> c" in the stack trace instead of just "a -> c" # display "a -> b -> c" in the stack trace instead of just "a -> c"
info = symbol.SymbolInformation(lib, code_addr) info = symbol.SymbolInformation(lib, code_addr)
nest_count = len(info) - 1 nest_count = len(info) - 1
for (source_symbol, source_location, object_symbol_with_offset) in info: for (source_symbol, source_location, symbol_with_offset) in info:
if not source_symbol: if not source_symbol:
if symbol_present: if symbol_present:
source_symbol = symbol.CallCppFilt(symbol_name) source_symbol = symbol.CallCppFilt(symbol_name)
else: else:
source_symbol = "<unknown>" source_symbol = "<unknown>"
if not symbol.VERBOSE:
source_symbol = symbol.FormatSymbolWithoutParameters(source_symbol)
symbol_with_offset = symbol.FormatSymbolWithoutParameters(symbol_with_offset)
if not source_location: if not source_location:
source_location = area source_location = area
if lib_name: if lib_name:
@@ -515,11 +518,9 @@ class TraceConverter:
arrow = "v-------------->" arrow = "v-------------->"
self.trace_lines.append((arrow, source_symbol, source_location)) self.trace_lines.append((arrow, source_symbol, source_location))
else: else:
if not object_symbol_with_offset: if not symbol_with_offset:
object_symbol_with_offset = source_symbol symbol_with_offset = source_symbol
self.trace_lines.append((code_addr, self.trace_lines.append((code_addr, symbol_with_offset, source_location))
object_symbol_with_offset,
source_location))
if self.code_line.match(line): if self.code_line.match(line):
# Code lines should be ignored. If this were exluded the 'code around' # Code lines should be ignored. If this were exluded the 'code around'
# sections would trigger value_line matches. # sections would trigger value_line matches.

View File

@@ -60,6 +60,7 @@ SYMBOLS_DIR = FindSymbolsDir()
ARCH = None ARCH = None
VERBOSE = False
# These are private. Do not access them from other modules. # These are private. Do not access them from other modules.
_CACHED_TOOLCHAIN = None _CACHED_TOOLCHAIN = None
@@ -484,6 +485,37 @@ def FormatSymbolWithOffset(symbol, offset):
return symbol return symbol
return "%s+%d" % (symbol, offset) return "%s+%d" % (symbol, offset)
def FormatSymbolWithoutParameters(symbol):
"""Remove parameters from function.
Rather than trying to parse the demangled C++ signature,
it just removes matching top level parenthesis.
"""
if not symbol:
return symbol
result = symbol
result = result.replace(") const", ")") # Strip const keyword.
result = result.replace("operator<<", "operator\u00AB") # Avoid unmatched '<'.
result = result.replace("operator>>", "operator\u00BB") # Avoid unmatched '>'.
result = result.replace("operator->", "operator\u2192") # Avoid unmatched '>'.
nested = [] # Keeps tract of current nesting level of parenthesis.
for i in reversed(range(len(result))): # Iterate backward to make cutting easier.
c = result[i]
if c == ')' or c == '>':
if len(nested) == 0:
end = i + 1 # Mark the end of top-level pair.
nested.append(c)
if c == '(' or c == '<':
if len(nested) == 0 or {')':'(', '>':'<'}[nested.pop()] != c:
return symbol # Malformed: character does not match its pair.
if len(nested) == 0 and c == '(' and (end - i) > 2:
result = result[:i] + result[end:] # Remove substring (i, end).
if len(nested) > 0:
return symbol # Malformed: missing pair.
return result.strip()
def GetAbiFromToolchain(toolchain_var, bits): def GetAbiFromToolchain(toolchain_var, bits):
toolchain = os.environ.get(toolchain_var) toolchain = os.environ.get(toolchain_var)
@@ -748,5 +780,32 @@ class SetArchTests(unittest.TestCase):
"Could not determine arch from input, use --arch=XXX to specify it", "Could not determine arch from input, use --arch=XXX to specify it",
SetAbi, []) SetAbi, [])
class FormatSymbolWithoutParametersTests(unittest.TestCase):
def test_c(self):
self.assertEqual(FormatSymbolWithoutParameters("foo"), "foo")
self.assertEqual(FormatSymbolWithoutParameters("foo+42"), "foo+42")
def test_simple(self):
self.assertEqual(FormatSymbolWithoutParameters("foo(int i)"), "foo")
self.assertEqual(FormatSymbolWithoutParameters("foo(int i)+42"), "foo+42")
self.assertEqual(FormatSymbolWithoutParameters("bar::foo(int i)+42"), "bar::foo+42")
self.assertEqual(FormatSymbolWithoutParameters("operator()"), "operator()")
def test_templates(self):
self.assertEqual(FormatSymbolWithoutParameters("bar::foo<T>(vector<T>& v)"), "bar::foo<T>")
self.assertEqual(FormatSymbolWithoutParameters("bar<T>::foo(vector<T>& v)"), "bar<T>::foo")
self.assertEqual(FormatSymbolWithoutParameters("bar::foo<T>(vector<T<U>>& v)"), "bar::foo<T>")
self.assertEqual(FormatSymbolWithoutParameters("bar::foo<(EnumType)0>(vector<(EnumType)0>& v)"),
"bar::foo<(EnumType)0>")
def test_nested(self):
self.assertEqual(FormatSymbolWithoutParameters("foo(int i)::bar(int j)"), "foo::bar")
def test_unballanced(self):
self.assertEqual(FormatSymbolWithoutParameters("foo(bar(int i)"), "foo(bar(int i)")
self.assertEqual(FormatSymbolWithoutParameters("foo)bar(int i)"), "foo)bar(int i)")
self.assertEqual(FormatSymbolWithoutParameters("foo<bar(int i)"), "foo<bar(int i)")
self.assertEqual(FormatSymbolWithoutParameters("foo>bar(int i)"), "foo>bar(int i)")
if __name__ == '__main__': if __name__ == '__main__':
unittest.main(verbosity=2) unittest.main(verbosity=2)