From 80547ae39da37fee013d7e837b991c8694102ad5 Mon Sep 17 00:00:00 2001 From: David Srbecky Date: Mon, 1 Nov 2021 21:59:59 +0000 Subject: [PATCH] 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 --- scripts/stack | 2 ++ scripts/stack_core.py | 13 +++++----- scripts/symbol.py | 59 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 6 deletions(-) diff --git a/scripts/stack b/scripts/stack index 718f49c0e..70bc3fbbe 100755 --- a/scripts/stack +++ b/scripts/stack @@ -32,6 +32,7 @@ def main(): group = parser.add_mutually_exclusive_group() group.add_argument('--symbols-dir', '--syms', '--symdir', help='the symbols directory') 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', metavar='FILE', default='-', @@ -52,6 +53,7 @@ def main(): with zipfile.ZipFile(args.symbols_zip) as zf: zf.extractall(tmp.name) symbol.SYMBOLS_DIR = glob.glob("%s/out/target/product/*/symbols" % tmp.name)[0] + symbol.VERBOSE = args.verbose if args.file == '-': print("Reading native crash info from stdin") f = sys.stdin diff --git a/scripts/stack_core.py b/scripts/stack_core.py index 6e84db3fe..50d5c9425 100755 --- a/scripts/stack_core.py +++ b/scripts/stack_core.py @@ -498,12 +498,15 @@ class TraceConverter: # display "a -> b -> c" in the stack trace instead of just "a -> c" info = symbol.SymbolInformation(lib, code_addr) 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 symbol_present: source_symbol = symbol.CallCppFilt(symbol_name) else: source_symbol = "" + if not symbol.VERBOSE: + source_symbol = symbol.FormatSymbolWithoutParameters(source_symbol) + symbol_with_offset = symbol.FormatSymbolWithoutParameters(symbol_with_offset) if not source_location: source_location = area if lib_name: @@ -515,11 +518,9 @@ class TraceConverter: arrow = "v-------------->" self.trace_lines.append((arrow, source_symbol, source_location)) else: - if not object_symbol_with_offset: - object_symbol_with_offset = source_symbol - self.trace_lines.append((code_addr, - object_symbol_with_offset, - source_location)) + if not symbol_with_offset: + symbol_with_offset = source_symbol + self.trace_lines.append((code_addr, symbol_with_offset, source_location)) if self.code_line.match(line): # Code lines should be ignored. If this were exluded the 'code around' # sections would trigger value_line matches. diff --git a/scripts/symbol.py b/scripts/symbol.py index 5ec4e483e..0a255e819 100755 --- a/scripts/symbol.py +++ b/scripts/symbol.py @@ -60,6 +60,7 @@ SYMBOLS_DIR = FindSymbolsDir() ARCH = None +VERBOSE = False # These are private. Do not access them from other modules. _CACHED_TOOLCHAIN = None @@ -484,6 +485,37 @@ def FormatSymbolWithOffset(symbol, offset): return symbol 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): 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", 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(vector& v)"), "bar::foo") + self.assertEqual(FormatSymbolWithoutParameters("bar::foo(vector& v)"), "bar::foo") + self.assertEqual(FormatSymbolWithoutParameters("bar::foo(vector>& v)"), "bar::foo") + 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("foobar(int i)"), "foo>bar(int i)") + if __name__ == '__main__': unittest.main(verbosity=2)