diff options
Diffstat (limited to 'third_party/libwebrtc/build/android/stacktrace')
8 files changed, 695 insertions, 0 deletions
diff --git a/third_party/libwebrtc/build/android/stacktrace/BUILD.gn b/third_party/libwebrtc/build/android/stacktrace/BUILD.gn new file mode 100644 index 0000000000..ce13a15b4b --- /dev/null +++ b/third_party/libwebrtc/build/android/stacktrace/BUILD.gn @@ -0,0 +1,28 @@ +# Copyright 2017 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("//build/config/android/rules.gni") + +java_library("java_deobfuscate_java") { + sources = [ "java/org/chromium/build/FlushingReTrace.java" ] + + # Avoid using java_prebuilt() to ensure all uses go through the checked-in + # wrapper script. + input_jars_paths = [ + "//third_party/proguard/lib/proguard603.jar", + "//third_party/proguard/lib/retrace603.jar", + ] +} + +# Use the checked-in copy of the wrapper script & .jar rather than the built +# one to simplify usage of the tool. +group("java_deobfuscate") { + data = [ + "java_deobfuscate.py", + "java_deobfuscate.jar", + "//third_party/proguard/lib/proguard603.jar", + "//third_party/proguard/lib/retrace603.jar", + ] + deps = [ "//third_party/jdk:java_data" ] +} diff --git a/third_party/libwebrtc/build/android/stacktrace/README.md b/third_party/libwebrtc/build/android/stacktrace/README.md new file mode 100644 index 0000000000..58ea94be98 --- /dev/null +++ b/third_party/libwebrtc/build/android/stacktrace/README.md @@ -0,0 +1,28 @@ +# java_deobfuscate.py + +A wrapper around ProGuard's ReTrace tool, which: + +1) Updates the regular expression used to identify stack lines, and +2) Streams its output. + +The second point here is what allows you to run: + + adb logcat | build/android/stacktrace/java_deobfuscate.py out/Default/apks/ChromePublic.apk.mapping + +And have it actually show output without logcat terminating. + + +## Update Instructions: + + ninja -C out/Release java_deobfuscate + cp out/Release/lib.java/build/android/stacktrace/java_deobfuscate.jar build/android/stacktrace + +# stackwalker.py + +Extracts Breakpad microdumps from a log file and uses `stackwalker` to symbolize +them. + + +# crashpad_stackwalker.py + +Fetches Crashpad dumps from a given device, walks and symbolizes the stacks. diff --git a/third_party/libwebrtc/build/android/stacktrace/crashpad_stackwalker.py b/third_party/libwebrtc/build/android/stacktrace/crashpad_stackwalker.py new file mode 100755 index 0000000000..ab5dfe195c --- /dev/null +++ b/third_party/libwebrtc/build/android/stacktrace/crashpad_stackwalker.py @@ -0,0 +1,175 @@ +#!/usr/bin/env vpython3 +# +# Copyright 2019 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# Fetches Crashpad dumps from a given device, walks and symbolizes the stacks. +# All the non-trivial operations are performed by generate_breakpad_symbols.py, +# dump_syms, minidump_dump and minidump_stackwalk. + +import argparse +import logging +import os +import posixpath +import re +import sys +import shutil +import subprocess +import tempfile + +_BUILD_ANDROID_PATH = os.path.abspath( + os.path.join(os.path.dirname(__file__), '..')) +sys.path.append(_BUILD_ANDROID_PATH) +import devil_chromium +from devil.android import device_utils +from devil.utils import timeout_retry + + +def _CreateSymbolsDir(build_path, dynamic_library_names): + generator = os.path.normpath( + os.path.join(_BUILD_ANDROID_PATH, '..', '..', 'components', 'crash', + 'content', 'tools', 'generate_breakpad_symbols.py')) + syms_dir = os.path.join(build_path, 'crashpad_syms') + shutil.rmtree(syms_dir, ignore_errors=True) + os.mkdir(syms_dir) + for lib in dynamic_library_names: + unstripped_library_path = os.path.join(build_path, 'lib.unstripped', lib) + if not os.path.exists(unstripped_library_path): + continue + logging.info('Generating symbols for: %s', unstripped_library_path) + cmd = [ + generator, + '--symbols-dir', + syms_dir, + '--build-dir', + build_path, + '--binary', + unstripped_library_path, + '--platform', + 'android', + ] + return_code = subprocess.call(cmd) + if return_code != 0: + logging.error('Could not extract symbols, command failed: %s', + ' '.join(cmd)) + return syms_dir + + +def _ChooseLatestCrashpadDump(device, crashpad_dump_path): + if not device.PathExists(crashpad_dump_path): + logging.warning('Crashpad dump directory does not exist: %s', + crashpad_dump_path) + return None + latest = None + latest_timestamp = 0 + for crashpad_file in device.ListDirectory(crashpad_dump_path): + if crashpad_file.endswith('.dmp'): + stat = device.StatPath(posixpath.join(crashpad_dump_path, crashpad_file)) + current_timestamp = stat['st_mtime'] + if current_timestamp > latest_timestamp: + latest_timestamp = current_timestamp + latest = crashpad_file + return latest + + +def _ExtractLibraryNamesFromDump(build_path, dump_path): + default_library_name = 'libmonochrome.so' + dumper_path = os.path.join(build_path, 'minidump_dump') + if not os.access(dumper_path, os.X_OK): + logging.warning( + 'Cannot extract library name from dump because %s is not found, ' + 'default to: %s', dumper_path, default_library_name) + return [default_library_name] + p = subprocess.Popen([dumper_path, dump_path], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + stdout, stderr = p.communicate() + if p.returncode != 0: + # Dumper errors often do not affect stack walkability, just a warning. + logging.warning('Reading minidump failed with output:\n%s', stderr) + + library_names = [] + module_library_line_re = re.compile(r'[(]code_file[)]\s+= ' + r'"(?P<library_name>lib[^. ]+.so)"') + in_module = False + for line in stdout.splitlines(): + line = line.lstrip().rstrip('\n') + if line == 'MDRawModule': + in_module = True + continue + if line == '': + in_module = False + continue + if in_module: + m = module_library_line_re.match(line) + if m: + library_names.append(m.group('library_name')) + if not library_names: + logging.warning( + 'Could not find any library name in the dump, ' + 'default to: %s', default_library_name) + return [default_library_name] + return library_names + + +def main(): + logging.basicConfig(level=logging.INFO) + parser = argparse.ArgumentParser( + description='Fetches Crashpad dumps from a given device, ' + 'walks and symbolizes the stacks.') + parser.add_argument('--device', required=True, help='Device serial number') + parser.add_argument('--adb-path', help='Path to the "adb" command') + parser.add_argument( + '--build-path', + required=True, + help='Build output directory, equivalent to CHROMIUM_OUTPUT_DIR') + parser.add_argument( + '--chrome-cache-path', + required=True, + help='Directory on the device where Chrome stores cached files,' + ' crashpad stores dumps in a subdirectory of it') + args = parser.parse_args() + + stackwalk_path = os.path.join(args.build_path, 'minidump_stackwalk') + if not os.path.exists(stackwalk_path): + logging.error('Missing minidump_stackwalk executable') + return 1 + + devil_chromium.Initialize(output_directory=args.build_path, + adb_path=args.adb_path) + device = device_utils.DeviceUtils(args.device) + + device_crashpad_path = posixpath.join(args.chrome_cache_path, 'Crashpad', + 'pending') + + def CrashpadDumpExists(): + return _ChooseLatestCrashpadDump(device, device_crashpad_path) + + crashpad_file = timeout_retry.WaitFor( + CrashpadDumpExists, wait_period=1, max_tries=9) + if not crashpad_file: + logging.error('Could not locate a crashpad dump') + return 1 + + dump_dir = tempfile.mkdtemp() + symbols_dir = None + try: + device.PullFile( + device_path=posixpath.join(device_crashpad_path, crashpad_file), + host_path=dump_dir) + dump_full_path = os.path.join(dump_dir, crashpad_file) + library_names = _ExtractLibraryNamesFromDump(args.build_path, + dump_full_path) + symbols_dir = _CreateSymbolsDir(args.build_path, library_names) + stackwalk_cmd = [stackwalk_path, dump_full_path, symbols_dir] + subprocess.call(stackwalk_cmd) + finally: + shutil.rmtree(dump_dir, ignore_errors=True) + if symbols_dir: + shutil.rmtree(symbols_dir, ignore_errors=True) + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/third_party/libwebrtc/build/android/stacktrace/java/org/chromium/build/FlushingReTrace.java b/third_party/libwebrtc/build/android/stacktrace/java/org/chromium/build/FlushingReTrace.java new file mode 100644 index 0000000000..baa931328b --- /dev/null +++ b/third_party/libwebrtc/build/android/stacktrace/java/org/chromium/build/FlushingReTrace.java @@ -0,0 +1,116 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.build; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.LineNumberReader; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; + +import proguard.retrace.ReTrace; + +/** + * A wrapper around ReTrace that: + * 1. Hardcodes a more useful line regular expression + * 2. Disables output buffering + */ +public class FlushingReTrace { + // E.g.: D/ConnectivityService(18029): Message + // E.g.: W/GCM ( 151): Message + // E.g.: 09-08 14:22:59.995 18029 18055 I ProcessStatsService: Message + // E.g.: 09-08 14:30:59.145 17731 18020 D MDnsDS : Message + private static final String LOGCAT_PREFIX = + "(?:[VDIWEF]/.*?\\( *\\d+\\): |\\d\\d-\\d\\d [0-9:. ]+[VDIWEF] .*?: )?"; + + // Note: Order of these sub-patterns defines their precedence. + // Note: Deobfuscation of methods without the presense of line numbers basically never works. + // There is a test for these pattern at //build/android/stacktrace/java_deobfuscate_test.py + private static final String LINE_PARSE_REGEX = + // Eagerly match logcat prefix to avoid conflicting with the patterns below. + LOGCAT_PREFIX + + "(?:" + // Based on default ReTrace regex, but with whitespaces allowed in file:line parentheses + // and "at" changed to to allow : + // E.g.: 06-22 13:58:02.895 4674 4674 E THREAD_STATE: bLA.a( PG : 173 ) + // Normal stack trace lines look like: + // \tat org.chromium.chrome.browser.tab.Tab.handleJavaCrash(Tab.java:682) + + "(?:.*?(?::|\\bat)\\s+%c\\.%m\\s*\\(\\s*%s(?:\\s*:\\s*%l\\s*)?\\))|" + // E.g.: Caused by: java.lang.NullPointerException: Attempt to read from field 'int bLA' + // on a null object reference + + "(?:.*java\\.lang\\.NullPointerException.*[\"']%t\\s*%c\\.(?:%f|%m\\(%a\\))[\"'].*)|" + // E.g.: java.lang.VerifyError: bLA + + "(?:java\\.lang\\.VerifyError: %c)|" + // E.g.: java.lang.NoSuchFieldError: No instance field e of type L...; in class LbxK; + + "(?:java\\.lang\\.NoSuchFieldError: No instance field %f of type .*? in class L%C;)|" + // E.g.: Object of type Clazz was not destroyed... (See LifetimeAssert.java) + + "(?:.*?Object of type %c .*)|" + // E.g.: VFY: unable to resolve new-instance 3810 (LSome/Framework/Class;) in Lfoo/Bar; + + "(?:.*L%C;.*)|" + // E.g.: END SomeTestClass#someMethod + + "(?:.*?%c#%m.*?)|" + // Special-case for a common junit logcat message: + // E.g.: java.lang.NoClassDefFoundError: SomeFrameworkClass in isTestClass for Foo + + "(?:.* isTestClass for %c)|" + // E.g.: Caused by: java.lang.RuntimeException: Intentional Java Crash + + "(?:Caused by: %c:.*)|" + // Quoted values and lines that end with a class / class+method: + // E.g.: The class: Foo + // E.g.: INSTRUMENTATION_STATUS: class=Foo + // E.g.: NoClassDefFoundError: SomeFrameworkClass in isTestClass for Foo + // E.g.: Could not find class 'SomeFrameworkClass', referenced from method Foo.bar + // E.g.: Could not find method SomeFrameworkMethod, referenced from method Foo.bar + // E.g.: The member "Foo.bar" + // E.g.: The class "Foobar" + // Be careful about matching %c without %m since language tags look like class names. + + "(?:.*?%c\\.%m)|" + + "(?:.*?\"%c\\.%m\".*)|" + + "(?:.*\\b(?:[Cc]lass|[Tt]ype)\\b.*?\"%c\".*)|" + + "(?:.*\\b(?:[Cc]lass|[Tt]ype)\\b.*?%c)|" + // E.g.: java.lang.RuntimeException: Intentional Java Crash + + "(?:%c:.*)|" + // See if entire line matches a class name (e.g. for manual deobfuscation) + + "(?:%c)" + + ")"; + + private static void usage() { + System.err.println("Usage: echo $OBFUSCATED_CLASS | java_deobfuscate Foo.apk.mapping"); + System.err.println("Usage: java_deobfuscate Foo.apk.mapping < foo.log"); + System.err.println("Note: Deobfuscation of symbols outside the context of stack " + + "traces will work only when lines match the regular expression defined " + + "in FlushingReTrace.java."); + System.err.println("Also: Deobfuscation of method names without associated line " + + "numbers does not seem to work."); + System.exit(1); + } + + public static void main(String[] args) { + if (args.length != 1 || args[0].startsWith("-")) { + usage(); + } + + File mappingFile = new File(args[0]); + try { + LineNumberReader reader = new LineNumberReader( + new BufferedReader(new InputStreamReader(System.in, "UTF-8"))); + + // Enabling autoFlush is the main difference from ReTrace.main(). + boolean autoFlush = true; + PrintWriter writer = + new PrintWriter(new OutputStreamWriter(System.out, "UTF-8"), autoFlush); + + boolean verbose = false; + new ReTrace(LINE_PARSE_REGEX, verbose, mappingFile).retrace(reader, writer); + } catch (IOException ex) { + // Print a verbose stack trace. + ex.printStackTrace(); + System.exit(1); + } + + System.exit(0); + } +} diff --git a/third_party/libwebrtc/build/android/stacktrace/java_deobfuscate.jar b/third_party/libwebrtc/build/android/stacktrace/java_deobfuscate.jar Binary files differnew file mode 100644 index 0000000000..36a1b706a3 --- /dev/null +++ b/third_party/libwebrtc/build/android/stacktrace/java_deobfuscate.jar diff --git a/third_party/libwebrtc/build/android/stacktrace/java_deobfuscate.py b/third_party/libwebrtc/build/android/stacktrace/java_deobfuscate.py new file mode 100755 index 0000000000..8c231ecfcc --- /dev/null +++ b/third_party/libwebrtc/build/android/stacktrace/java_deobfuscate.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +# +# Copyright 2020 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +"""Wrapper script for java_deobfuscate. + +This is also a buildable target, but having it pre-built here simplifies usage. +""" + +import os +import sys + +DIR_SOURCE_ROOT = os.path.normpath( + os.path.join(os.path.dirname(__file__), '../../../')) + + +def main(): + classpath = [ + os.path.join(DIR_SOURCE_ROOT, 'build', 'android', 'stacktrace', + 'java_deobfuscate.jar'), + os.path.join(DIR_SOURCE_ROOT, 'third_party', 'proguard', 'lib', + 'proguard603.jar'), + os.path.join(DIR_SOURCE_ROOT, 'third_party', 'proguard', 'lib', + 'retrace603.jar'), + ] + java_path = os.path.join(DIR_SOURCE_ROOT, 'third_party', 'jdk', 'current', + 'bin', 'java') + + cmd = [ + java_path, '-classpath', ':'.join(classpath), + 'org.chromium.build.FlushingReTrace' + ] + cmd.extend(sys.argv[1:]) + os.execvp(cmd[0], cmd) + + +if __name__ == '__main__': + main() diff --git a/third_party/libwebrtc/build/android/stacktrace/java_deobfuscate_test.py b/third_party/libwebrtc/build/android/stacktrace/java_deobfuscate_test.py new file mode 100755 index 0000000000..d68323f129 --- /dev/null +++ b/third_party/libwebrtc/build/android/stacktrace/java_deobfuscate_test.py @@ -0,0 +1,172 @@ +#!/usr/bin/env vpython3 +# +# Copyright 2017 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +"""Tests for java_deobfuscate.""" + +import argparse +import os +import subprocess +import sys +import tempfile +import unittest + +# Set by command-line argument. +_JAVA_DEOBFUSCATE_PATH = None + +LINE_PREFIXES = [ + '', + # logcat -v threadtime + '09-08 14:38:35.535 18029 18084 E qcom_sensors_hal: ', + # logcat + 'W/GCM (15158): ', + 'W/GCM ( 158): ', +] + +TEST_MAP = """\ +this.was.Deobfuscated -> FOO: + int[] mFontFamily -> a + 1:3:void someMethod(int,android.os.Bundle):65:67 -> bar +never.Deobfuscated -> NOTFOO: + int[] mFontFamily -> a + 1:3:void someMethod(int,android.os.Bundle):65:67 -> bar +""" + +TEST_DATA = [ + '', + 'FOO', + 'FOO.bar', + 'Here is a FOO', + 'Here is a class FOO', + 'Here is a class FOO baz', + 'Here is a "FOO" baz', + 'Here is a type "FOO" baz', + 'Here is a "FOO.bar" baz', + 'SomeError: SomeFrameworkClass in isTestClass for FOO', + 'Here is a FOO.bar', + 'Here is a FOO.bar baz', + 'END FOO#bar', + 'new-instance 3810 (LSome/Framework/Class;) in LFOO;', + 'FOO: Error message', + 'Caused by: FOO: Error message', + '\tat FOO.bar(PG:1)', + '\t at\t FOO.bar\t (\t PG:\t 1\t )', + ('Unable to start activity ComponentInfo{garbage.in/here.test}:' + ' java.lang.NullPointerException: Attempt to invoke interface method' + ' \'void FOO.bar(int,android.os.Bundle)\' on a null object reference'), + ('Caused by: java.lang.NullPointerException: Attempt to read from field' + ' \'int[] FOO.a\' on a null object reference'), + 'java.lang.VerifyError: FOO', + ('java.lang.NoSuchFieldError: No instance field a of type ' + 'Ljava/lang/Class; in class LFOO;'), + 'NOTFOO: Object of type FOO was not destroyed...', +] + +EXPECTED_OUTPUT = [ + '', + 'this.was.Deobfuscated', + 'this.was.Deobfuscated.someMethod', + 'Here is a FOO', + 'Here is a class this.was.Deobfuscated', + 'Here is a class FOO baz', + 'Here is a "FOO" baz', + 'Here is a type "this.was.Deobfuscated" baz', + 'Here is a "this.was.Deobfuscated.someMethod" baz', + 'SomeError: SomeFrameworkClass in isTestClass for this.was.Deobfuscated', + 'Here is a this.was.Deobfuscated.someMethod', + 'Here is a FOO.bar baz', + 'END this.was.Deobfuscated#someMethod', + 'new-instance 3810 (LSome/Framework/Class;) in Lthis/was/Deobfuscated;', + 'this.was.Deobfuscated: Error message', + 'Caused by: this.was.Deobfuscated: Error message', + '\tat this.was.Deobfuscated.someMethod(Deobfuscated.java:65)', + ('\t at\t this.was.Deobfuscated.someMethod\t ' + '(\t Deobfuscated.java:\t 65\t )'), + ('Unable to start activity ComponentInfo{garbage.in/here.test}:' + ' java.lang.NullPointerException: Attempt to invoke interface method' + ' \'void this.was.Deobfuscated.someMethod(int,android.os.Bundle)\' on a' + ' null object reference'), + ('Caused by: java.lang.NullPointerException: Attempt to read from field' + ' \'int[] this.was.Deobfuscated.mFontFamily\' on a null object reference'), + 'java.lang.VerifyError: this.was.Deobfuscated', + ('java.lang.NoSuchFieldError: No instance field mFontFamily of type ' + 'Ljava/lang/Class; in class Lthis/was/Deobfuscated;'), + 'NOTFOO: Object of type this.was.Deobfuscated was not destroyed...', +] +TEST_DATA = [s + '\n' for s in TEST_DATA] +EXPECTED_OUTPUT = [s + '\n' for s in EXPECTED_OUTPUT] + + +class JavaDeobfuscateTest(unittest.TestCase): + + def __init__(self, *args, **kwargs): + super(JavaDeobfuscateTest, self).__init__(*args, **kwargs) + self._map_file = None + + def setUp(self): + self._map_file = tempfile.NamedTemporaryFile() + self._map_file.write(TEST_MAP) + self._map_file.flush() + + def tearDown(self): + if self._map_file: + self._map_file.close() + + def _testImpl(self, input_lines=None, expected_output_lines=None, + prefix=''): + self.assertTrue(bool(input_lines) == bool(expected_output_lines)) + + if not input_lines: + input_lines = [prefix + x for x in TEST_DATA] + if not expected_output_lines: + expected_output_lines = [prefix + x for x in EXPECTED_OUTPUT] + + cmd = [_JAVA_DEOBFUSCATE_PATH, self._map_file.name] + proc = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE) + proc_output, _ = proc.communicate(''.join(input_lines)) + actual_output_lines = proc_output.splitlines(True) + for actual, expected in zip(actual_output_lines, expected_output_lines): + self.assertTrue( + actual == expected or actual.replace('bar', 'someMethod') == expected, + msg=''.join([ + 'Deobfuscation failed.\n', + ' actual: %s' % actual, + ' expected: %s' % expected])) + + def testNoPrefix(self): + self._testImpl(prefix='') + + def testThreadtimePrefix(self): + self._testImpl(prefix='09-08 14:38:35.535 18029 18084 E qcom_sensors_hal: ') + + def testStandardPrefix(self): + self._testImpl(prefix='W/GCM (15158): ') + + def testStandardPrefixWithPadding(self): + self._testImpl(prefix='W/GCM ( 158): ') + + @unittest.skip('causes java_deobfuscate to hang, see crbug.com/876539') + def testIndefiniteHang(self): + # Test for crbug.com/876539. + self._testImpl( + input_lines=[ + 'VFY: unable to resolve virtual method 2: LFOO;' + + '.onDescendantInvalidated ' + + '(Landroid/view/View;Landroid/view/View;)V', + ], + expected_output_lines=[ + 'VFY: unable to resolve virtual method 2: Lthis.was.Deobfuscated;' + + '.onDescendantInvalidated ' + + '(Landroid/view/View;Landroid/view/View;)V', + ]) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('--java-deobfuscate-path', type=os.path.realpath, + required=True) + known_args, unittest_args = parser.parse_known_args() + _JAVA_DEOBFUSCATE_PATH = known_args.java_deobfuscate_path + unittest_args = [sys.argv[0]] + unittest_args + unittest.main(argv=unittest_args) diff --git a/third_party/libwebrtc/build/android/stacktrace/stackwalker.py b/third_party/libwebrtc/build/android/stacktrace/stackwalker.py new file mode 100755 index 0000000000..16b514901b --- /dev/null +++ b/third_party/libwebrtc/build/android/stacktrace/stackwalker.py @@ -0,0 +1,137 @@ +#!/usr/bin/env vpython3 +# +# Copyright 2016 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +from __future__ import print_function + +import argparse +import os +import re +import sys +import tempfile + +if __name__ == '__main__': + sys.path.append(os.path.join(os.path.dirname(__file__), '..')) +from pylib.constants import host_paths + +if host_paths.DEVIL_PATH not in sys.path: + sys.path.append(host_paths.DEVIL_PATH) +from devil.utils import cmd_helper + + +_MICRODUMP_BEGIN = re.compile( + '.*google-breakpad: -----BEGIN BREAKPAD MICRODUMP-----') +_MICRODUMP_END = re.compile( + '.*google-breakpad: -----END BREAKPAD MICRODUMP-----') + +""" Example Microdump +<timestamp> 6270 6131 F google-breakpad: -----BEGIN BREAKPAD MICRODUMP----- +<timestamp> 6270 6131 F google-breakpad: V Chrome_Android:54.0.2790.0 +... +<timestamp> 6270 6131 F google-breakpad: -----END BREAKPAD MICRODUMP----- + +""" + + +def GetMicroDumps(dump_path): + """Returns all microdumps found in given log file + + Args: + dump_path: Path to the log file. + + Returns: + List of all microdumps as lists of lines. + """ + with open(dump_path, 'r') as d: + data = d.read() + all_dumps = [] + current_dump = None + for line in data.splitlines(): + if current_dump is not None: + if _MICRODUMP_END.match(line): + current_dump.append(line) + all_dumps.append(current_dump) + current_dump = None + else: + current_dump.append(line) + elif _MICRODUMP_BEGIN.match(line): + current_dump = [] + current_dump.append(line) + return all_dumps + + +def SymbolizeMicroDump(stackwalker_binary_path, dump, symbols_path): + """Runs stackwalker on microdump. + + Runs the stackwalker binary at stackwalker_binary_path on a given microdump + using the symbols at symbols_path. + + Args: + stackwalker_binary_path: Path to the stackwalker binary. + dump: The microdump to run the stackwalker on. + symbols_path: Path the the symbols file to use. + + Returns: + Output from stackwalker tool. + """ + with tempfile.NamedTemporaryFile() as tf: + for l in dump: + tf.write('%s\n' % l) + cmd = [stackwalker_binary_path, tf.name, symbols_path] + return cmd_helper.GetCmdOutput(cmd) + + +def AddArguments(parser): + parser.add_argument('--stackwalker-binary-path', required=True, + help='Path to stackwalker binary.') + parser.add_argument('--stack-trace-path', required=True, + help='Path to stacktrace containing microdump.') + parser.add_argument('--symbols-path', required=True, + help='Path to symbols file.') + parser.add_argument('--output-file', + help='Path to dump stacktrace output to') + + +def _PrintAndLog(line, fp): + if fp: + fp.write('%s\n' % line) + print(line) + + +def main(): + parser = argparse.ArgumentParser() + AddArguments(parser) + args = parser.parse_args() + + micro_dumps = GetMicroDumps(args.stack_trace_path) + if not micro_dumps: + print('No microdump found. Exiting.') + return 0 + + symbolized_dumps = [] + for micro_dump in micro_dumps: + symbolized_dumps.append(SymbolizeMicroDump( + args.stackwalker_binary_path, micro_dump, args.symbols_path)) + + try: + fp = open(args.output_file, 'w') if args.output_file else None + _PrintAndLog('%d microdumps found.' % len(micro_dumps), fp) + _PrintAndLog('---------- Start output from stackwalker ----------', fp) + for index, symbolized_dump in list(enumerate(symbolized_dumps)): + _PrintAndLog( + '------------------ Start dump %d ------------------' % index, fp) + _PrintAndLog(symbolized_dump, fp) + _PrintAndLog( + '------------------- End dump %d -------------------' % index, fp) + _PrintAndLog('----------- End output from stackwalker -----------', fp) + except Exception: + if fp: + fp.close() + raise + return 0 + + +if __name__ == '__main__': + sys.exit(main()) |