summaryrefslogtreecommitdiffstats
path: root/third_party/libwebrtc/build/android/pylib/symbols
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/libwebrtc/build/android/pylib/symbols')
-rw-r--r--third_party/libwebrtc/build/android/pylib/symbols/__init__.py0
-rwxr-xr-xthird_party/libwebrtc/build/android/pylib/symbols/apk_lib_dump.py61
-rw-r--r--third_party/libwebrtc/build/android/pylib/symbols/apk_native_libs.py419
-rwxr-xr-xthird_party/libwebrtc/build/android/pylib/symbols/apk_native_libs_unittest.py397
-rw-r--r--third_party/libwebrtc/build/android/pylib/symbols/deobfuscator.py178
-rw-r--r--third_party/libwebrtc/build/android/pylib/symbols/elf_symbolizer.py497
-rwxr-xr-xthird_party/libwebrtc/build/android/pylib/symbols/elf_symbolizer_unittest.py196
-rw-r--r--third_party/libwebrtc/build/android/pylib/symbols/mock_addr2line/__init__.py0
-rwxr-xr-xthird_party/libwebrtc/build/android/pylib/symbols/mock_addr2line/mock_addr2line81
-rw-r--r--third_party/libwebrtc/build/android/pylib/symbols/stack_symbolizer.py86
-rw-r--r--third_party/libwebrtc/build/android/pylib/symbols/symbol_utils.py813
-rwxr-xr-xthird_party/libwebrtc/build/android/pylib/symbols/symbol_utils_unittest.py942
12 files changed, 3670 insertions, 0 deletions
diff --git a/third_party/libwebrtc/build/android/pylib/symbols/__init__.py b/third_party/libwebrtc/build/android/pylib/symbols/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/third_party/libwebrtc/build/android/pylib/symbols/__init__.py
diff --git a/third_party/libwebrtc/build/android/pylib/symbols/apk_lib_dump.py b/third_party/libwebrtc/build/android/pylib/symbols/apk_lib_dump.py
new file mode 100755
index 0000000000..f40c7581fc
--- /dev/null
+++ b/third_party/libwebrtc/build/android/pylib/symbols/apk_lib_dump.py
@@ -0,0 +1,61 @@
+#!/usr/bin/env python3
+
+# Copyright 2018 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.
+
+"""Dump shared library information from an APK file.
+
+This script is used to dump which *uncompressed* native shared libraries an
+APK contains, as well as their position within the file. This is mostly useful
+to diagnose logcat and tombstone symbolization issues when the libraries are
+loaded directly from the APK at runtime.
+
+The default format will print one line per uncompressed shared library with the
+following format:
+
+ 0x<start-offset> 0x<end-offset> 0x<file-size> <file-path>
+
+The --format=python option can be used to dump the same information that is
+easy to use in a Python script, e.g. with a line like:
+
+ (0x<start-offset>, 0x<end-offset>, 0x<file-size>, <file-path>),
+"""
+
+
+
+import argparse
+import os
+import sys
+
+sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..'))
+
+from pylib.symbols import apk_native_libs
+
+def main():
+ parser = argparse.ArgumentParser(
+ description=__doc__,
+ formatter_class=argparse.RawDescriptionHelpFormatter)
+
+ parser.add_argument('apk', help='Input APK file path.')
+
+ parser.add_argument('--format', help='Select output format',
+ default='default', choices=['default', 'python'])
+
+ args = parser.parse_args()
+
+ apk_reader = apk_native_libs.ApkReader(args.apk)
+ lib_map = apk_native_libs.ApkNativeLibraries(apk_reader)
+ for lib_path, file_offset, file_size in lib_map.GetDumpList():
+ if args.format == 'python':
+ print('(0x%08x, 0x%08x, 0x%08x, \'%s\'),' %
+ (file_offset, file_offset + file_size, file_size, lib_path))
+ else:
+ print('0x%08x 0x%08x 0x%08x %s' % (file_offset, file_offset + file_size,
+ file_size, lib_path))
+
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/third_party/libwebrtc/build/android/pylib/symbols/apk_native_libs.py b/third_party/libwebrtc/build/android/pylib/symbols/apk_native_libs.py
new file mode 100644
index 0000000000..59b303990b
--- /dev/null
+++ b/third_party/libwebrtc/build/android/pylib/symbols/apk_native_libs.py
@@ -0,0 +1,419 @@
+# Copyright 2018 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 logging
+import os
+import re
+import struct
+import zipfile
+
+# The default zipfile python module cannot open APKs properly, but this
+# fixes it. Note that simply importing this file is sufficient to
+# ensure that zip works correctly for all other modules. See:
+# http://bugs.python.org/issue14315
+# https://hg.python.org/cpython/rev/6dd5e9556a60#l2.8
+def _PatchZipFile():
+ # pylint: disable=protected-access
+ oldDecodeExtra = zipfile.ZipInfo._decodeExtra
+ def decodeExtra(self):
+ try:
+ oldDecodeExtra(self)
+ except struct.error:
+ pass
+ zipfile.ZipInfo._decodeExtra = decodeExtra
+_PatchZipFile()
+
+
+class ApkZipInfo(object):
+ """Models a single file entry from an ApkReader.
+
+ This is very similar to the zipfile.ZipInfo class. It provides a few
+ properties describing the entry:
+ - filename (same as ZipInfo.filename)
+ - file_size (same as ZipInfo.file_size)
+ - compress_size (same as ZipInfo.file_size)
+ - file_offset (note: not provided by ZipInfo)
+
+ And a few useful methods: IsCompressed() and IsElfFile().
+
+ Entries can be created by using ApkReader() methods.
+ """
+ def __init__(self, zip_file, zip_info):
+ """Construct instance. Do not call this directly. Use ApkReader methods."""
+ self._file = zip_file
+ self._info = zip_info
+ self._file_offset = None
+
+ @property
+ def filename(self):
+ """Entry's file path within APK."""
+ return self._info.filename
+
+ @property
+ def file_size(self):
+ """Entry's extracted file size in bytes."""
+ return self._info.file_size
+
+ @property
+ def compress_size(self):
+ """Entry' s compressed file size in bytes."""
+ return self._info.compress_size
+
+ @property
+ def file_offset(self):
+ """Entry's starting file offset in the APK."""
+ if self._file_offset is None:
+ self._file_offset = self._ZipFileOffsetFromLocalHeader(
+ self._file.fp, self._info.header_offset)
+ return self._file_offset
+
+ def __repr__(self):
+ """Convert to string for debugging."""
+ return 'ApkZipInfo["%s",size=0x%x,compressed=0x%x,offset=0x%x]' % (
+ self.filename, self.file_size, self.compress_size, self.file_offset)
+
+ def IsCompressed(self):
+ """Returns True iff the entry is compressed."""
+ return self._info.compress_type != zipfile.ZIP_STORED
+
+ def IsElfFile(self):
+ """Returns True iff the entry is an ELF file."""
+ with self._file.open(self._info, 'r') as f:
+ return f.read(4) == '\x7fELF'
+
+ @staticmethod
+ def _ZipFileOffsetFromLocalHeader(fd, local_header_offset):
+ """Return a file's start offset from its zip archive local header.
+
+ Args:
+ fd: Input file object.
+ local_header_offset: Local header offset (from its ZipInfo entry).
+ Returns:
+ file start offset.
+ """
+ FILE_NAME_LEN_OFFSET = 26
+ FILE_NAME_OFFSET = 30
+ fd.seek(local_header_offset + FILE_NAME_LEN_OFFSET)
+ file_name_len = struct.unpack('H', fd.read(2))[0]
+ extra_field_len = struct.unpack('H', fd.read(2))[0]
+ file_offset = (local_header_offset + FILE_NAME_OFFSET +
+ file_name_len + extra_field_len)
+ return file_offset
+
+
+class ApkReader(object):
+ """A convenience class used to read the content of APK files.
+
+ Its design is very similar to the one from zipfile.ZipFile, except
+ that its returns ApkZipInfo entries which provide a |file_offset|
+ property that can be used to know where a given file is located inside
+ the archive.
+
+ It is also easy to mock for unit-testing (see MockApkReader in
+ apk_utils_unittest.py) without creating any files on disk.
+
+ Usage is the following:
+ - Create an instance using a with statement (for proper unit-testing).
+ - Call ListEntries() to list all entries in the archive. This returns
+ a list of ApkZipInfo entries.
+ - Or call FindEntry() corresponding to a given path within the archive.
+
+ For example:
+ with ApkReader(input_apk_path) as reader:
+ info = reader.FindEntry('lib/armeabi-v7a/libfoo.so')
+ if info.IsCompressed() or not info.IsElfFile():
+ raise Exception('Invalid library path")
+
+ The ApkZipInfo can be used to inspect the entry's metadata, or read its
+ content with the ReadAll() method. See its documentation for all details.
+ """
+ def __init__(self, apk_path):
+ """Initialize instance."""
+ self._zip_file = zipfile.ZipFile(apk_path, 'r')
+ self._path = apk_path
+
+ def __enter__(self):
+ """Python context manager entry."""
+ return self
+
+ def __exit__(self, *kwargs):
+ """Python context manager exit."""
+ self.Close()
+
+ @property
+ def path(self):
+ """The corresponding input APK path."""
+ return self._path
+
+ def Close(self):
+ """Close the reader (and underlying ZipFile instance)."""
+ self._zip_file.close()
+
+ def ListEntries(self):
+ """Return a list of ApkZipInfo entries for this APK."""
+ result = []
+ for info in self._zip_file.infolist():
+ result.append(ApkZipInfo(self._zip_file, info))
+ return result
+
+ def FindEntry(self, file_path):
+ """Return an ApkZipInfo instance for a given archive file path.
+
+ Args:
+ file_path: zip file path.
+ Return:
+ A new ApkZipInfo entry on success.
+ Raises:
+ KeyError on failure (entry not found).
+ """
+ info = self._zip_file.getinfo(file_path)
+ return ApkZipInfo(self._zip_file, info)
+
+
+
+class ApkNativeLibraries(object):
+ """A class for the list of uncompressed shared libraries inside an APK.
+
+ Create a new instance by passing the path to an input APK, then use
+ the FindLibraryByOffset() method to find the native shared library path
+ corresponding to a given file offset.
+
+ GetAbiList() and GetLibrariesList() can also be used to inspect
+ the state of the instance.
+ """
+ def __init__(self, apk_reader):
+ """Initialize instance.
+
+ Args:
+ apk_reader: An ApkReader instance corresponding to the input APK.
+ """
+ self._native_libs = []
+ for entry in apk_reader.ListEntries():
+ # Chromium uses so-called 'placeholder' native shared libraries
+ # that have a size of 0, and are only used to deal with bugs in
+ # older Android system releases (they are never loaded and cannot
+ # appear in stack traces). Ignore these here to avoid generating
+ # confusing results.
+ if entry.file_size == 0:
+ continue
+
+ # Only uncompressed libraries can appear in stack traces.
+ if entry.IsCompressed():
+ continue
+
+ # Only consider files within lib/ and with a filename ending with .so
+ # at the moment. NOTE: Do not require a 'lib' prefix, since that would
+ # prevent finding the 'crazy.libXXX.so' libraries used by Chromium.
+ if (not entry.filename.startswith('lib/') or
+ not entry.filename.endswith('.so')):
+ continue
+
+ lib_path = entry.filename
+
+ self._native_libs.append(
+ (lib_path, entry.file_offset, entry.file_offset + entry.file_size))
+
+ def IsEmpty(self):
+ """Return true iff the list is empty."""
+ return not bool(self._native_libs)
+
+ def GetLibraries(self):
+ """Return the list of all library paths in this instance."""
+ return sorted([x[0] for x in self._native_libs])
+
+ def GetDumpList(self):
+ """Retrieve full library map.
+
+ Returns:
+ A list of (lib_path, file_offset, file_size) tuples, sorted
+ in increasing |file_offset| values.
+ """
+ result = []
+ for entry in self._native_libs:
+ lib_path, file_start, file_end = entry
+ result.append((lib_path, file_start, file_end - file_start))
+
+ return sorted(result, key=lambda x: x[1])
+
+ def FindLibraryByOffset(self, file_offset):
+ """Find the native library at a given file offset.
+
+ Args:
+ file_offset: File offset within the original APK.
+ Returns:
+ Returns a (lib_path, lib_offset) tuple on success, or (None, 0)
+ on failure. Note that lib_path will omit the 'lib/$ABI/' prefix,
+ lib_offset is the adjustment of file_offset within the library.
+ """
+ for lib_path, start_offset, end_offset in self._native_libs:
+ if file_offset >= start_offset and file_offset < end_offset:
+ return (lib_path, file_offset - start_offset)
+
+ return (None, 0)
+
+
+class ApkLibraryPathTranslator(object):
+ """Translates APK file paths + byte offsets into library path + offset.
+
+ The purpose of this class is to translate a native shared library path
+ that points to an APK into a new device-specific path that points to a
+ native shared library, as if it was installed there. E.g.:
+
+ ('/data/data/com.example.app-1/base.apk', 0x123be00)
+
+ would be translated into:
+
+ ('/data/data/com.example.app-1/base.apk!lib/libfoo.so', 0x3be00)
+
+ If the original APK (installed as base.apk) contains an uncompressed shared
+ library under lib/armeabi-v7a/libfoo.so at offset 0x120000.
+
+ Note that the virtual device path after the ! doesn't necessarily match
+ the path inside the .apk. This doesn't really matter for the rest of
+ the symbolization functions since only the file's base name can be used
+ to find the corresponding file on the host.
+
+ Usage is the following:
+
+ 1/ Create new instance.
+
+ 2/ Call AddHostApk() one or several times to add the host path
+ of an APK, its package name, and device-installed named.
+
+ 3/ Call TranslatePath() to translate a (path, offset) tuple corresponding
+ to an on-device APK, into the corresponding virtual device library
+ path and offset.
+ """
+
+ # Depending on the version of the system, a non-system APK might be installed
+ # on a path that looks like the following:
+ #
+ # * /data/..../<package_name>-<number>.apk, where <number> is used to
+ # distinguish several versions of the APK during package updates.
+ #
+ # * /data/..../<package_name>-<suffix>/base.apk, where <suffix> is a
+ # string of random ASCII characters following the dash after the
+ # package name. This serves as a way to distinguish the installation
+ # paths during package update, and randomize its final location
+ # (to prevent apps from hard-coding the paths to other apps).
+ #
+ # Note that the 'base.apk' name comes from the system.
+ #
+ # * /data/.../<package_name>-<suffix>/<split_name>.apk, where <suffix>
+ # is the same as above, and <split_name> is the name of am app bundle
+ # split APK.
+ #
+ # System APKs are installed on paths that look like /system/app/Foo.apk
+ # but this class ignores them intentionally.
+
+ # Compiler regular expression for the first format above.
+ _RE_APK_PATH_1 = re.compile(
+ r'/data/.*/(?P<package_name>[A-Za-z0-9_.]+)-(?P<version>[0-9]+)\.apk')
+
+ # Compiled regular expression for the second and third formats above.
+ _RE_APK_PATH_2 = re.compile(
+ r'/data/.*/(?P<package_name>[A-Za-z0-9_.]+)-(?P<suffix>[^/]+)/' +
+ r'(?P<apk_name>.+\.apk)')
+
+ def __init__(self):
+ """Initialize instance. Call AddHostApk() to add host apk file paths."""
+ self._path_map = {} # Maps (package_name, apk_name) to host-side APK path.
+ self._libs_map = {} # Maps APK host path to ApkNativeLibrariesMap instance.
+
+ def AddHostApk(self, package_name, native_libs, device_apk_name=None):
+ """Add a file path to the host APK search list.
+
+ Args:
+ package_name: Corresponding apk package name.
+ native_libs: ApkNativeLibraries instance for the corresponding APK.
+ device_apk_name: Optional expected name of the installed APK on the
+ device. This is only useful when symbolizing app bundle that run on
+ Android L+. I.e. it will be ignored in other cases.
+ """
+ if native_libs.IsEmpty():
+ logging.debug('Ignoring host APK without any uncompressed native ' +
+ 'libraries: %s', device_apk_name)
+ return
+
+ # If the APK name is not provided, use the default of 'base.apk'. This
+ # will be ignored if we find <package_name>-<number>.apk file paths
+ # in the input, but will work properly for Android L+, as long as we're
+ # not using Android app bundles.
+ device_apk_name = device_apk_name or 'base.apk'
+
+ key = "%s/%s" % (package_name, device_apk_name)
+ if key in self._libs_map:
+ raise KeyError('There is already an APK associated with (%s)' % key)
+
+ self._libs_map[key] = native_libs
+
+ @staticmethod
+ def _MatchApkDeviceInstallPath(apk_path):
+ """Check whether a given path matches an installed APK device file path.
+
+ Args:
+ apk_path: Device-specific file path.
+ Returns:
+ On success, a (package_name, apk_name) tuple. On failure, (None. None).
+ """
+ m = ApkLibraryPathTranslator._RE_APK_PATH_1.match(apk_path)
+ if m:
+ return (m.group('package_name'), 'base.apk')
+
+ m = ApkLibraryPathTranslator._RE_APK_PATH_2.match(apk_path)
+ if m:
+ return (m.group('package_name'), m.group('apk_name'))
+
+ return (None, None)
+
+ def TranslatePath(self, apk_path, apk_offset):
+ """Translate a potential apk file path + offset into library path + offset.
+
+ Args:
+ apk_path: Library or apk file path on the device (e.g.
+ '/data/data/com.example.app-XSAHKSJH/base.apk').
+ apk_offset: Byte offset within the library or apk.
+
+ Returns:
+ a new (lib_path, lib_offset) tuple. If |apk_path| points to an APK,
+ then this function searches inside the corresponding host-side APKs
+ (added with AddHostApk() above) for the corresponding uncompressed
+ native shared library at |apk_offset|, if found, this returns a new
+ device-specific path corresponding to a virtual installation of said
+ library with an adjusted offset.
+
+ Otherwise, just return the original (apk_path, apk_offset) values.
+ """
+ if not apk_path.endswith('.apk'):
+ return (apk_path, apk_offset)
+
+ apk_package, apk_name = self._MatchApkDeviceInstallPath(apk_path)
+ if not apk_package:
+ return (apk_path, apk_offset)
+
+ key = '%s/%s' % (apk_package, apk_name)
+ native_libs = self._libs_map.get(key)
+ if not native_libs:
+ logging.debug('Unknown %s package', key)
+ return (apk_path, apk_offset)
+
+ lib_name, new_offset = native_libs.FindLibraryByOffset(apk_offset)
+ if not lib_name:
+ logging.debug('Invalid offset in %s.apk package: %d', key, apk_offset)
+ return (apk_path, apk_offset)
+
+ lib_name = os.path.basename(lib_name)
+
+ # Some libraries are stored with a crazy. prefix inside the APK, this
+ # is done to prevent the PackageManager from extracting the libraries
+ # at installation time when running on pre Android M systems, where the
+ # system linker cannot load libraries directly from APKs.
+ crazy_prefix = 'crazy.'
+ if lib_name.startswith(crazy_prefix):
+ lib_name = lib_name[len(crazy_prefix):]
+
+ # Put this in a fictional lib sub-directory for good measure.
+ new_path = '%s!lib/%s' % (apk_path, lib_name)
+
+ return (new_path, new_offset)
diff --git a/third_party/libwebrtc/build/android/pylib/symbols/apk_native_libs_unittest.py b/third_party/libwebrtc/build/android/pylib/symbols/apk_native_libs_unittest.py
new file mode 100755
index 0000000000..59f7e2b02a
--- /dev/null
+++ b/third_party/libwebrtc/build/android/pylib/symbols/apk_native_libs_unittest.py
@@ -0,0 +1,397 @@
+#!/usr/bin/env vpython3
+# Copyright 2018 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 logging
+import unittest
+
+from pylib.symbols import apk_native_libs
+
+# Mock ELF-like data
+MOCK_ELF_DATA = '\x7fELFFFFFFFFFFFFFFFF'
+
+class MockApkZipInfo(object):
+ """A mock ApkZipInfo class, returned by MockApkReaderFactory instances."""
+ def __init__(self, filename, file_size, compress_size, file_offset,
+ file_data):
+ self.filename = filename
+ self.file_size = file_size
+ self.compress_size = compress_size
+ self.file_offset = file_offset
+ self._data = file_data
+
+ def __repr__(self):
+ """Convert to string for debugging."""
+ return 'MockApkZipInfo["%s",size=%d,compressed=%d,offset=%d]' % (
+ self.filename, self.file_size, self.compress_size, self.file_offset)
+
+ def IsCompressed(self):
+ """Returns True iff the entry is compressed."""
+ return self.file_size != self.compress_size
+
+ def IsElfFile(self):
+ """Returns True iff the entry is an ELF file."""
+ if not self._data or len(self._data) < 4:
+ return False
+
+ return self._data[0:4] == '\x7fELF'
+
+
+class MockApkReader(object):
+ """A mock ApkReader instance used during unit-testing.
+
+ Do not use directly, but use a MockApkReaderFactory context, as in:
+
+ with MockApkReaderFactory() as mock:
+ mock.AddTestEntry(file_path, file_size, compress_size, file_data)
+ ...
+
+ # Actually returns the mock instance.
+ apk_reader = apk_native_libs.ApkReader('/some/path.apk')
+ """
+ def __init__(self, apk_path='test.apk'):
+ """Initialize instance."""
+ self._entries = []
+ self._fake_offset = 0
+ self._path = apk_path
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, *kwarg):
+ self.Close()
+ return
+
+ @property
+ def path(self):
+ return self._path
+
+ def AddTestEntry(self, filepath, file_size, compress_size, file_data):
+ """Add a new entry to the instance for unit-tests.
+
+ Do not call this directly, use the AddTestEntry() method on the parent
+ MockApkReaderFactory instance.
+
+ Args:
+ filepath: archive file path.
+ file_size: uncompressed file size in bytes.
+ compress_size: compressed size in bytes.
+ file_data: file data to be checked by IsElfFile()
+
+ Note that file_data can be None, or that its size can be actually
+ smaller than |compress_size| when used during unit-testing.
+ """
+ self._entries.append(MockApkZipInfo(filepath, file_size, compress_size,
+ self._fake_offset, file_data))
+ self._fake_offset += compress_size
+
+ def Close(self): # pylint: disable=no-self-use
+ """Close this reader instance."""
+ return
+
+ def ListEntries(self):
+ """Return a list of MockApkZipInfo instances for this input APK."""
+ return self._entries
+
+ def FindEntry(self, file_path):
+ """Find the MockApkZipInfo instance corresponds to a given file path."""
+ for entry in self._entries:
+ if entry.filename == file_path:
+ return entry
+ raise KeyError('Could not find mock zip archive member for: ' + file_path)
+
+
+class MockApkReaderTest(unittest.TestCase):
+
+ def testEmpty(self):
+ with MockApkReader() as reader:
+ entries = reader.ListEntries()
+ self.assertTrue(len(entries) == 0)
+ with self.assertRaises(KeyError):
+ reader.FindEntry('non-existent-entry.txt')
+
+ def testSingleEntry(self):
+ with MockApkReader() as reader:
+ reader.AddTestEntry('some-path/some-file', 20000, 12345, file_data=None)
+ entries = reader.ListEntries()
+ self.assertTrue(len(entries) == 1)
+ entry = entries[0]
+ self.assertEqual(entry.filename, 'some-path/some-file')
+ self.assertEqual(entry.file_size, 20000)
+ self.assertEqual(entry.compress_size, 12345)
+ self.assertTrue(entry.IsCompressed())
+
+ entry2 = reader.FindEntry('some-path/some-file')
+ self.assertEqual(entry, entry2)
+
+ def testMultipleEntries(self):
+ with MockApkReader() as reader:
+ _ENTRIES = {
+ 'foo.txt': (1024, 1024, 'FooFooFoo'),
+ 'lib/bar/libcode.so': (16000, 3240, 1024, '\x7fELFFFFFFFFFFFF'),
+ }
+ for path, props in _ENTRIES.items():
+ reader.AddTestEntry(path, props[0], props[1], props[2])
+
+ entries = reader.ListEntries()
+ self.assertEqual(len(entries), len(_ENTRIES))
+ for path, props in _ENTRIES.items():
+ entry = reader.FindEntry(path)
+ self.assertEqual(entry.filename, path)
+ self.assertEqual(entry.file_size, props[0])
+ self.assertEqual(entry.compress_size, props[1])
+
+
+class ApkNativeLibrariesTest(unittest.TestCase):
+
+ def setUp(self):
+ logging.getLogger().setLevel(logging.ERROR)
+
+ def testEmptyApk(self):
+ with MockApkReader() as reader:
+ libs_map = apk_native_libs.ApkNativeLibraries(reader)
+ self.assertTrue(libs_map.IsEmpty())
+ self.assertEqual(len(libs_map.GetLibraries()), 0)
+ lib_path, lib_offset = libs_map.FindLibraryByOffset(0)
+ self.assertIsNone(lib_path)
+ self.assertEqual(lib_offset, 0)
+
+ def testSimpleApk(self):
+ with MockApkReader() as reader:
+ _MOCK_ENTRIES = [
+ # Top-level library should be ignored.
+ ('libfoo.so', 1000, 1000, MOCK_ELF_DATA, False),
+ # Library not under lib/ should be ignored.
+ ('badlib/test-abi/libfoo2.so', 1001, 1001, MOCK_ELF_DATA, False),
+ # Library under lib/<abi>/ but without .so extension should be ignored.
+ ('lib/test-abi/libfoo4.so.1', 1003, 1003, MOCK_ELF_DATA, False),
+ # Library under lib/<abi>/ with .so suffix, but compressed -> ignored.
+ ('lib/test-abi/libfoo5.so', 1004, 1003, MOCK_ELF_DATA, False),
+ # First correct library
+ ('lib/test-abi/libgood1.so', 1005, 1005, MOCK_ELF_DATA, True),
+ # Second correct library: support sub-directories
+ ('lib/test-abi/subdir/libgood2.so', 1006, 1006, MOCK_ELF_DATA, True),
+ # Third correct library, no lib prefix required
+ ('lib/test-abi/crazy.libgood3.so', 1007, 1007, MOCK_ELF_DATA, True),
+ ]
+ file_offsets = []
+ prev_offset = 0
+ for ent in _MOCK_ENTRIES:
+ reader.AddTestEntry(ent[0], ent[1], ent[2], ent[3])
+ file_offsets.append(prev_offset)
+ prev_offset += ent[2]
+
+ libs_map = apk_native_libs.ApkNativeLibraries(reader)
+ self.assertFalse(libs_map.IsEmpty())
+ self.assertEqual(libs_map.GetLibraries(), [
+ 'lib/test-abi/crazy.libgood3.so',
+ 'lib/test-abi/libgood1.so',
+ 'lib/test-abi/subdir/libgood2.so',
+ ])
+
+ BIAS = 10
+ for mock_ent, file_offset in zip(_MOCK_ENTRIES, file_offsets):
+ if mock_ent[4]:
+ lib_path, lib_offset = libs_map.FindLibraryByOffset(
+ file_offset + BIAS)
+ self.assertEqual(lib_path, mock_ent[0])
+ self.assertEqual(lib_offset, BIAS)
+
+
+ def testMultiAbiApk(self):
+ with MockApkReader() as reader:
+ _MOCK_ENTRIES = [
+ ('lib/abi1/libfoo.so', 1000, 1000, MOCK_ELF_DATA),
+ ('lib/abi2/libfoo.so', 1000, 1000, MOCK_ELF_DATA),
+ ]
+ for ent in _MOCK_ENTRIES:
+ reader.AddTestEntry(ent[0], ent[1], ent[2], ent[3])
+
+ libs_map = apk_native_libs.ApkNativeLibraries(reader)
+ self.assertFalse(libs_map.IsEmpty())
+ self.assertEqual(libs_map.GetLibraries(), [
+ 'lib/abi1/libfoo.so', 'lib/abi2/libfoo.so'])
+
+ lib1_name, lib1_offset = libs_map.FindLibraryByOffset(10)
+ self.assertEqual(lib1_name, 'lib/abi1/libfoo.so')
+ self.assertEqual(lib1_offset, 10)
+
+ lib2_name, lib2_offset = libs_map.FindLibraryByOffset(1000)
+ self.assertEqual(lib2_name, 'lib/abi2/libfoo.so')
+ self.assertEqual(lib2_offset, 0)
+
+
+class MockApkNativeLibraries(apk_native_libs.ApkNativeLibraries):
+ """A mock ApkNativeLibraries instance that can be used as input to
+ ApkLibraryPathTranslator without creating an ApkReader instance.
+
+ Create a new instance, then call AddTestEntry or AddTestEntries
+ as many times as necessary, before using it as a regular
+ ApkNativeLibraries instance.
+ """
+ # pylint: disable=super-init-not-called
+ def __init__(self):
+ self._native_libs = []
+
+ # pylint: enable=super-init-not-called
+
+ def AddTestEntry(self, lib_path, file_offset, file_size):
+ """Add a new test entry.
+
+ Args:
+ entry: A tuple of (library-path, file-offset, file-size) values,
+ (e.g. ('lib/armeabi-v8a/libfoo.so', 0x10000, 0x2000)).
+ """
+ self._native_libs.append((lib_path, file_offset, file_offset + file_size))
+
+ def AddTestEntries(self, entries):
+ """Add a list of new test entries.
+
+ Args:
+ entries: A list of (library-path, file-offset, file-size) values.
+ """
+ for entry in entries:
+ self.AddTestEntry(entry[0], entry[1], entry[2])
+
+
+class MockApkNativeLibrariesTest(unittest.TestCase):
+
+ def testEmptyInstance(self):
+ mock = MockApkNativeLibraries()
+ self.assertTrue(mock.IsEmpty())
+ self.assertEqual(mock.GetLibraries(), [])
+ self.assertEqual(mock.GetDumpList(), [])
+
+ def testAddTestEntry(self):
+ mock = MockApkNativeLibraries()
+ mock.AddTestEntry('lib/armeabi-v7a/libfoo.so', 0x20000, 0x4000)
+ mock.AddTestEntry('lib/x86/libzoo.so', 0x10000, 0x10000)
+ mock.AddTestEntry('lib/armeabi-v7a/libbar.so', 0x24000, 0x8000)
+ self.assertFalse(mock.IsEmpty())
+ self.assertEqual(mock.GetLibraries(), ['lib/armeabi-v7a/libbar.so',
+ 'lib/armeabi-v7a/libfoo.so',
+ 'lib/x86/libzoo.so'])
+ self.assertEqual(mock.GetDumpList(), [
+ ('lib/x86/libzoo.so', 0x10000, 0x10000),
+ ('lib/armeabi-v7a/libfoo.so', 0x20000, 0x4000),
+ ('lib/armeabi-v7a/libbar.so', 0x24000, 0x8000),
+ ])
+
+ def testAddTestEntries(self):
+ mock = MockApkNativeLibraries()
+ mock.AddTestEntries([
+ ('lib/armeabi-v7a/libfoo.so', 0x20000, 0x4000),
+ ('lib/x86/libzoo.so', 0x10000, 0x10000),
+ ('lib/armeabi-v7a/libbar.so', 0x24000, 0x8000),
+ ])
+ self.assertFalse(mock.IsEmpty())
+ self.assertEqual(mock.GetLibraries(), ['lib/armeabi-v7a/libbar.so',
+ 'lib/armeabi-v7a/libfoo.so',
+ 'lib/x86/libzoo.so'])
+ self.assertEqual(mock.GetDumpList(), [
+ ('lib/x86/libzoo.so', 0x10000, 0x10000),
+ ('lib/armeabi-v7a/libfoo.so', 0x20000, 0x4000),
+ ('lib/armeabi-v7a/libbar.so', 0x24000, 0x8000),
+ ])
+
+
+class ApkLibraryPathTranslatorTest(unittest.TestCase):
+
+ def _CheckUntranslated(self, translator, path, offset):
+ """Check that a given (path, offset) is not modified by translation."""
+ self.assertEqual(translator.TranslatePath(path, offset), (path, offset))
+
+
+ def _CheckTranslated(self, translator, path, offset, new_path, new_offset):
+ """Check that (path, offset) is translated into (new_path, new_offset)."""
+ self.assertEqual(translator.TranslatePath(path, offset),
+ (new_path, new_offset))
+
+ def testEmptyInstance(self):
+ translator = apk_native_libs.ApkLibraryPathTranslator()
+ self._CheckUntranslated(
+ translator, '/data/data/com.example.app-1/base.apk', 0x123456)
+
+ def testSimpleApk(self):
+ mock_libs = MockApkNativeLibraries()
+ mock_libs.AddTestEntries([
+ ('lib/test-abi/libfoo.so', 200, 2000),
+ ('lib/test-abi/libbar.so', 3200, 3000),
+ ('lib/test-abi/crazy.libzoo.so', 6200, 2000),
+ ])
+ translator = apk_native_libs.ApkLibraryPathTranslator()
+ translator.AddHostApk('com.example.app', mock_libs)
+
+ # Offset is within the first uncompressed library
+ self._CheckTranslated(
+ translator,
+ '/data/data/com.example.app-9.apk', 757,
+ '/data/data/com.example.app-9.apk!lib/libfoo.so', 557)
+
+ # Offset is within the second compressed library.
+ self._CheckUntranslated(
+ translator,
+ '/data/data/com.example.app-9/base.apk', 2800)
+
+ # Offset is within the third uncompressed library.
+ self._CheckTranslated(
+ translator,
+ '/data/data/com.example.app-1/base.apk', 3628,
+ '/data/data/com.example.app-1/base.apk!lib/libbar.so', 428)
+
+ # Offset is within the fourth uncompressed library with crazy. prefix
+ self._CheckTranslated(
+ translator,
+ '/data/data/com.example.app-XX/base.apk', 6500,
+ '/data/data/com.example.app-XX/base.apk!lib/libzoo.so', 300)
+
+ # Out-of-bounds apk offset.
+ self._CheckUntranslated(
+ translator,
+ '/data/data/com.example.app-1/base.apk', 10000)
+
+ # Invalid package name.
+ self._CheckUntranslated(
+ translator, '/data/data/com.example2.app-1/base.apk', 757)
+
+ # Invalid apk name.
+ self._CheckUntranslated(
+ translator, '/data/data/com.example.app-2/not-base.apk', 100)
+
+ # Invalid file extensions.
+ self._CheckUntranslated(
+ translator, '/data/data/com.example.app-2/base', 100)
+
+ self._CheckUntranslated(
+ translator, '/data/data/com.example.app-2/base.apk.dex', 100)
+
+ def testBundleApks(self):
+ mock_libs1 = MockApkNativeLibraries()
+ mock_libs1.AddTestEntries([
+ ('lib/test-abi/libfoo.so', 200, 2000),
+ ('lib/test-abi/libbbar.so', 3200, 3000),
+ ])
+ mock_libs2 = MockApkNativeLibraries()
+ mock_libs2.AddTestEntries([
+ ('lib/test-abi/libzoo.so', 200, 2000),
+ ('lib/test-abi/libtool.so', 3000, 4000),
+ ])
+ translator = apk_native_libs.ApkLibraryPathTranslator()
+ translator.AddHostApk('com.example.app', mock_libs1, 'base-master.apk')
+ translator.AddHostApk('com.example.app', mock_libs2, 'feature-master.apk')
+
+ self._CheckTranslated(
+ translator,
+ '/data/app/com.example.app-XUIYIUW/base-master.apk', 757,
+ '/data/app/com.example.app-XUIYIUW/base-master.apk!lib/libfoo.so', 557)
+
+ self._CheckTranslated(
+ translator,
+ '/data/app/com.example.app-XUIYIUW/feature-master.apk', 3200,
+ '/data/app/com.example.app-XUIYIUW/feature-master.apk!lib/libtool.so',
+ 200)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/third_party/libwebrtc/build/android/pylib/symbols/deobfuscator.py b/third_party/libwebrtc/build/android/pylib/symbols/deobfuscator.py
new file mode 100644
index 0000000000..1fd188a425
--- /dev/null
+++ b/third_party/libwebrtc/build/android/pylib/symbols/deobfuscator.py
@@ -0,0 +1,178 @@
+# 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 logging
+import os
+import subprocess
+import threading
+import time
+import uuid
+
+from devil.utils import reraiser_thread
+from pylib import constants
+
+
+_MINIUMUM_TIMEOUT = 3.0
+_PER_LINE_TIMEOUT = .002 # Should be able to process 500 lines per second.
+_PROCESS_START_TIMEOUT = 10.0
+_MAX_RESTARTS = 10 # Should be plenty unless tool is crashing on start-up.
+
+
+class Deobfuscator(object):
+ def __init__(self, mapping_path):
+ script_path = os.path.join(constants.DIR_SOURCE_ROOT, 'build', 'android',
+ 'stacktrace', 'java_deobfuscate.py')
+ cmd = [script_path, mapping_path]
+ # Allow only one thread to call TransformLines() at a time.
+ self._lock = threading.Lock()
+ # Ensure that only one thread attempts to kill self._proc in Close().
+ self._close_lock = threading.Lock()
+ self._closed_called = False
+ # Assign to None so that attribute exists if Popen() throws.
+ self._proc = None
+ # Start process eagerly to hide start-up latency.
+ self._proc_start_time = time.time()
+ self._proc = subprocess.Popen(cmd,
+ bufsize=1,
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ universal_newlines=True,
+ close_fds=True)
+
+ def IsClosed(self):
+ return self._closed_called or self._proc.returncode is not None
+
+ def IsBusy(self):
+ return self._lock.locked()
+
+ def IsReady(self):
+ return not self.IsClosed() and not self.IsBusy()
+
+ def TransformLines(self, lines):
+ """Deobfuscates obfuscated names found in the given lines.
+
+ If anything goes wrong (process crashes, timeout, etc), returns |lines|.
+
+ Args:
+ lines: A list of strings without trailing newlines.
+
+ Returns:
+ A list of strings without trailing newlines.
+ """
+ if not lines:
+ return []
+
+ # Deobfuscated stacks contain more frames than obfuscated ones when method
+ # inlining occurs. To account for the extra output lines, keep reading until
+ # this eof_line token is reached.
+ eof_line = uuid.uuid4().hex
+ out_lines = []
+
+ def deobfuscate_reader():
+ while True:
+ line = self._proc.stdout.readline()
+ # Return an empty string at EOF (when stdin is closed).
+ if not line:
+ break
+ line = line[:-1]
+ if line == eof_line:
+ break
+ out_lines.append(line)
+
+ if self.IsBusy():
+ logging.warning('deobfuscator: Having to wait for Java deobfuscation.')
+
+ # Allow only one thread to operate at a time.
+ with self._lock:
+ if self.IsClosed():
+ if not self._closed_called:
+ logging.warning('deobfuscator: Process exited with code=%d.',
+ self._proc.returncode)
+ self.Close()
+ return lines
+
+ # TODO(agrieve): Can probably speed this up by only sending lines through
+ # that might contain an obfuscated name.
+ reader_thread = reraiser_thread.ReraiserThread(deobfuscate_reader)
+ reader_thread.start()
+
+ try:
+ self._proc.stdin.write('\n'.join(lines))
+ self._proc.stdin.write('\n{}\n'.format(eof_line))
+ self._proc.stdin.flush()
+ time_since_proc_start = time.time() - self._proc_start_time
+ timeout = (max(0, _PROCESS_START_TIMEOUT - time_since_proc_start) +
+ max(_MINIUMUM_TIMEOUT, len(lines) * _PER_LINE_TIMEOUT))
+ reader_thread.join(timeout)
+ if self.IsClosed():
+ logging.warning(
+ 'deobfuscator: Close() called by another thread during join().')
+ return lines
+ if reader_thread.is_alive():
+ logging.error('deobfuscator: Timed out.')
+ self.Close()
+ return lines
+ return out_lines
+ except IOError:
+ logging.exception('deobfuscator: Exception during java_deobfuscate')
+ self.Close()
+ return lines
+
+ def Close(self):
+ with self._close_lock:
+ needs_closing = not self.IsClosed()
+ self._closed_called = True
+
+ if needs_closing:
+ self._proc.stdin.close()
+ self._proc.kill()
+ self._proc.wait()
+
+ def __del__(self):
+ # self._proc is None when Popen() fails.
+ if not self._closed_called and self._proc:
+ logging.error('deobfuscator: Forgot to Close()')
+ self.Close()
+
+
+class DeobfuscatorPool(object):
+ # As of Sep 2017, each instance requires about 500MB of RAM, as measured by:
+ # /usr/bin/time -v build/android/stacktrace/java_deobfuscate.py \
+ # out/Release/apks/ChromePublic.apk.mapping
+ def __init__(self, mapping_path, pool_size=4):
+ self._mapping_path = mapping_path
+ self._pool = [Deobfuscator(mapping_path) for _ in range(pool_size)]
+ # Allow only one thread to select from the pool at a time.
+ self._lock = threading.Lock()
+ self._num_restarts = 0
+
+ def TransformLines(self, lines):
+ with self._lock:
+ assert self._pool, 'TransformLines() called on a closed DeobfuscatorPool.'
+
+ # De-obfuscation is broken.
+ if self._num_restarts == _MAX_RESTARTS:
+ raise Exception('Deobfuscation seems broken.')
+
+ # Restart any closed Deobfuscators.
+ for i, d in enumerate(self._pool):
+ if d.IsClosed():
+ logging.warning('deobfuscator: Restarting closed instance.')
+ self._pool[i] = Deobfuscator(self._mapping_path)
+ self._num_restarts += 1
+ if self._num_restarts == _MAX_RESTARTS:
+ logging.warning('deobfuscator: MAX_RESTARTS reached.')
+
+ selected = next((x for x in self._pool if x.IsReady()), self._pool[0])
+ # Rotate the order so that next caller will not choose the same one.
+ self._pool.remove(selected)
+ self._pool.append(selected)
+
+ return selected.TransformLines(lines)
+
+ def Close(self):
+ with self._lock:
+ for d in self._pool:
+ d.Close()
+ self._pool = None
diff --git a/third_party/libwebrtc/build/android/pylib/symbols/elf_symbolizer.py b/third_party/libwebrtc/build/android/pylib/symbols/elf_symbolizer.py
new file mode 100644
index 0000000000..4198511bf3
--- /dev/null
+++ b/third_party/libwebrtc/build/android/pylib/symbols/elf_symbolizer.py
@@ -0,0 +1,497 @@
+# Copyright 2014 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 collections
+import datetime
+import logging
+import multiprocessing
+import os
+import posixpath
+try:
+ from queue import Empty, Queue
+except ImportError:
+ from Queue import Empty, Queue
+import re
+import subprocess
+import sys
+import threading
+import time
+
+
+# addr2line builds a possibly infinite memory cache that can exhaust
+# the computer's memory if allowed to grow for too long. This constant
+# controls how many lookups we do before restarting the process. 4000
+# gives near peak performance without extreme memory usage.
+ADDR2LINE_RECYCLE_LIMIT = 4000
+
+
+ELF_MAGIC = '\x7f\x45\x4c\x46'
+
+
+def ContainsElfMagic(file_path):
+ if os.path.getsize(file_path) < 4:
+ return False
+ try:
+ with open(file_path, 'r') as f:
+ b = f.read(4)
+ return b == ELF_MAGIC
+ except IOError:
+ return False
+
+
+class ELFSymbolizer(object):
+ """An uber-fast (multiprocessing, pipelined and asynchronous) ELF symbolizer.
+
+ This class is a frontend for addr2line (part of GNU binutils), designed to
+ symbolize batches of large numbers of symbols for a given ELF file. It
+ supports sharding symbolization against many addr2line instances and
+ pipelining of multiple requests per each instance (in order to hide addr2line
+ internals and OS pipe latencies).
+
+ The interface exhibited by this class is a very simple asynchronous interface,
+ which is based on the following three methods:
+ - SymbolizeAsync(): used to request (enqueue) resolution of a given address.
+ - The |callback| method: used to communicated back the symbol information.
+ - Join(): called to conclude the batch to gather the last outstanding results.
+ In essence, before the Join method returns, this class will have issued as
+ many callbacks as the number of SymbolizeAsync() calls. In this regard, note
+ that due to multiprocess sharding, callbacks can be delivered out of order.
+
+ Some background about addr2line:
+ - it is invoked passing the elf path in the cmdline, piping the addresses in
+ its stdin and getting results on its stdout.
+ - it has pretty large response times for the first requests, but it
+ works very well in streaming mode once it has been warmed up.
+ - it doesn't scale by itself (on more cores). However, spawning multiple
+ instances at the same time on the same file is pretty efficient as they
+ keep hitting the pagecache and become mostly CPU bound.
+ - it might hang or crash, mostly for OOM. This class deals with both of these
+ problems.
+
+ Despite the "scary" imports and the multi* words above, (almost) no multi-
+ threading/processing is involved from the python viewpoint. Concurrency
+ here is achieved by spawning several addr2line subprocesses and handling their
+ output pipes asynchronously. Therefore, all the code here (with the exception
+ of the Queue instance in Addr2Line) should be free from mind-blowing
+ thread-safety concerns.
+
+ The multiprocess sharding works as follows:
+ The symbolizer tries to use the lowest number of addr2line instances as
+ possible (with respect of |max_concurrent_jobs|) and enqueue all the requests
+ in a single addr2line instance. For few symbols (i.e. dozens) sharding isn't
+ worth the startup cost.
+ The multiprocess logic kicks in as soon as the queues for the existing
+ instances grow. Specifically, once all the existing instances reach the
+ |max_queue_size| bound, a new addr2line instance is kicked in.
+ In the case of a very eager producer (i.e. all |max_concurrent_jobs| instances
+ have a backlog of |max_queue_size|), back-pressure is applied on the caller by
+ blocking the SymbolizeAsync method.
+
+ This module has been deliberately designed to be dependency free (w.r.t. of
+ other modules in this project), to allow easy reuse in external projects.
+ """
+
+ def __init__(self, elf_file_path, addr2line_path, callback, inlines=False,
+ max_concurrent_jobs=None, addr2line_timeout=30, max_queue_size=50,
+ source_root_path=None, strip_base_path=None):
+ """Args:
+ elf_file_path: path of the elf file to be symbolized.
+ addr2line_path: path of the toolchain's addr2line binary.
+ callback: a callback which will be invoked for each resolved symbol with
+ the two args (sym_info, callback_arg). The former is an instance of
+ |ELFSymbolInfo| and contains the symbol information. The latter is an
+ embedder-provided argument which is passed to SymbolizeAsync().
+ inlines: when True, the ELFSymbolInfo will contain also the details about
+ the outer inlining functions. When False, only the innermost function
+ will be provided.
+ max_concurrent_jobs: Max number of addr2line instances spawned.
+ Parallelize responsibly, addr2line is a memory and I/O monster.
+ max_queue_size: Max number of outstanding requests per addr2line instance.
+ addr2line_timeout: Max time (in seconds) to wait for a addr2line response.
+ After the timeout, the instance will be considered hung and respawned.
+ source_root_path: In some toolchains only the name of the source file is
+ is output, without any path information; disambiguation searches
+ through the source directory specified by |source_root_path| argument
+ for files whose name matches, adding the full path information to the
+ output. For example, if the toolchain outputs "unicode.cc" and there
+ is a file called "unicode.cc" located under |source_root_path|/foo,
+ the tool will replace "unicode.cc" with
+ "|source_root_path|/foo/unicode.cc". If there are multiple files with
+ the same name, disambiguation will fail because the tool cannot
+ determine which of the files was the source of the symbol.
+ strip_base_path: Rebases the symbols source paths onto |source_root_path|
+ (i.e replace |strip_base_path| with |source_root_path).
+ """
+ assert(os.path.isfile(addr2line_path)), 'Cannot find ' + addr2line_path
+ self.elf_file_path = elf_file_path
+ self.addr2line_path = addr2line_path
+ self.callback = callback
+ self.inlines = inlines
+ self.max_concurrent_jobs = (max_concurrent_jobs or
+ min(multiprocessing.cpu_count(), 4))
+ self.max_queue_size = max_queue_size
+ self.addr2line_timeout = addr2line_timeout
+ self.requests_counter = 0 # For generating monotonic request IDs.
+ self._a2l_instances = [] # Up to |max_concurrent_jobs| _Addr2Line inst.
+
+ # If necessary, create disambiguation lookup table
+ self.disambiguate = source_root_path is not None
+ self.disambiguation_table = {}
+ self.strip_base_path = strip_base_path
+ if self.disambiguate:
+ self.source_root_path = os.path.abspath(source_root_path)
+ self._CreateDisambiguationTable()
+
+ # Create one addr2line instance. More instances will be created on demand
+ # (up to |max_concurrent_jobs|) depending on the rate of the requests.
+ self._CreateNewA2LInstance()
+
+ def SymbolizeAsync(self, addr, callback_arg=None):
+ """Requests symbolization of a given address.
+
+ This method is not guaranteed to return immediately. It generally does, but
+ in some scenarios (e.g. all addr2line instances have full queues) it can
+ block to create back-pressure.
+
+ Args:
+ addr: address to symbolize.
+ callback_arg: optional argument which will be passed to the |callback|."""
+ assert isinstance(addr, int)
+
+ # Process all the symbols that have been resolved in the meanwhile.
+ # Essentially, this drains all the addr2line(s) out queues.
+ for a2l_to_purge in self._a2l_instances:
+ a2l_to_purge.ProcessAllResolvedSymbolsInQueue()
+ a2l_to_purge.RecycleIfNecessary()
+
+ # Find the best instance according to this logic:
+ # 1. Find an existing instance with the shortest queue.
+ # 2. If all of instances' queues are full, but there is room in the pool,
+ # (i.e. < |max_concurrent_jobs|) create a new instance.
+ # 3. If there were already |max_concurrent_jobs| instances and all of them
+ # had full queues, make back-pressure.
+
+ # 1.
+ def _SortByQueueSizeAndReqID(a2l):
+ return (a2l.queue_size, a2l.first_request_id)
+ a2l = min(self._a2l_instances, key=_SortByQueueSizeAndReqID)
+
+ # 2.
+ if (a2l.queue_size >= self.max_queue_size and
+ len(self._a2l_instances) < self.max_concurrent_jobs):
+ a2l = self._CreateNewA2LInstance()
+
+ # 3.
+ if a2l.queue_size >= self.max_queue_size:
+ a2l.WaitForNextSymbolInQueue()
+
+ a2l.EnqueueRequest(addr, callback_arg)
+
+ def WaitForIdle(self):
+ """Waits for all the outstanding requests to complete."""
+ for a2l in self._a2l_instances:
+ a2l.WaitForIdle()
+
+ def Join(self):
+ """Waits for all the outstanding requests to complete and terminates."""
+ for a2l in self._a2l_instances:
+ a2l.WaitForIdle()
+ a2l.Terminate()
+
+ def _CreateNewA2LInstance(self):
+ assert len(self._a2l_instances) < self.max_concurrent_jobs
+ a2l = ELFSymbolizer.Addr2Line(self)
+ self._a2l_instances.append(a2l)
+ return a2l
+
+ def _CreateDisambiguationTable(self):
+ """ Non-unique file names will result in None entries"""
+ start_time = time.time()
+ logging.info('Collecting information about available source files...')
+ self.disambiguation_table = {}
+
+ for root, _, filenames in os.walk(self.source_root_path):
+ for f in filenames:
+ self.disambiguation_table[f] = os.path.join(root, f) if (f not in
+ self.disambiguation_table) else None
+ logging.info('Finished collecting information about '
+ 'possible files (took %.1f s).',
+ (time.time() - start_time))
+
+
+ class Addr2Line(object):
+ """A python wrapper around an addr2line instance.
+
+ The communication with the addr2line process looks as follows:
+ [STDIN] [STDOUT] (from addr2line's viewpoint)
+ > f001111
+ > f002222
+ < Symbol::Name(foo, bar) for f001111
+ < /path/to/source/file.c:line_number
+ > f003333
+ < Symbol::Name2() for f002222
+ < /path/to/source/file.c:line_number
+ < Symbol::Name3() for f003333
+ < /path/to/source/file.c:line_number
+ """
+
+ SYM_ADDR_RE = re.compile(r'([^:]+):(\?|\d+).*')
+
+ def __init__(self, symbolizer):
+ self._symbolizer = symbolizer
+ self._lib_file_name = posixpath.basename(symbolizer.elf_file_path)
+
+ # The request queue (i.e. addresses pushed to addr2line's stdin and not
+ # yet retrieved on stdout)
+ self._request_queue = collections.deque()
+
+ # This is essentially len(self._request_queue). It has been optimized to a
+ # separate field because turned out to be a perf hot-spot.
+ self.queue_size = 0
+
+ # Keep track of the number of symbols a process has processed to
+ # avoid a single process growing too big and using all the memory.
+ self._processed_symbols_count = 0
+
+ # Objects required to handle the addr2line subprocess.
+ self._proc = None # Subprocess.Popen(...) instance.
+ self._thread = None # Threading.thread instance.
+ self._out_queue = None # Queue instance (for buffering a2l stdout).
+ self._RestartAddr2LineProcess()
+
+ def EnqueueRequest(self, addr, callback_arg):
+ """Pushes an address to addr2line's stdin (and keeps track of it)."""
+ self._symbolizer.requests_counter += 1 # For global "age" of requests.
+ req_idx = self._symbolizer.requests_counter
+ self._request_queue.append((addr, callback_arg, req_idx))
+ self.queue_size += 1
+ self._WriteToA2lStdin(addr)
+
+ def WaitForIdle(self):
+ """Waits until all the pending requests have been symbolized."""
+ while self.queue_size > 0:
+ self.WaitForNextSymbolInQueue()
+
+ def WaitForNextSymbolInQueue(self):
+ """Waits for the next pending request to be symbolized."""
+ if not self.queue_size:
+ return
+
+ # This outer loop guards against a2l hanging (detecting stdout timeout).
+ while True:
+ start_time = datetime.datetime.now()
+ timeout = datetime.timedelta(seconds=self._symbolizer.addr2line_timeout)
+
+ # The inner loop guards against a2l crashing (checking if it exited).
+ while datetime.datetime.now() - start_time < timeout:
+ # poll() returns !None if the process exited. a2l should never exit.
+ if self._proc.poll():
+ logging.warning('addr2line crashed, respawning (lib: %s).',
+ self._lib_file_name)
+ self._RestartAddr2LineProcess()
+ # TODO(primiano): the best thing to do in this case would be
+ # shrinking the pool size as, very likely, addr2line is crashed
+ # due to low memory (and the respawned one will die again soon).
+
+ try:
+ lines = self._out_queue.get(block=True, timeout=0.25)
+ except Empty:
+ # On timeout (1/4 s.) repeat the inner loop and check if either the
+ # addr2line process did crash or we waited its output for too long.
+ continue
+
+ # In nominal conditions, we get straight to this point.
+ self._ProcessSymbolOutput(lines)
+ return
+
+ # If this point is reached, we waited more than |addr2line_timeout|.
+ logging.warning('Hung addr2line process, respawning (lib: %s).',
+ self._lib_file_name)
+ self._RestartAddr2LineProcess()
+
+ def ProcessAllResolvedSymbolsInQueue(self):
+ """Consumes all the addr2line output lines produced (without blocking)."""
+ if not self.queue_size:
+ return
+ while True:
+ try:
+ lines = self._out_queue.get_nowait()
+ except Empty:
+ break
+ self._ProcessSymbolOutput(lines)
+
+ def RecycleIfNecessary(self):
+ """Restarts the process if it has been used for too long.
+
+ A long running addr2line process will consume excessive amounts
+ of memory without any gain in performance."""
+ if self._processed_symbols_count >= ADDR2LINE_RECYCLE_LIMIT:
+ self._RestartAddr2LineProcess()
+
+
+ def Terminate(self):
+ """Kills the underlying addr2line process.
+
+ The poller |_thread| will terminate as well due to the broken pipe."""
+ try:
+ self._proc.kill()
+ self._proc.communicate() # Essentially wait() without risking deadlock.
+ except Exception: # pylint: disable=broad-except
+ # An exception while terminating? How interesting.
+ pass
+ self._proc = None
+
+ def _WriteToA2lStdin(self, addr):
+ self._proc.stdin.write('%s\n' % hex(addr))
+ if self._symbolizer.inlines:
+ # In the case of inlines we output an extra blank line, which causes
+ # addr2line to emit a (??,??:0) tuple that we use as a boundary marker.
+ self._proc.stdin.write('\n')
+ self._proc.stdin.flush()
+
+ def _ProcessSymbolOutput(self, lines):
+ """Parses an addr2line symbol output and triggers the client callback."""
+ (_, callback_arg, _) = self._request_queue.popleft()
+ self.queue_size -= 1
+
+ innermost_sym_info = None
+ sym_info = None
+ for (line1, line2) in lines:
+ prev_sym_info = sym_info
+ name = line1 if not line1.startswith('?') else None
+ source_path = None
+ source_line = None
+ m = ELFSymbolizer.Addr2Line.SYM_ADDR_RE.match(line2)
+ if m:
+ if not m.group(1).startswith('?'):
+ source_path = m.group(1)
+ if not m.group(2).startswith('?'):
+ source_line = int(m.group(2))
+ else:
+ logging.warning('Got invalid symbol path from addr2line: %s', line2)
+
+ # In case disambiguation is on, and needed
+ was_ambiguous = False
+ disambiguated = False
+ if self._symbolizer.disambiguate:
+ if source_path and not posixpath.isabs(source_path):
+ path = self._symbolizer.disambiguation_table.get(source_path)
+ was_ambiguous = True
+ disambiguated = path is not None
+ source_path = path if disambiguated else source_path
+
+ # Use absolute paths (so that paths are consistent, as disambiguation
+ # uses absolute paths)
+ if source_path and not was_ambiguous:
+ source_path = os.path.abspath(source_path)
+
+ if source_path and self._symbolizer.strip_base_path:
+ # Strip the base path
+ source_path = re.sub('^' + self._symbolizer.strip_base_path,
+ self._symbolizer.source_root_path or '', source_path)
+
+ sym_info = ELFSymbolInfo(name, source_path, source_line, was_ambiguous,
+ disambiguated)
+ if prev_sym_info:
+ prev_sym_info.inlined_by = sym_info
+ if not innermost_sym_info:
+ innermost_sym_info = sym_info
+
+ self._processed_symbols_count += 1
+ self._symbolizer.callback(innermost_sym_info, callback_arg)
+
+ def _RestartAddr2LineProcess(self):
+ if self._proc:
+ self.Terminate()
+
+ # The only reason of existence of this Queue (and the corresponding
+ # Thread below) is the lack of a subprocess.stdout.poll_avail_lines().
+ # Essentially this is a pipe able to extract a couple of lines atomically.
+ self._out_queue = Queue()
+
+ # Start the underlying addr2line process in line buffered mode.
+
+ cmd = [self._symbolizer.addr2line_path, '--functions', '--demangle',
+ '--exe=' + self._symbolizer.elf_file_path]
+ if self._symbolizer.inlines:
+ cmd += ['--inlines']
+ self._proc = subprocess.Popen(cmd,
+ bufsize=1,
+ universal_newlines=True,
+ stdout=subprocess.PIPE,
+ stdin=subprocess.PIPE,
+ stderr=sys.stderr,
+ close_fds=True)
+
+ # Start the poller thread, which simply moves atomically the lines read
+ # from the addr2line's stdout to the |_out_queue|.
+ self._thread = threading.Thread(
+ target=ELFSymbolizer.Addr2Line.StdoutReaderThread,
+ args=(self._proc.stdout, self._out_queue, self._symbolizer.inlines))
+ self._thread.daemon = True # Don't prevent early process exit.
+ self._thread.start()
+
+ self._processed_symbols_count = 0
+
+ # Replay the pending requests on the new process (only for the case
+ # of a hung addr2line timing out during the game).
+ for (addr, _, _) in self._request_queue:
+ self._WriteToA2lStdin(addr)
+
+ @staticmethod
+ def StdoutReaderThread(process_pipe, my_queue, inlines):
+ """The poller thread fn, which moves the addr2line stdout to the |queue|.
+
+ This is the only piece of code not running on the main thread. It merely
+ writes to a Queue, which is thread-safe. In the case of inlines, it
+ detects the ??,??:0 marker and sends the lines atomically, such that the
+ main thread always receives all the lines corresponding to one symbol in
+ one shot."""
+ try:
+ lines_for_one_symbol = []
+ while True:
+ line1 = process_pipe.readline().rstrip('\r\n')
+ if not line1:
+ break
+ line2 = process_pipe.readline().rstrip('\r\n')
+ if not line2:
+ break
+ inline_has_more_lines = inlines and (len(lines_for_one_symbol) == 0 or
+ (line1 != '??' and line2 != '??:0'))
+ if not inlines or inline_has_more_lines:
+ lines_for_one_symbol += [(line1, line2)]
+ if inline_has_more_lines:
+ continue
+ my_queue.put(lines_for_one_symbol)
+ lines_for_one_symbol = []
+ process_pipe.close()
+
+ # Every addr2line processes will die at some point, please die silently.
+ except (IOError, OSError):
+ pass
+
+ @property
+ def first_request_id(self):
+ """Returns the request_id of the oldest pending request in the queue."""
+ return self._request_queue[0][2] if self._request_queue else 0
+
+
+class ELFSymbolInfo(object):
+ """The result of the symbolization passed as first arg. of each callback."""
+
+ def __init__(self, name, source_path, source_line, was_ambiguous=False,
+ disambiguated=False):
+ """All the fields here can be None (if addr2line replies with '??')."""
+ self.name = name
+ self.source_path = source_path
+ self.source_line = source_line
+ # In the case of |inlines|=True, the |inlined_by| points to the outer
+ # function inlining the current one (and so on, to form a chain).
+ self.inlined_by = None
+ self.disambiguated = disambiguated
+ self.was_ambiguous = was_ambiguous
+
+ def __str__(self):
+ return '%s [%s:%d]' % (
+ self.name or '??', self.source_path or '??', self.source_line or 0)
diff --git a/third_party/libwebrtc/build/android/pylib/symbols/elf_symbolizer_unittest.py b/third_party/libwebrtc/build/android/pylib/symbols/elf_symbolizer_unittest.py
new file mode 100755
index 0000000000..f906da8314
--- /dev/null
+++ b/third_party/libwebrtc/build/android/pylib/symbols/elf_symbolizer_unittest.py
@@ -0,0 +1,196 @@
+#!/usr/bin/env python3
+# Copyright 2014 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 functools
+import logging
+import os
+import unittest
+
+from pylib.symbols import elf_symbolizer
+from pylib.symbols import mock_addr2line
+
+
+_MOCK_A2L_PATH = os.path.join(os.path.dirname(mock_addr2line.__file__),
+ 'mock_addr2line')
+_INCOMPLETE_MOCK_ADDR = 1024 * 1024
+_UNKNOWN_MOCK_ADDR = 2 * 1024 * 1024
+_INLINE_MOCK_ADDR = 3 * 1024 * 1024
+
+
+class ELFSymbolizerTest(unittest.TestCase):
+ def setUp(self):
+ self._callback = functools.partial(
+ ELFSymbolizerTest._SymbolizeCallback, self)
+ self._resolved_addresses = set()
+ # Mute warnings, we expect them due to the crash/hang tests.
+ logging.getLogger().setLevel(logging.ERROR)
+
+ def testParallelism1(self):
+ self._RunTest(max_concurrent_jobs=1, num_symbols=100)
+
+ def testParallelism4(self):
+ self._RunTest(max_concurrent_jobs=4, num_symbols=100)
+
+ def testParallelism8(self):
+ self._RunTest(max_concurrent_jobs=8, num_symbols=100)
+
+ def testCrash(self):
+ os.environ['MOCK_A2L_CRASH_EVERY'] = '99'
+ self._RunTest(max_concurrent_jobs=1, num_symbols=100)
+ os.environ['MOCK_A2L_CRASH_EVERY'] = '0'
+
+ def testHang(self):
+ os.environ['MOCK_A2L_HANG_EVERY'] = '99'
+ self._RunTest(max_concurrent_jobs=1, num_symbols=100)
+ os.environ['MOCK_A2L_HANG_EVERY'] = '0'
+
+ def testInlines(self):
+ """Stimulate the inline processing logic."""
+ symbolizer = elf_symbolizer.ELFSymbolizer(
+ elf_file_path='/path/doesnt/matter/mock_lib1.so',
+ addr2line_path=_MOCK_A2L_PATH,
+ callback=self._callback,
+ inlines=True,
+ max_concurrent_jobs=4)
+
+ for addr in range(1000):
+ exp_inline = False
+ exp_unknown = False
+
+ # First 100 addresses with inlines.
+ if addr < 100:
+ addr += _INLINE_MOCK_ADDR
+ exp_inline = True
+
+ # Followed by 100 without inlines.
+ elif addr < 200:
+ pass
+
+ # Followed by 100 interleaved inlines and not inlines.
+ elif addr < 300:
+ if addr & 1:
+ addr += _INLINE_MOCK_ADDR
+ exp_inline = True
+
+ # Followed by 100 interleaved inlines and unknonwn.
+ elif addr < 400:
+ if addr & 1:
+ addr += _INLINE_MOCK_ADDR
+ exp_inline = True
+ else:
+ addr += _UNKNOWN_MOCK_ADDR
+ exp_unknown = True
+
+ exp_name = 'mock_sym_for_addr_%d' % addr if not exp_unknown else None
+ exp_source_path = 'mock_src/mock_lib1.so.c' if not exp_unknown else None
+ exp_source_line = addr if not exp_unknown else None
+ cb_arg = (addr, exp_name, exp_source_path, exp_source_line, exp_inline)
+ symbolizer.SymbolizeAsync(addr, cb_arg)
+
+ symbolizer.Join()
+
+ def testIncompleteSyminfo(self):
+ """Stimulate the symbol-not-resolved logic."""
+ symbolizer = elf_symbolizer.ELFSymbolizer(
+ elf_file_path='/path/doesnt/matter/mock_lib1.so',
+ addr2line_path=_MOCK_A2L_PATH,
+ callback=self._callback,
+ max_concurrent_jobs=1)
+
+ # Test symbols with valid name but incomplete path.
+ addr = _INCOMPLETE_MOCK_ADDR
+ exp_name = 'mock_sym_for_addr_%d' % addr
+ exp_source_path = None
+ exp_source_line = None
+ cb_arg = (addr, exp_name, exp_source_path, exp_source_line, False)
+ symbolizer.SymbolizeAsync(addr, cb_arg)
+
+ # Test symbols with no name or sym info.
+ addr = _UNKNOWN_MOCK_ADDR
+ exp_name = None
+ exp_source_path = None
+ exp_source_line = None
+ cb_arg = (addr, exp_name, exp_source_path, exp_source_line, False)
+ symbolizer.SymbolizeAsync(addr, cb_arg)
+
+ symbolizer.Join()
+
+ def testWaitForIdle(self):
+ symbolizer = elf_symbolizer.ELFSymbolizer(
+ elf_file_path='/path/doesnt/matter/mock_lib1.so',
+ addr2line_path=_MOCK_A2L_PATH,
+ callback=self._callback,
+ max_concurrent_jobs=1)
+
+ # Test symbols with valid name but incomplete path.
+ addr = _INCOMPLETE_MOCK_ADDR
+ exp_name = 'mock_sym_for_addr_%d' % addr
+ exp_source_path = None
+ exp_source_line = None
+ cb_arg = (addr, exp_name, exp_source_path, exp_source_line, False)
+ symbolizer.SymbolizeAsync(addr, cb_arg)
+ symbolizer.WaitForIdle()
+
+ # Test symbols with no name or sym info.
+ addr = _UNKNOWN_MOCK_ADDR
+ exp_name = None
+ exp_source_path = None
+ exp_source_line = None
+ cb_arg = (addr, exp_name, exp_source_path, exp_source_line, False)
+ symbolizer.SymbolizeAsync(addr, cb_arg)
+ symbolizer.Join()
+
+ def _RunTest(self, max_concurrent_jobs, num_symbols):
+ symbolizer = elf_symbolizer.ELFSymbolizer(
+ elf_file_path='/path/doesnt/matter/mock_lib1.so',
+ addr2line_path=_MOCK_A2L_PATH,
+ callback=self._callback,
+ max_concurrent_jobs=max_concurrent_jobs,
+ addr2line_timeout=0.5)
+
+ for addr in range(num_symbols):
+ exp_name = 'mock_sym_for_addr_%d' % addr
+ exp_source_path = 'mock_src/mock_lib1.so.c'
+ exp_source_line = addr
+ cb_arg = (addr, exp_name, exp_source_path, exp_source_line, False)
+ symbolizer.SymbolizeAsync(addr, cb_arg)
+
+ symbolizer.Join()
+
+ # Check that all the expected callbacks have been received.
+ for addr in range(num_symbols):
+ self.assertIn(addr, self._resolved_addresses)
+ self._resolved_addresses.remove(addr)
+
+ # Check for unexpected callbacks.
+ self.assertEqual(len(self._resolved_addresses), 0)
+
+ def _SymbolizeCallback(self, sym_info, cb_arg):
+ self.assertTrue(isinstance(sym_info, elf_symbolizer.ELFSymbolInfo))
+ self.assertTrue(isinstance(cb_arg, tuple))
+ self.assertEqual(len(cb_arg), 5)
+
+ # Unpack expectations from the callback extra argument.
+ (addr, exp_name, exp_source_path, exp_source_line, exp_inlines) = cb_arg
+ if exp_name is None:
+ self.assertIsNone(sym_info.name)
+ else:
+ self.assertTrue(sym_info.name.startswith(exp_name))
+ self.assertEqual(sym_info.source_path, exp_source_path)
+ self.assertEqual(sym_info.source_line, exp_source_line)
+
+ if exp_inlines:
+ self.assertEqual(sym_info.name, exp_name + '_inner')
+ self.assertEqual(sym_info.inlined_by.name, exp_name + '_middle')
+ self.assertEqual(sym_info.inlined_by.inlined_by.name,
+ exp_name + '_outer')
+
+ # Check against duplicate callbacks.
+ self.assertNotIn(addr, self._resolved_addresses)
+ self._resolved_addresses.add(addr)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/third_party/libwebrtc/build/android/pylib/symbols/mock_addr2line/__init__.py b/third_party/libwebrtc/build/android/pylib/symbols/mock_addr2line/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/third_party/libwebrtc/build/android/pylib/symbols/mock_addr2line/__init__.py
diff --git a/third_party/libwebrtc/build/android/pylib/symbols/mock_addr2line/mock_addr2line b/third_party/libwebrtc/build/android/pylib/symbols/mock_addr2line/mock_addr2line
new file mode 100755
index 0000000000..8b2a72375d
--- /dev/null
+++ b/third_party/libwebrtc/build/android/pylib/symbols/mock_addr2line/mock_addr2line
@@ -0,0 +1,81 @@
+#!/usr/bin/env python
+# Copyright 2014 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.
+
+"""Simple mock for addr2line.
+
+Outputs mock symbol information, with each symbol being a function of the
+original address (so it is easy to double-check consistency in unittests).
+"""
+
+from __future__ import print_function
+
+import optparse
+import os
+import posixpath
+import sys
+import time
+
+
+def main(argv):
+ parser = optparse.OptionParser()
+ parser.add_option('-e', '--exe', dest='exe') # Path of the debug-library.so.
+ # Silently swallow the other unnecessary arguments.
+ parser.add_option('-C', '--demangle', action='store_true')
+ parser.add_option('-f', '--functions', action='store_true')
+ parser.add_option('-i', '--inlines', action='store_true')
+ options, _ = parser.parse_args(argv[1:])
+ lib_file_name = posixpath.basename(options.exe)
+ processed_sym_count = 0
+ crash_every = int(os.environ.get('MOCK_A2L_CRASH_EVERY', 0))
+ hang_every = int(os.environ.get('MOCK_A2L_HANG_EVERY', 0))
+
+ while(True):
+ line = sys.stdin.readline().rstrip('\r')
+ if not line:
+ break
+
+ # An empty line should generate '??,??:0' (is used as marker for inlines).
+ if line == '\n':
+ print('??')
+ print('??:0')
+ sys.stdout.flush()
+ continue
+
+ addr = int(line, 16)
+ processed_sym_count += 1
+ if crash_every and processed_sym_count % crash_every == 0:
+ sys.exit(1)
+ if hang_every and processed_sym_count % hang_every == 0:
+ time.sleep(1)
+
+ # Addresses < 1M will return good mock symbol information.
+ if addr < 1024 * 1024:
+ print('mock_sym_for_addr_%d' % addr)
+ print('mock_src/%s.c:%d' % (lib_file_name, addr))
+
+ # Addresses 1M <= x < 2M will return symbols with a name but a missing path.
+ elif addr < 2 * 1024 * 1024:
+ print('mock_sym_for_addr_%d' % addr)
+ print('??:0')
+
+ # Addresses 2M <= x < 3M will return unknown symbol information.
+ elif addr < 3 * 1024 * 1024:
+ print('??')
+ print('??')
+
+ # Addresses 3M <= x < 4M will return inlines.
+ elif addr < 4 * 1024 * 1024:
+ print('mock_sym_for_addr_%d_inner' % addr)
+ print('mock_src/%s.c:%d' % (lib_file_name, addr))
+ print('mock_sym_for_addr_%d_middle' % addr)
+ print('mock_src/%s.c:%d' % (lib_file_name, addr))
+ print('mock_sym_for_addr_%d_outer' % addr)
+ print('mock_src/%s.c:%d' % (lib_file_name, addr))
+
+ sys.stdout.flush()
+
+
+if __name__ == '__main__':
+ main(sys.argv) \ No newline at end of file
diff --git a/third_party/libwebrtc/build/android/pylib/symbols/stack_symbolizer.py b/third_party/libwebrtc/build/android/pylib/symbols/stack_symbolizer.py
new file mode 100644
index 0000000000..fdd47780f7
--- /dev/null
+++ b/third_party/libwebrtc/build/android/pylib/symbols/stack_symbolizer.py
@@ -0,0 +1,86 @@
+# 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 logging
+import os
+import re
+import tempfile
+import time
+
+from devil.utils import cmd_helper
+from pylib import constants
+
+_STACK_TOOL = os.path.join(os.path.dirname(__file__), '..', '..', '..', '..',
+ 'third_party', 'android_platform', 'development',
+ 'scripts', 'stack')
+ABI_REG = re.compile('ABI: \'(.+?)\'')
+
+
+def _DeviceAbiToArch(device_abi):
+ # The order of this list is significant to find the more specific match
+ # (e.g., arm64) before the less specific (e.g., arm).
+ arches = ['arm64', 'arm', 'x86_64', 'x86_64', 'x86', 'mips']
+ for arch in arches:
+ if arch in device_abi:
+ return arch
+ raise RuntimeError('Unknown device ABI: %s' % device_abi)
+
+
+class Symbolizer(object):
+ """A helper class to symbolize stack."""
+
+ def __init__(self, apk_under_test=None):
+ self._apk_under_test = apk_under_test
+ self._time_spent_symbolizing = 0
+
+
+ def __del__(self):
+ self.CleanUp()
+
+
+ def CleanUp(self):
+ """Clean up the temporary directory of apk libs."""
+ if self._time_spent_symbolizing > 0:
+ logging.info(
+ 'Total time spent symbolizing: %.2fs', self._time_spent_symbolizing)
+
+
+ def ExtractAndResolveNativeStackTraces(self, data_to_symbolize,
+ device_abi, include_stack=True):
+ """Run the stack tool for given input.
+
+ Args:
+ data_to_symbolize: a list of strings to symbolize.
+ include_stack: boolean whether to include stack data in output.
+ device_abi: the default ABI of the device which generated the tombstone.
+
+ Yields:
+ A string for each line of resolved stack output.
+ """
+ if not os.path.exists(_STACK_TOOL):
+ logging.warning('%s missing. Unable to resolve native stack traces.',
+ _STACK_TOOL)
+ return
+
+ arch = _DeviceAbiToArch(device_abi)
+ if not arch:
+ logging.warning('No device_abi can be found.')
+ return
+
+ cmd = [_STACK_TOOL, '--arch', arch, '--output-directory',
+ constants.GetOutDirectory(), '--more-info']
+ env = dict(os.environ)
+ env['PYTHONDONTWRITEBYTECODE'] = '1'
+ with tempfile.NamedTemporaryFile(mode='w') as f:
+ f.write('\n'.join(data_to_symbolize))
+ f.flush()
+ start = time.time()
+ try:
+ _, output = cmd_helper.GetCmdStatusAndOutput(cmd + [f.name], env=env)
+ finally:
+ self._time_spent_symbolizing += time.time() - start
+ for line in output.splitlines():
+ if not include_stack and 'Stack Data:' in line:
+ break
+ yield line
diff --git a/third_party/libwebrtc/build/android/pylib/symbols/symbol_utils.py b/third_party/libwebrtc/build/android/pylib/symbols/symbol_utils.py
new file mode 100644
index 0000000000..0b6ec8bb29
--- /dev/null
+++ b/third_party/libwebrtc/build/android/pylib/symbols/symbol_utils.py
@@ -0,0 +1,813 @@
+# Copyright 2018 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 bisect
+import collections
+import logging
+import os
+import re
+
+from pylib.constants import host_paths
+from pylib.symbols import elf_symbolizer
+
+
+def _AndroidAbiToCpuArch(android_abi):
+ """Return the Chromium CPU architecture name for a given Android ABI."""
+ _ARCH_MAP = {
+ 'armeabi': 'arm',
+ 'armeabi-v7a': 'arm',
+ 'arm64-v8a': 'arm64',
+ 'x86_64': 'x64',
+ }
+ return _ARCH_MAP.get(android_abi, android_abi)
+
+
+def _HexAddressRegexpFor(android_abi):
+ """Return a regexp matching hexadecimal addresses for a given Android ABI."""
+ if android_abi in ['x86_64', 'arm64-v8a', 'mips64']:
+ width = 16
+ else:
+ width = 8
+ return '[0-9a-f]{%d}' % width
+
+
+class HostLibraryFinder(object):
+ """Translate device library path to matching host unstripped library path.
+
+ Usage is the following:
+ 1) Create instance.
+ 2) Call AddSearchDir() once or more times to add host directory path to
+ look for unstripped native libraries.
+ 3) Call Find(device_libpath) repeatedly to translate a device-specific
+ library path into the corresponding host path to the unstripped
+ version.
+ """
+ def __init__(self):
+ """Initialize instance."""
+ self._search_dirs = []
+ self._lib_map = {} # Map of library name to host file paths.
+
+ def AddSearchDir(self, lib_dir):
+ """Add a directory to the search path for host native shared libraries.
+
+ Args:
+ lib_dir: host path containing native libraries.
+ """
+ if not os.path.exists(lib_dir):
+ logging.warning('Ignoring missing host library directory: %s', lib_dir)
+ return
+ if not os.path.isdir(lib_dir):
+ logging.warning('Ignoring invalid host library directory: %s', lib_dir)
+ return
+ self._search_dirs.append(lib_dir)
+ self._lib_map = {} # Reset the map.
+
+ def Find(self, device_libpath):
+ """Find the host file path matching a specific device library path.
+
+ Args:
+ device_libpath: device-specific file path to library or executable.
+ Returns:
+ host file path to the unstripped version of the library, or None.
+ """
+ host_lib_path = None
+ lib_name = os.path.basename(device_libpath)
+ host_lib_path = self._lib_map.get(lib_name)
+ if not host_lib_path:
+ for search_dir in self._search_dirs:
+ lib_path = os.path.join(search_dir, lib_name)
+ if os.path.exists(lib_path):
+ host_lib_path = lib_path
+ break
+
+ if not host_lib_path:
+ logging.debug('Could not find host library for: %s', lib_name)
+ self._lib_map[lib_name] = host_lib_path
+
+ return host_lib_path
+
+
+
+class SymbolResolver(object):
+ """A base class for objets that can symbolize library (path, offset)
+ pairs into symbol information strings. Usage is the following:
+
+ 1) Create new instance (by calling the constructor of a derived
+ class, since this is only the base one).
+
+ 2) Call SetAndroidAbi() before any call to FindSymbolInfo() in order
+ to set the Android CPU ABI used for symbolization.
+
+ 3) Before the first call to FindSymbolInfo(), one can call
+ AddLibraryOffset(), or AddLibraryOffsets() to record a set of offsets
+ that you will want to symbolize later through FindSymbolInfo(). Doing
+ so allows some SymbolResolver derived classes to work faster (e.g. the
+ one that invokes the 'addr2line' program, since the latter works faster
+ if the offsets provided as inputs are sorted in increasing order).
+
+ 3) Call FindSymbolInfo(path, offset) to return the corresponding
+ symbol information string, or None if this doesn't correspond
+ to anything the instance can handle.
+
+ Note that whether the path is specific to the device or to the
+ host depends on the derived class implementation.
+ """
+ def __init__(self):
+ self._android_abi = None
+ self._lib_offsets_map = collections.defaultdict(set)
+
+ def SetAndroidAbi(self, android_abi):
+ """Set the Android ABI value for this instance.
+
+ Calling this function before FindSymbolInfo() is required by some
+ derived class implementations.
+
+ Args:
+ android_abi: Native Android CPU ABI name (e.g. 'armeabi-v7a').
+ Raises:
+ Exception if the ABI was already set with a different value.
+ """
+ if self._android_abi and self._android_abi != android_abi:
+ raise Exception('Cannot reset Android ABI to new value %s, already set '
+ 'to %s' % (android_abi, self._android_abi))
+
+ self._android_abi = android_abi
+
+ def AddLibraryOffset(self, lib_path, offset):
+ """Associate a single offset to a given device library.
+
+ This must be called before FindSymbolInfo(), otherwise its input arguments
+ will be ignored.
+
+ Args:
+ lib_path: A library path.
+ offset: An integer offset within the corresponding library that will be
+ symbolized by future calls to FindSymbolInfo.
+ """
+ self._lib_offsets_map[lib_path].add(offset)
+
+ def AddLibraryOffsets(self, lib_path, lib_offsets):
+ """Associate a set of wanted offsets to a given device library.
+
+ This must be called before FindSymbolInfo(), otherwise its input arguments
+ will be ignored.
+
+ Args:
+ lib_path: A library path.
+ lib_offsets: An iterable of integer offsets within the corresponding
+ library that will be symbolized by future calls to FindSymbolInfo.
+ """
+ self._lib_offsets_map[lib_path].update(lib_offsets)
+
+ # pylint: disable=unused-argument,no-self-use
+ def FindSymbolInfo(self, lib_path, lib_offset):
+ """Symbolize a device library path and offset.
+
+ Args:
+ lib_path: Library path (device or host specific, depending on the
+ derived class implementation).
+ lib_offset: Integer offset within the library.
+ Returns:
+ Corresponding symbol information string, or None.
+ """
+ # The base implementation cannot symbolize anything.
+ return None
+ # pylint: enable=unused-argument,no-self-use
+
+
+class ElfSymbolResolver(SymbolResolver):
+ """A SymbolResolver that can symbolize host path + offset values using
+ an elf_symbolizer.ELFSymbolizer instance.
+ """
+ def __init__(self, addr2line_path_for_tests=None):
+ super(ElfSymbolResolver, self).__init__()
+ self._addr2line_path = addr2line_path_for_tests
+
+ # Used to cache one ELFSymbolizer instance per library path.
+ self._elf_symbolizer_cache = {}
+
+ # Used to cache FindSymbolInfo() results. Maps host library paths
+ # to (offset -> symbol info string) dictionaries.
+ self._symbol_info_cache = collections.defaultdict(dict)
+ self._allow_symbolizer = True
+
+ def _CreateSymbolizerFor(self, host_path):
+ """Create the ELFSymbolizer instance associated with a given lib path."""
+ addr2line_path = self._addr2line_path
+ if not addr2line_path:
+ if not self._android_abi:
+ raise Exception(
+ 'Android CPU ABI must be set before calling FindSymbolInfo!')
+
+ cpu_arch = _AndroidAbiToCpuArch(self._android_abi)
+ self._addr2line_path = host_paths.ToolPath('addr2line', cpu_arch)
+
+ return elf_symbolizer.ELFSymbolizer(
+ elf_file_path=host_path, addr2line_path=self._addr2line_path,
+ callback=ElfSymbolResolver._Callback, inlines=True)
+
+ def DisallowSymbolizerForTesting(self):
+ """Disallow FindSymbolInfo() from using a symbolizer.
+
+ This is used during unit-testing to ensure that the offsets that were
+ recorded via AddLibraryOffset()/AddLibraryOffsets() are properly
+ symbolized, but not anything else.
+ """
+ self._allow_symbolizer = False
+
+ def FindSymbolInfo(self, host_path, offset):
+ """Override SymbolResolver.FindSymbolInfo.
+
+ Args:
+ host_path: Host-specific path to the native shared library.
+ offset: Integer offset within the native library.
+ Returns:
+ A symbol info string, or None.
+ """
+ offset_map = self._symbol_info_cache[host_path]
+ symbol_info = offset_map.get(offset)
+ if symbol_info:
+ return symbol_info
+
+ # Create symbolizer on demand.
+ symbolizer = self._elf_symbolizer_cache.get(host_path)
+ if not symbolizer:
+ symbolizer = self._CreateSymbolizerFor(host_path)
+ self._elf_symbolizer_cache[host_path] = symbolizer
+
+ # If there are pre-recorded offsets for this path, symbolize them now.
+ offsets = self._lib_offsets_map.get(host_path)
+ if offsets:
+ offset_map = {}
+ for pre_offset in offsets:
+ symbolizer.SymbolizeAsync(
+ pre_offset, callback_arg=(offset_map, pre_offset))
+ symbolizer.WaitForIdle()
+ self._symbol_info_cache[host_path] = offset_map
+
+ symbol_info = offset_map.get(offset)
+ if symbol_info:
+ return symbol_info
+
+ if not self._allow_symbolizer:
+ return None
+
+ # Symbolize single offset. Slower if addresses are not provided in
+ # increasing order to addr2line.
+ symbolizer.SymbolizeAsync(offset,
+ callback_arg=(offset_map, offset))
+ symbolizer.WaitForIdle()
+ return offset_map.get(offset)
+
+ @staticmethod
+ def _Callback(sym_info, callback_arg):
+ offset_map, offset = callback_arg
+ offset_map[offset] = str(sym_info)
+
+
+class DeviceSymbolResolver(SymbolResolver):
+ """A SymbolResolver instance that accepts device-specific path.
+
+ Usage is the following:
+ 1) Create new instance, passing a parent SymbolResolver instance that
+ accepts host-specific paths, and a HostLibraryFinder instance.
+
+ 2) Optional: call AddApkOffsets() to add offsets from within an APK
+ that contains uncompressed native shared libraries.
+
+ 3) Use it as any SymbolResolver instance.
+ """
+ def __init__(self, host_resolver, host_lib_finder):
+ """Initialize instance.
+
+ Args:
+ host_resolver: A parent SymbolResolver instance that will be used
+ to resolve symbols from host library paths.
+ host_lib_finder: A HostLibraryFinder instance used to locate
+ unstripped libraries on the host.
+ """
+ super(DeviceSymbolResolver, self).__init__()
+ self._host_lib_finder = host_lib_finder
+ self._bad_device_lib_paths = set()
+ self._host_resolver = host_resolver
+
+ def SetAndroidAbi(self, android_abi):
+ super(DeviceSymbolResolver, self).SetAndroidAbi(android_abi)
+ self._host_resolver.SetAndroidAbi(android_abi)
+
+ def AddLibraryOffsets(self, device_lib_path, lib_offsets):
+ """Associate a set of wanted offsets to a given device library.
+
+ This must be called before FindSymbolInfo(), otherwise its input arguments
+ will be ignored.
+
+ Args:
+ device_lib_path: A device-specific library path.
+ lib_offsets: An iterable of integer offsets within the corresponding
+ library that will be symbolized by future calls to FindSymbolInfo.
+ want to symbolize.
+ """
+ if device_lib_path in self._bad_device_lib_paths:
+ return
+
+ host_lib_path = self._host_lib_finder.Find(device_lib_path)
+ if not host_lib_path:
+ # NOTE: self._bad_device_lib_paths is only used to only print this
+ # warning once per bad library.
+ logging.warning('Could not find host library matching device path: %s',
+ device_lib_path)
+ self._bad_device_lib_paths.add(device_lib_path)
+ return
+
+ self._host_resolver.AddLibraryOffsets(host_lib_path, lib_offsets)
+
+ def AddApkOffsets(self, device_apk_path, apk_offsets, apk_translator):
+ """Associate a set of wanted offsets to a given device APK path.
+
+ This converts the APK-relative offsets into offsets relative to the
+ uncompressed libraries it contains, then calls AddLibraryOffsets()
+ for each one of the libraries.
+
+ Must be called before FindSymbolInfo() as well, otherwise input arguments
+ will be ignored.
+
+ Args:
+ device_apk_path: Device-specific APK path.
+ apk_offsets: Iterable of offsets within the APK file.
+ apk_translator: An ApkLibraryPathTranslator instance used to extract
+ library paths from the APK.
+ """
+ libraries_map = collections.defaultdict(set)
+ for offset in apk_offsets:
+ lib_path, lib_offset = apk_translator.TranslatePath(device_apk_path,
+ offset)
+ libraries_map[lib_path].add(lib_offset)
+
+ for lib_path, lib_offsets in libraries_map.items():
+ self.AddLibraryOffsets(lib_path, lib_offsets)
+
+ def FindSymbolInfo(self, device_path, offset):
+ """Overrides SymbolResolver.FindSymbolInfo.
+
+ Args:
+ device_path: Device-specific library path (e.g.
+ '/data/app/com.example.app-1/lib/x86/libfoo.so')
+ offset: Offset in device library path.
+ Returns:
+ Corresponding symbol information string, or None.
+ """
+ host_path = self._host_lib_finder.Find(device_path)
+ if not host_path:
+ return None
+
+ return self._host_resolver.FindSymbolInfo(host_path, offset)
+
+
+class MemoryMap(object):
+ """Models the memory map of a given process. Usage is:
+
+ 1) Create new instance, passing the Android ABI.
+
+ 2) Call TranslateLine() whenever you want to detect and translate any
+ memory map input line.
+
+ 3) Otherwise, it is possible to parse the whole memory map input with
+ ParseLines(), then call FindSectionForAddress() repeatedly in order
+ to translate a memory address into the corresponding mapping and
+ file information tuple (e.g. to symbolize stack entries).
+ """
+
+ # A named tuple describing interesting memory map line items.
+ # Fields:
+ # addr_start: Mapping start address in memory.
+ # file_offset: Corresponding file offset.
+ # file_size: Corresponding mapping size in bytes.
+ # file_path: Input file path.
+ # match: Corresponding regular expression match object.
+ LineTuple = collections.namedtuple('MemoryMapLineTuple',
+ 'addr_start,file_offset,file_size,'
+ 'file_path, match')
+
+ # A name tuple describing a memory map section.
+ # Fields:
+ # address: Memory address.
+ # size: Size in bytes in memory
+ # offset: Starting file offset.
+ # path: Input file path.
+ SectionTuple = collections.namedtuple('MemoryMapSection',
+ 'address,size,offset,path')
+
+ def __init__(self, android_abi):
+ """Initializes instance.
+
+ Args:
+ android_abi: Android CPU ABI name (e.g. 'armeabi-v7a')
+ """
+ hex_addr = _HexAddressRegexpFor(android_abi)
+
+ # pylint: disable=line-too-long
+ # A regular expression used to match memory map entries which look like:
+ # b278c000-b2790fff r-- 4fda000 5000 /data/app/com.google.android.apps.chrome-2/base.apk
+ # pylint: enable=line-too-long
+ self._re_map_section = re.compile(
+ r'\s*(?P<addr_start>' + hex_addr + r')-(?P<addr_end>' + hex_addr + ')' +
+ r'\s+' +
+ r'(?P<perm>...)\s+' +
+ r'(?P<file_offset>[0-9a-f]+)\s+' +
+ r'(?P<file_size>[0-9a-f]+)\s*' +
+ r'(?P<file_path>[^ \t]+)?')
+
+ self._addr_map = [] # Sorted list of (address, size, path, offset) tuples.
+ self._sorted_addresses = [] # Sorted list of address fields in _addr_map.
+ self._in_section = False
+
+ def TranslateLine(self, line, apk_path_translator):
+ """Try to translate a memory map input line, if detected.
+
+ This only takes care of converting mapped APK file path and offsets
+ into a corresponding uncompressed native library file path + new offsets,
+ e.g. '..... <offset> <size> /data/.../base.apk' gets
+ translated into '.... <new-offset> <size> /data/.../base.apk!lib/libfoo.so'
+
+ This function should always work, even if ParseLines() was not called
+ previously.
+
+ Args:
+ line: Input memory map / tombstone line.
+ apk_translator: An ApkLibraryPathTranslator instance, used to map
+ APK offsets into uncompressed native libraries + new offsets.
+ Returns:
+ Translated memory map line, if relevant, or unchanged input line
+ otherwise.
+ """
+ t = self._ParseLine(line.rstrip())
+ if not t:
+ return line
+
+ new_path, new_offset = apk_path_translator.TranslatePath(
+ t.file_path, t.file_offset)
+
+ if new_path == t.file_path:
+ return line
+
+ pos = t.match.start('file_path')
+ return '%s%s (offset 0x%x)%s' % (line[0:pos], new_path, new_offset,
+ line[t.match.end('file_path'):])
+
+ def ParseLines(self, input_lines, in_section=False):
+ """Parse a list of input lines and extract the APK memory map out of it.
+
+ Args:
+ input_lines: list, or iterable, of input lines.
+ in_section: Optional. If true, considers that the input lines are
+ already part of the memory map. Otherwise, wait until the start of
+ the section appears in the input before trying to record data.
+ Returns:
+ True iff APK-related memory map entries were found. False otherwise.
+ """
+ addr_list = [] # list of (address, size, file_path, file_offset) tuples.
+ self._in_section = in_section
+ for line in input_lines:
+ t = self._ParseLine(line.rstrip())
+ if not t:
+ continue
+
+ addr_list.append(t)
+
+ self._addr_map = sorted(addr_list, key=lambda x: x.addr_start)
+ self._sorted_addresses = [e.addr_start for e in self._addr_map]
+ return bool(self._addr_map)
+
+ def _ParseLine(self, line):
+ """Used internally to recognized memory map input lines.
+
+ Args:
+ line: Input logcat or tomstone line.
+ Returns:
+ A LineTuple instance on success, or None on failure.
+ """
+ if not self._in_section:
+ self._in_section = line.startswith('memory map:')
+ return None
+
+ m = self._re_map_section.match(line)
+ if not m:
+ self._in_section = False # End of memory map section
+ return None
+
+ # Only accept .apk and .so files that are not from the system partitions.
+ file_path = m.group('file_path')
+ if not file_path:
+ return None
+
+ if file_path.startswith('/system') or file_path.startswith('/vendor'):
+ return None
+
+ if not (file_path.endswith('.apk') or file_path.endswith('.so')):
+ return None
+
+ addr_start = int(m.group('addr_start'), 16)
+ file_offset = int(m.group('file_offset'), 16)
+ file_size = int(m.group('file_size'), 16)
+
+ return self.LineTuple(addr_start, file_offset, file_size, file_path, m)
+
+ def Dump(self):
+ """Print memory map for debugging."""
+ print('MEMORY MAP [')
+ for t in self._addr_map:
+ print('[%08x-%08x %08x %08x %s]' %
+ (t.addr_start, t.addr_start + t.file_size, t.file_size,
+ t.file_offset, t.file_path))
+ print('] MEMORY MAP')
+
+ def FindSectionForAddress(self, addr):
+ """Find the map section corresponding to a specific memory address.
+
+ Call this method only after using ParseLines() was called to extract
+ relevant information from the memory map.
+
+ Args:
+ addr: Memory address
+ Returns:
+ A SectionTuple instance on success, or None on failure.
+ """
+ pos = bisect.bisect_right(self._sorted_addresses, addr)
+ if pos > 0:
+ # All values in [0,pos) are <= addr, just ensure that the last
+ # one contains the address as well.
+ entry = self._addr_map[pos - 1]
+ if entry.addr_start + entry.file_size > addr:
+ return self.SectionTuple(entry.addr_start, entry.file_size,
+ entry.file_offset, entry.file_path)
+ return None
+
+
+class BacktraceTranslator(object):
+ """Translates backtrace-related lines in a tombstone or crash report.
+
+ Usage is the following:
+ 1) Create new instance with appropriate arguments.
+ 2) If the tombstone / logcat input is available, one can call
+ FindLibraryOffsets() in order to detect which library offsets
+ will need to be symbolized during a future parse. Doing so helps
+ speed up the ELF symbolizer.
+ 3) For each tombstone/logcat input line, call TranslateLine() to
+ try to detect and symbolize backtrace lines.
+ """
+
+ # A named tuple for relevant input backtrace lines.
+ # Fields:
+ # rel_pc: Instruction pointer, relative to offset in library start.
+ # location: Library or APK file path.
+ # offset: Load base of executable code in library or apk file path.
+ # match: The corresponding regular expression match object.
+ # Note:
+ # The actual instruction pointer always matches the position at
+ # |offset + rel_pc| in |location|.
+ LineTuple = collections.namedtuple('BacktraceLineTuple',
+ 'rel_pc,location,offset,match')
+
+ def __init__(self, android_abi, apk_translator):
+ """Initialize instance.
+
+ Args:
+ android_abi: Android CPU ABI name (e.g. 'armeabi-v7a').
+ apk_translator: ApkLibraryPathTranslator instance used to convert
+ mapped APK file offsets into uncompressed library file paths with
+ new offsets.
+ """
+ hex_addr = _HexAddressRegexpFor(android_abi)
+
+ # A regular expression used to match backtrace lines.
+ self._re_backtrace = re.compile(
+ r'.*#(?P<frame>[0-9]{2})\s+' +
+ r'(..)\s+' +
+ r'(?P<rel_pc>' + hex_addr + r')\s+' +
+ r'(?P<location>[^ \t]+)' +
+ r'(\s+\(offset 0x(?P<offset>[0-9a-f]+)\))?')
+
+ # In certain cases, offset will be provided as <location>+0x<offset>
+ # instead of <location> (offset 0x<offset>). This is a regexp to detect
+ # this.
+ self._re_location_offset = re.compile(
+ r'.*\+0x(?P<offset>[0-9a-f]+)$')
+
+ self._apk_translator = apk_translator
+ self._in_section = False
+
+ def _ParseLine(self, line):
+ """Used internally to detect and decompose backtrace input lines.
+
+ Args:
+ line: input tombstone line.
+ Returns:
+ A LineTuple instance on success, None on failure.
+ """
+ if not self._in_section:
+ self._in_section = line.startswith('backtrace:')
+ return None
+
+ line = line.rstrip()
+ m = self._re_backtrace.match(line)
+ if not m:
+ self._in_section = False
+ return None
+
+ location = m.group('location')
+ offset = m.group('offset')
+ if not offset:
+ m2 = self._re_location_offset.match(location)
+ if m2:
+ offset = m2.group('offset')
+ location = location[0:m2.start('offset') - 3]
+
+ if not offset:
+ return None
+
+ offset = int(offset, 16)
+ rel_pc = int(m.group('rel_pc'), 16)
+
+ # Two cases to consider here:
+ #
+ # * If this is a library file directly mapped in memory, then |rel_pc|
+ # if the direct offset within the library, and doesn't need any kind
+ # of adjustement.
+ #
+ # * If this is a library mapped directly from an .apk file, then
+ # |rel_pc| is the offset in the APK, and |offset| happens to be the
+ # load base of the corresponding library.
+ #
+ if location.endswith('.so'):
+ # For a native library directly mapped from the file system,
+ return self.LineTuple(rel_pc, location, offset, m)
+
+ if location.endswith('.apk'):
+ # For a native library inside an memory-mapped APK file,
+ new_location, new_offset = self._apk_translator.TranslatePath(
+ location, offset)
+
+ return self.LineTuple(rel_pc, new_location, new_offset, m)
+
+ # Ignore anything else (e.g. .oat or .odex files).
+ return None
+
+ def FindLibraryOffsets(self, input_lines, in_section=False):
+ """Parse a tombstone's backtrace section and find all library offsets in it.
+
+ Args:
+ input_lines: List or iterables of intput tombstone lines.
+ in_section: Optional. If True, considers that the stack section has
+ already started.
+ Returns:
+ A dictionary mapping device library paths to sets of offsets within
+ then.
+ """
+ self._in_section = in_section
+ result = collections.defaultdict(set)
+ for line in input_lines:
+ t = self._ParseLine(line)
+ if not t:
+ continue
+
+ result[t.location].add(t.offset + t.rel_pc)
+ return result
+
+ def TranslateLine(self, line, symbol_resolver):
+ """Symbolize backtrace line if recognized.
+
+ Args:
+ line: input backtrace line.
+ symbol_resolver: symbol resolver instance to use. This method will
+ call its FindSymbolInfo(device_lib_path, lib_offset) method to
+ convert offsets into symbol informations strings.
+ Returns:
+ Translated line (unchanged if not recognized as a back trace).
+ """
+ t = self._ParseLine(line)
+ if not t:
+ return line
+
+ symbol_info = symbol_resolver.FindSymbolInfo(t.location,
+ t.offset + t.rel_pc)
+ if not symbol_info:
+ symbol_info = 'offset 0x%x' % t.offset
+
+ pos = t.match.start('location')
+ pos2 = t.match.end('offset') + 1
+ if pos2 <= 0:
+ pos2 = t.match.end('location')
+ return '%s%s (%s)%s' % (line[:pos], t.location, symbol_info, line[pos2:])
+
+
+class StackTranslator(object):
+ """Translates stack-related lines in a tombstone or crash report."""
+
+ # A named tuple describing relevant stack input lines.
+ # Fields:
+ # address: Address as it appears in the stack.
+ # lib_path: Library path where |address| is mapped.
+ # lib_offset: Library load base offset. for |lib_path|.
+ # match: Corresponding regular expression match object.
+ LineTuple = collections.namedtuple('StackLineTuple',
+ 'address, lib_path, lib_offset, match')
+
+ def __init__(self, android_abi, memory_map, apk_translator):
+ """Initialize instance."""
+ hex_addr = _HexAddressRegexpFor(android_abi)
+
+ # pylint: disable=line-too-long
+ # A regular expression used to recognize stack entries like:
+ #
+ # #05 bf89a180 bf89a1e4 [stack]
+ # bf89a1c8 a0c01c51 /data/app/com.google.android.apps.chrome-2/base.apk
+ # bf89a080 00000000
+ # ........ ........
+ # pylint: enable=line-too-long
+ self._re_stack_line = re.compile(
+ r'\s+(?P<frame_number>#[0-9]+)?\s*' +
+ r'(?P<stack_addr>' + hex_addr + r')\s+' +
+ r'(?P<stack_value>' + hex_addr + r')' +
+ r'(\s+(?P<location>[^ \t]+))?')
+
+ self._re_stack_abbrev = re.compile(r'\s+[.]+\s+[.]+')
+
+ self._memory_map = memory_map
+ self._apk_translator = apk_translator
+ self._in_section = False
+
+ def _ParseLine(self, line):
+ """Check a given input line for a relevant _re_stack_line match.
+
+ Args:
+ line: input tombstone line.
+ Returns:
+ A LineTuple instance on success, None on failure.
+ """
+ line = line.rstrip()
+ if not self._in_section:
+ self._in_section = line.startswith('stack:')
+ return None
+
+ m = self._re_stack_line.match(line)
+ if not m:
+ if not self._re_stack_abbrev.match(line):
+ self._in_section = False
+ return None
+
+ location = m.group('location')
+ if not location:
+ return None
+
+ if not location.endswith('.apk') and not location.endswith('.so'):
+ return None
+
+ addr = int(m.group('stack_value'), 16)
+ t = self._memory_map.FindSectionForAddress(addr)
+ if t is None:
+ return None
+
+ lib_path = t.path
+ lib_offset = t.offset + (addr - t.address)
+
+ if lib_path.endswith('.apk'):
+ lib_path, lib_offset = self._apk_translator.TranslatePath(
+ lib_path, lib_offset)
+
+ return self.LineTuple(addr, lib_path, lib_offset, m)
+
+ def FindLibraryOffsets(self, input_lines, in_section=False):
+ """Parse a tombstone's stack section and find all library offsets in it.
+
+ Args:
+ input_lines: List or iterables of intput tombstone lines.
+ in_section: Optional. If True, considers that the stack section has
+ already started.
+ Returns:
+ A dictionary mapping device library paths to sets of offsets within
+ then.
+ """
+ result = collections.defaultdict(set)
+ self._in_section = in_section
+ for line in input_lines:
+ t = self._ParseLine(line)
+ if t:
+ result[t.lib_path].add(t.lib_offset)
+ return result
+
+ def TranslateLine(self, line, symbol_resolver=None):
+ """Try to translate a line of the stack dump."""
+ t = self._ParseLine(line)
+ if not t:
+ return line
+
+ symbol_info = symbol_resolver.FindSymbolInfo(t.lib_path, t.lib_offset)
+ if not symbol_info:
+ return line
+
+ pos = t.match.start('location')
+ pos2 = t.match.end('location')
+ return '%s%s (%s)%s' % (line[:pos], t.lib_path, symbol_info, line[pos2:])
diff --git a/third_party/libwebrtc/build/android/pylib/symbols/symbol_utils_unittest.py b/third_party/libwebrtc/build/android/pylib/symbols/symbol_utils_unittest.py
new file mode 100755
index 0000000000..2ec81f96fa
--- /dev/null
+++ b/third_party/libwebrtc/build/android/pylib/symbols/symbol_utils_unittest.py
@@ -0,0 +1,942 @@
+#!/usr/bin/env vpython3
+# Copyright 2018 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 collections
+import contextlib
+import logging
+import os
+import re
+import shutil
+import tempfile
+import unittest
+
+from pylib.symbols import apk_native_libs_unittest
+from pylib.symbols import mock_addr2line
+from pylib.symbols import symbol_utils
+
+_MOCK_ELF_DATA = apk_native_libs_unittest.MOCK_ELF_DATA
+
+_MOCK_A2L_PATH = os.path.join(os.path.dirname(mock_addr2line.__file__),
+ 'mock_addr2line')
+
+
+# pylint: disable=line-too-long
+
+# list of (start_offset, end_offset, size, libpath) tuples corresponding
+# to the content of base.apk. This was taken from an x86 ChromeModern.apk
+# component build.
+_TEST_APK_LIBS = [
+ (0x01331000, 0x013696bc, 0x000386bc, 'libaccessibility.cr.so'),
+ (0x0136a000, 0x013779c4, 0x0000d9c4, 'libanimation.cr.so'),
+ (0x01378000, 0x0137f7e8, 0x000077e8, 'libapdu.cr.so'),
+ (0x01380000, 0x0155ccc8, 0x001dccc8, 'libbase.cr.so'),
+ (0x0155d000, 0x015ab98c, 0x0004e98c, 'libbase_i18n.cr.so'),
+ (0x015ac000, 0x015dff4c, 0x00033f4c, 'libbindings.cr.so'),
+ (0x015e0000, 0x015f5a54, 0x00015a54, 'libbindings_base.cr.so'),
+ (0x0160e000, 0x01731960, 0x00123960, 'libblink_common.cr.so'),
+ (0x01732000, 0x0174ce54, 0x0001ae54, 'libblink_controller.cr.so'),
+ (0x0174d000, 0x0318c528, 0x01a3f528, 'libblink_core.cr.so'),
+ (0x0318d000, 0x03191700, 0x00004700, 'libblink_mojom_broadcastchannel_bindings_shared.cr.so'),
+ (0x03192000, 0x03cd7918, 0x00b45918, 'libblink_modules.cr.so'),
+ (0x03cd8000, 0x03d137d0, 0x0003b7d0, 'libblink_mojo_bindings_shared.cr.so'),
+ (0x03d14000, 0x03d2670c, 0x0001270c, 'libblink_offscreen_canvas_mojo_bindings_shared.cr.so'),
+ (0x03d27000, 0x046c7054, 0x009a0054, 'libblink_platform.cr.so'),
+ (0x046c8000, 0x0473fbfc, 0x00077bfc, 'libbluetooth.cr.so'),
+ (0x04740000, 0x04878f40, 0x00138f40, 'libboringssl.cr.so'),
+ (0x04879000, 0x0498466c, 0x0010b66c, 'libc++_shared.so'),
+ (0x04985000, 0x0498d93c, 0x0000893c, 'libcaptive_portal.cr.so'),
+ (0x0498e000, 0x049947cc, 0x000067cc, 'libcapture_base.cr.so'),
+ (0x04995000, 0x04b39f18, 0x001a4f18, 'libcapture_lib.cr.so'),
+ (0x04b3a000, 0x04b488ec, 0x0000e8ec, 'libcbor.cr.so'),
+ (0x04b49000, 0x04e9ea5c, 0x00355a5c, 'libcc.cr.so'),
+ (0x04e9f000, 0x04ed6404, 0x00037404, 'libcc_animation.cr.so'),
+ (0x04ed7000, 0x04ef5ab4, 0x0001eab4, 'libcc_base.cr.so'),
+ (0x04ef6000, 0x04fd9364, 0x000e3364, 'libcc_blink.cr.so'),
+ (0x04fda000, 0x04fe2758, 0x00008758, 'libcc_debug.cr.so'),
+ (0x04fe3000, 0x0500ae0c, 0x00027e0c, 'libcc_ipc.cr.so'),
+ (0x0500b000, 0x05078f38, 0x0006df38, 'libcc_paint.cr.so'),
+ (0x05079000, 0x0507e734, 0x00005734, 'libcdm_manager.cr.so'),
+ (0x0507f000, 0x06f4d744, 0x01ece744, 'libchrome.cr.so'),
+ (0x06f54000, 0x06feb830, 0x00097830, 'libchromium_sqlite3.cr.so'),
+ (0x06fec000, 0x0706f554, 0x00083554, 'libclient.cr.so'),
+ (0x07070000, 0x0708da60, 0x0001da60, 'libcloud_policy_proto_generated_compile.cr.so'),
+ (0x0708e000, 0x07121f28, 0x00093f28, 'libcodec.cr.so'),
+ (0x07122000, 0x07134ab8, 0x00012ab8, 'libcolor_space.cr.so'),
+ (0x07135000, 0x07138614, 0x00003614, 'libcommon.cr.so'),
+ (0x07139000, 0x0717c938, 0x00043938, 'libcompositor.cr.so'),
+ (0x0717d000, 0x0923d78c, 0x020c078c, 'libcontent.cr.so'),
+ (0x0923e000, 0x092ae87c, 0x0007087c, 'libcontent_common_mojo_bindings_shared.cr.so'),
+ (0x092af000, 0x092be718, 0x0000f718, 'libcontent_public_common_mojo_bindings_shared.cr.so'),
+ (0x092bf000, 0x092d9a20, 0x0001aa20, 'libcrash_key.cr.so'),
+ (0x092da000, 0x092eda58, 0x00013a58, 'libcrcrypto.cr.so'),
+ (0x092ee000, 0x092f16e0, 0x000036e0, 'libdevice_base.cr.so'),
+ (0x092f2000, 0x092fe8d8, 0x0000c8d8, 'libdevice_event_log.cr.so'),
+ (0x092ff000, 0x093026a4, 0x000036a4, 'libdevice_features.cr.so'),
+ (0x09303000, 0x093f1220, 0x000ee220, 'libdevice_gamepad.cr.so'),
+ (0x093f2000, 0x09437f54, 0x00045f54, 'libdevice_vr_mojo_bindings.cr.so'),
+ (0x09438000, 0x0954c168, 0x00114168, 'libdevice_vr_mojo_bindings_blink.cr.so'),
+ (0x0954d000, 0x0955d720, 0x00010720, 'libdevice_vr_mojo_bindings_shared.cr.so'),
+ (0x0955e000, 0x0956b9c0, 0x0000d9c0, 'libdevices.cr.so'),
+ (0x0956c000, 0x0957cae8, 0x00010ae8, 'libdiscardable_memory_client.cr.so'),
+ (0x0957d000, 0x09588854, 0x0000b854, 'libdiscardable_memory_common.cr.so'),
+ (0x09589000, 0x0959cbb4, 0x00013bb4, 'libdiscardable_memory_service.cr.so'),
+ (0x0959d000, 0x095b6b90, 0x00019b90, 'libdisplay.cr.so'),
+ (0x095b7000, 0x095be930, 0x00007930, 'libdisplay_types.cr.so'),
+ (0x095bf000, 0x095c46c4, 0x000056c4, 'libdisplay_util.cr.so'),
+ (0x095c5000, 0x095f54a4, 0x000304a4, 'libdomain_reliability.cr.so'),
+ (0x095f6000, 0x0966fe08, 0x00079e08, 'libembedder.cr.so'),
+ (0x09670000, 0x096735f8, 0x000035f8, 'libembedder_switches.cr.so'),
+ (0x09674000, 0x096a3460, 0x0002f460, 'libevents.cr.so'),
+ (0x096a4000, 0x096b6d40, 0x00012d40, 'libevents_base.cr.so'),
+ (0x096b7000, 0x0981a778, 0x00163778, 'libffmpeg.cr.so'),
+ (0x0981b000, 0x09945c94, 0x0012ac94, 'libfido.cr.so'),
+ (0x09946000, 0x09a330dc, 0x000ed0dc, 'libfingerprint.cr.so'),
+ (0x09a34000, 0x09b53170, 0x0011f170, 'libfreetype_harfbuzz.cr.so'),
+ (0x09b54000, 0x09bc5c5c, 0x00071c5c, 'libgcm.cr.so'),
+ (0x09bc6000, 0x09cc8584, 0x00102584, 'libgeolocation.cr.so'),
+ (0x09cc9000, 0x09cdc8d4, 0x000138d4, 'libgeometry.cr.so'),
+ (0x09cdd000, 0x09cec8b4, 0x0000f8b4, 'libgeometry_skia.cr.so'),
+ (0x09ced000, 0x09d10e14, 0x00023e14, 'libgesture_detection.cr.so'),
+ (0x09d11000, 0x09d7595c, 0x0006495c, 'libgfx.cr.so'),
+ (0x09d76000, 0x09d7d7cc, 0x000077cc, 'libgfx_ipc.cr.so'),
+ (0x09d7e000, 0x09d82708, 0x00004708, 'libgfx_ipc_buffer_types.cr.so'),
+ (0x09d83000, 0x09d89748, 0x00006748, 'libgfx_ipc_color.cr.so'),
+ (0x09d8a000, 0x09d8f6f4, 0x000056f4, 'libgfx_ipc_geometry.cr.so'),
+ (0x09d90000, 0x09d94754, 0x00004754, 'libgfx_ipc_skia.cr.so'),
+ (0x09d95000, 0x09d9869c, 0x0000369c, 'libgfx_switches.cr.so'),
+ (0x09d99000, 0x09dba0ac, 0x000210ac, 'libgin.cr.so'),
+ (0x09dbb000, 0x09e0a8cc, 0x0004f8cc, 'libgl_in_process_context.cr.so'),
+ (0x09e0b000, 0x09e17a18, 0x0000ca18, 'libgl_init.cr.so'),
+ (0x09e18000, 0x09ee34e4, 0x000cb4e4, 'libgl_wrapper.cr.so'),
+ (0x09ee4000, 0x0a1a2e00, 0x002bee00, 'libgles2.cr.so'),
+ (0x0a1a3000, 0x0a24556c, 0x000a256c, 'libgles2_implementation.cr.so'),
+ (0x0a246000, 0x0a267038, 0x00021038, 'libgles2_utils.cr.so'),
+ (0x0a268000, 0x0a3288e4, 0x000c08e4, 'libgpu.cr.so'),
+ (0x0a329000, 0x0a3627ec, 0x000397ec, 'libgpu_ipc_service.cr.so'),
+ (0x0a363000, 0x0a388a18, 0x00025a18, 'libgpu_util.cr.so'),
+ (0x0a389000, 0x0a506d8c, 0x0017dd8c, 'libhost.cr.so'),
+ (0x0a507000, 0x0a6f0ec0, 0x001e9ec0, 'libicui18n.cr.so'),
+ (0x0a6f1000, 0x0a83b4c8, 0x0014a4c8, 'libicuuc.cr.so'),
+ (0x0a83c000, 0x0a8416e4, 0x000056e4, 'libinterfaces_shared.cr.so'),
+ (0x0a842000, 0x0a87e2a0, 0x0003c2a0, 'libipc.cr.so'),
+ (0x0a87f000, 0x0a88c98c, 0x0000d98c, 'libipc_mojom.cr.so'),
+ (0x0a88d000, 0x0a8926e4, 0x000056e4, 'libipc_mojom_shared.cr.so'),
+ (0x0a893000, 0x0a8a1e18, 0x0000ee18, 'libkeyed_service_content.cr.so'),
+ (0x0a8a2000, 0x0a8b4a30, 0x00012a30, 'libkeyed_service_core.cr.so'),
+ (0x0a8b5000, 0x0a930a80, 0x0007ba80, 'libleveldatabase.cr.so'),
+ (0x0a931000, 0x0a9b3908, 0x00082908, 'libmanager.cr.so'),
+ (0x0a9b4000, 0x0aea9bb4, 0x004f5bb4, 'libmedia.cr.so'),
+ (0x0aeaa000, 0x0b08cb88, 0x001e2b88, 'libmedia_blink.cr.so'),
+ (0x0b08d000, 0x0b0a4728, 0x00017728, 'libmedia_devices_mojo_bindings_shared.cr.so'),
+ (0x0b0a5000, 0x0b1943ec, 0x000ef3ec, 'libmedia_gpu.cr.so'),
+ (0x0b195000, 0x0b2d07d4, 0x0013b7d4, 'libmedia_mojo_services.cr.so'),
+ (0x0b2d1000, 0x0b2d4760, 0x00003760, 'libmessage_center.cr.so'),
+ (0x0b2d5000, 0x0b2e0938, 0x0000b938, 'libmessage_support.cr.so'),
+ (0x0b2e1000, 0x0b2f3ad0, 0x00012ad0, 'libmetrics_cpp.cr.so'),
+ (0x0b2f4000, 0x0b313bb8, 0x0001fbb8, 'libmidi.cr.so'),
+ (0x0b314000, 0x0b31b848, 0x00007848, 'libmojo_base_lib.cr.so'),
+ (0x0b31c000, 0x0b3329f8, 0x000169f8, 'libmojo_base_mojom.cr.so'),
+ (0x0b333000, 0x0b34b98c, 0x0001898c, 'libmojo_base_mojom_blink.cr.so'),
+ (0x0b34c000, 0x0b354700, 0x00008700, 'libmojo_base_mojom_shared.cr.so'),
+ (0x0b355000, 0x0b3608b0, 0x0000b8b0, 'libmojo_base_shared_typemap_traits.cr.so'),
+ (0x0b361000, 0x0b3ad454, 0x0004c454, 'libmojo_edk.cr.so'),
+ (0x0b3ae000, 0x0b3c4a20, 0x00016a20, 'libmojo_edk_ports.cr.so'),
+ (0x0b3c5000, 0x0b3d38a0, 0x0000e8a0, 'libmojo_mojom_bindings.cr.so'),
+ (0x0b3d4000, 0x0b3da6e8, 0x000066e8, 'libmojo_mojom_bindings_shared.cr.so'),
+ (0x0b3db000, 0x0b3e27f0, 0x000077f0, 'libmojo_public_system.cr.so'),
+ (0x0b3e3000, 0x0b3fa9fc, 0x000179fc, 'libmojo_public_system_cpp.cr.so'),
+ (0x0b3fb000, 0x0b407728, 0x0000c728, 'libmojom_core_shared.cr.so'),
+ (0x0b408000, 0x0b421744, 0x00019744, 'libmojom_platform_shared.cr.so'),
+ (0x0b422000, 0x0b43451c, 0x0001251c, 'libnative_theme.cr.so'),
+ (0x0b435000, 0x0baaa1bc, 0x006751bc, 'libnet.cr.so'),
+ (0x0bac4000, 0x0bb74670, 0x000b0670, 'libnetwork_cpp.cr.so'),
+ (0x0bb75000, 0x0bbaee8c, 0x00039e8c, 'libnetwork_cpp_base.cr.so'),
+ (0x0bbaf000, 0x0bd21844, 0x00172844, 'libnetwork_service.cr.so'),
+ (0x0bd22000, 0x0bd256e4, 0x000036e4, 'libnetwork_session_configurator.cr.so'),
+ (0x0bd26000, 0x0bd33734, 0x0000d734, 'libonc.cr.so'),
+ (0x0bd34000, 0x0bd9ce18, 0x00068e18, 'libperfetto.cr.so'),
+ (0x0bd9d000, 0x0bda4854, 0x00007854, 'libplatform.cr.so'),
+ (0x0bda5000, 0x0bec5ce4, 0x00120ce4, 'libpolicy_component.cr.so'),
+ (0x0bec6000, 0x0bf5ab58, 0x00094b58, 'libpolicy_proto.cr.so'),
+ (0x0bf5b000, 0x0bf86fbc, 0x0002bfbc, 'libprefs.cr.so'),
+ (0x0bf87000, 0x0bfa5d74, 0x0001ed74, 'libprinting.cr.so'),
+ (0x0bfa6000, 0x0bfe0e80, 0x0003ae80, 'libprotobuf_lite.cr.so'),
+ (0x0bfe1000, 0x0bff0a18, 0x0000fa18, 'libproxy_config.cr.so'),
+ (0x0bff1000, 0x0c0f6654, 0x00105654, 'libpublic.cr.so'),
+ (0x0c0f7000, 0x0c0fa6a4, 0x000036a4, 'librange.cr.so'),
+ (0x0c0fb000, 0x0c118058, 0x0001d058, 'libraster.cr.so'),
+ (0x0c119000, 0x0c133d00, 0x0001ad00, 'libresource_coordinator_cpp.cr.so'),
+ (0x0c134000, 0x0c1396a0, 0x000056a0, 'libresource_coordinator_cpp_base.cr.so'),
+ (0x0c13a000, 0x0c1973b8, 0x0005d3b8, 'libresource_coordinator_public_mojom.cr.so'),
+ (0x0c198000, 0x0c2033e8, 0x0006b3e8, 'libresource_coordinator_public_mojom_blink.cr.so'),
+ (0x0c204000, 0x0c219744, 0x00015744, 'libresource_coordinator_public_mojom_shared.cr.so'),
+ (0x0c21a000, 0x0c21e700, 0x00004700, 'libsandbox.cr.so'),
+ (0x0c21f000, 0x0c22f96c, 0x0001096c, 'libsandbox_services.cr.so'),
+ (0x0c230000, 0x0c249d58, 0x00019d58, 'libseccomp_bpf.cr.so'),
+ (0x0c24a000, 0x0c24e714, 0x00004714, 'libseccomp_starter_android.cr.so'),
+ (0x0c24f000, 0x0c4ae9f0, 0x0025f9f0, 'libservice.cr.so'),
+ (0x0c4af000, 0x0c4c3ae4, 0x00014ae4, 'libservice_manager_cpp.cr.so'),
+ (0x0c4c4000, 0x0c4cb708, 0x00007708, 'libservice_manager_cpp_types.cr.so'),
+ (0x0c4cc000, 0x0c4fbe30, 0x0002fe30, 'libservice_manager_mojom.cr.so'),
+ (0x0c4fc000, 0x0c532e78, 0x00036e78, 'libservice_manager_mojom_blink.cr.so'),
+ (0x0c533000, 0x0c53669c, 0x0000369c, 'libservice_manager_mojom_constants.cr.so'),
+ (0x0c537000, 0x0c53e85c, 0x0000785c, 'libservice_manager_mojom_constants_blink.cr.so'),
+ (0x0c53f000, 0x0c542668, 0x00003668, 'libservice_manager_mojom_constants_shared.cr.so'),
+ (0x0c543000, 0x0c54d700, 0x0000a700, 'libservice_manager_mojom_shared.cr.so'),
+ (0x0c54e000, 0x0c8fc6ec, 0x003ae6ec, 'libsessions.cr.so'),
+ (0x0c8fd000, 0x0c90a924, 0x0000d924, 'libshared_memory_support.cr.so'),
+ (0x0c90b000, 0x0c9148ec, 0x000098ec, 'libshell_dialogs.cr.so'),
+ (0x0c915000, 0x0cf8de70, 0x00678e70, 'libskia.cr.so'),
+ (0x0cf8e000, 0x0cf978bc, 0x000098bc, 'libsnapshot.cr.so'),
+ (0x0cf98000, 0x0cfb7d9c, 0x0001fd9c, 'libsql.cr.so'),
+ (0x0cfb8000, 0x0cfbe744, 0x00006744, 'libstartup_tracing.cr.so'),
+ (0x0cfbf000, 0x0d19b4e4, 0x001dc4e4, 'libstorage_browser.cr.so'),
+ (0x0d19c000, 0x0d2a773c, 0x0010b73c, 'libstorage_common.cr.so'),
+ (0x0d2a8000, 0x0d2ac6fc, 0x000046fc, 'libsurface.cr.so'),
+ (0x0d2ad000, 0x0d2baa98, 0x0000da98, 'libtracing.cr.so'),
+ (0x0d2bb000, 0x0d2f36b0, 0x000386b0, 'libtracing_cpp.cr.so'),
+ (0x0d2f4000, 0x0d326e70, 0x00032e70, 'libtracing_mojom.cr.so'),
+ (0x0d327000, 0x0d33270c, 0x0000b70c, 'libtracing_mojom_shared.cr.so'),
+ (0x0d333000, 0x0d46d804, 0x0013a804, 'libui_android.cr.so'),
+ (0x0d46e000, 0x0d4cb3f8, 0x0005d3f8, 'libui_base.cr.so'),
+ (0x0d4cc000, 0x0d4dbc40, 0x0000fc40, 'libui_base_ime.cr.so'),
+ (0x0d4dc000, 0x0d4e58d4, 0x000098d4, 'libui_data_pack.cr.so'),
+ (0x0d4e6000, 0x0d51d1e0, 0x000371e0, 'libui_devtools.cr.so'),
+ (0x0d51e000, 0x0d52b984, 0x0000d984, 'libui_message_center_cpp.cr.so'),
+ (0x0d52c000, 0x0d539a48, 0x0000da48, 'libui_touch_selection.cr.so'),
+ (0x0d53a000, 0x0d55bc60, 0x00021c60, 'liburl.cr.so'),
+ (0x0d55c000, 0x0d55f6b4, 0x000036b4, 'liburl_ipc.cr.so'),
+ (0x0d560000, 0x0d5af110, 0x0004f110, 'liburl_matcher.cr.so'),
+ (0x0d5b0000, 0x0d5e2fac, 0x00032fac, 'libuser_manager.cr.so'),
+ (0x0d5e3000, 0x0d5e66e4, 0x000036e4, 'libuser_prefs.cr.so'),
+ (0x0d5e7000, 0x0e3e1cc8, 0x00dfacc8, 'libv8.cr.so'),
+ (0x0e3e2000, 0x0e400ae0, 0x0001eae0, 'libv8_libbase.cr.so'),
+ (0x0e401000, 0x0e4d91d4, 0x000d81d4, 'libviz_common.cr.so'),
+ (0x0e4da000, 0x0e4df7e4, 0x000057e4, 'libviz_resource_format.cr.so'),
+ (0x0e4e0000, 0x0e5b7120, 0x000d7120, 'libweb_dialogs.cr.so'),
+ (0x0e5b8000, 0x0e5c7a18, 0x0000fa18, 'libwebdata_common.cr.so'),
+ (0x0e5c8000, 0x0e61bfe4, 0x00053fe4, 'libwtf.cr.so'),
+]
+
+
+# A small memory map fragment extracted from a tombstone for a process that
+# had loaded the APK corresponding to _TEST_APK_LIBS above.
+_TEST_MEMORY_MAP = r'''memory map:
+12c00000-12ccafff rw- 0 cb000 /dev/ashmem/dalvik-main space (deleted)
+12ccb000-130cafff rw- cb000 400000 /dev/ashmem/dalvik-main space (deleted)
+130cb000-32bfffff --- 4cb000 1fb35000 /dev/ashmem/dalvik-main space (deleted)
+32c00000-32c00fff rw- 0 1000 /dev/ashmem/dalvik-main space 1 (deleted)
+32c01000-52bfffff --- 1000 1ffff000 /dev/ashmem/dalvik-main space 1 (deleted)
+6f3b8000-6fd90fff rw- 0 9d9000 /data/dalvik-cache/x86/system@framework@boot.art
+6fd91000-71c42fff r-- 0 1eb2000 /data/dalvik-cache/x86/system@framework@boot.oat
+71c43000-7393efff r-x 1eb2000 1cfc000 /data/dalvik-cache/x86/system@framework@boot.oat (load base 0x71c43000)
+7393f000-7393ffff rw- 3bae000 1000 /data/dalvik-cache/x86/system@framework@boot.oat
+73940000-73a1bfff rw- 0 dc000 /dev/ashmem/dalvik-zygote space (deleted)
+73a1c000-73a1cfff rw- 0 1000 /dev/ashmem/dalvik-non moving space (deleted)
+73a1d000-73a2dfff rw- 1000 11000 /dev/ashmem/dalvik-non moving space (deleted)
+73a2e000-77540fff --- 12000 3b13000 /dev/ashmem/dalvik-non moving space (deleted)
+77541000-7793ffff rw- 3b25000 3ff000 /dev/ashmem/dalvik-non moving space (deleted)
+923aa000-92538fff r-- 8a9000 18f000 /data/app/com.example.app-2/base.apk
+92539000-9255bfff r-- 0 23000 /data/data/com.example.app/app_data/paks/es.pak@162db1c6689
+9255c000-92593fff r-- 213000 38000 /data/app/com.example.app-2/base.apk
+92594000-925c0fff r-- 87d000 2d000 /data/app/com.example.app-2/base.apk
+925c1000-927d3fff r-- a37000 213000 /data/app/com.example.app-2/base.apk
+927d4000-92e07fff r-- 24a000 634000 /data/app/com.example.app-2/base.apk
+92e08000-92e37fff r-- a931000 30000 /data/app/com.example.app-2/base.apk
+92e38000-92e86fff r-x a961000 4f000 /data/app/com.example.app-2/base.apk
+92e87000-92e8afff rw- a9b0000 4000 /data/app/com.example.app-2/base.apk
+92e8b000-92e8bfff rw- 0 1000
+92e8c000-92e9dfff r-- d5b0000 12000 /data/app/com.example.app-2/base.apk
+92e9e000-92ebcfff r-x d5c2000 1f000 /data/app/com.example.app-2/base.apk
+92ebd000-92ebefff rw- d5e1000 2000 /data/app/com.example.app-2/base.apk
+92ebf000-92ebffff rw- 0 1000
+'''
+
+# list of (address, size, path, offset) tuples that must appear in
+# _TEST_MEMORY_MAP. Not all sections need to be listed.
+_TEST_MEMORY_MAP_SECTIONS = [
+ (0x923aa000, 0x18f000, '/data/app/com.example.app-2/base.apk', 0x8a9000),
+ (0x9255c000, 0x038000, '/data/app/com.example.app-2/base.apk', 0x213000),
+ (0x92594000, 0x02d000, '/data/app/com.example.app-2/base.apk', 0x87d000),
+ (0x925c1000, 0x213000, '/data/app/com.example.app-2/base.apk', 0xa37000),
+]
+
+_EXPECTED_TEST_MEMORY_MAP = r'''memory map:
+12c00000-12ccafff rw- 0 cb000 /dev/ashmem/dalvik-main space (deleted)
+12ccb000-130cafff rw- cb000 400000 /dev/ashmem/dalvik-main space (deleted)
+130cb000-32bfffff --- 4cb000 1fb35000 /dev/ashmem/dalvik-main space (deleted)
+32c00000-32c00fff rw- 0 1000 /dev/ashmem/dalvik-main space 1 (deleted)
+32c01000-52bfffff --- 1000 1ffff000 /dev/ashmem/dalvik-main space 1 (deleted)
+6f3b8000-6fd90fff rw- 0 9d9000 /data/dalvik-cache/x86/system@framework@boot.art
+6fd91000-71c42fff r-- 0 1eb2000 /data/dalvik-cache/x86/system@framework@boot.oat
+71c43000-7393efff r-x 1eb2000 1cfc000 /data/dalvik-cache/x86/system@framework@boot.oat (load base 0x71c43000)
+7393f000-7393ffff rw- 3bae000 1000 /data/dalvik-cache/x86/system@framework@boot.oat
+73940000-73a1bfff rw- 0 dc000 /dev/ashmem/dalvik-zygote space (deleted)
+73a1c000-73a1cfff rw- 0 1000 /dev/ashmem/dalvik-non moving space (deleted)
+73a1d000-73a2dfff rw- 1000 11000 /dev/ashmem/dalvik-non moving space (deleted)
+73a2e000-77540fff --- 12000 3b13000 /dev/ashmem/dalvik-non moving space (deleted)
+77541000-7793ffff rw- 3b25000 3ff000 /dev/ashmem/dalvik-non moving space (deleted)
+923aa000-92538fff r-- 8a9000 18f000 /data/app/com.example.app-2/base.apk
+92539000-9255bfff r-- 0 23000 /data/data/com.example.app/app_data/paks/es.pak@162db1c6689
+9255c000-92593fff r-- 213000 38000 /data/app/com.example.app-2/base.apk
+92594000-925c0fff r-- 87d000 2d000 /data/app/com.example.app-2/base.apk
+925c1000-927d3fff r-- a37000 213000 /data/app/com.example.app-2/base.apk
+927d4000-92e07fff r-- 24a000 634000 /data/app/com.example.app-2/base.apk
+92e08000-92e37fff r-- a931000 30000 /data/app/com.example.app-2/base.apk!lib/libmanager.cr.so (offset 0x0)
+92e38000-92e86fff r-x a961000 4f000 /data/app/com.example.app-2/base.apk!lib/libmanager.cr.so (offset 0x30000)
+92e87000-92e8afff rw- a9b0000 4000 /data/app/com.example.app-2/base.apk!lib/libmanager.cr.so (offset 0x7f000)
+92e8b000-92e8bfff rw- 0 1000
+92e8c000-92e9dfff r-- d5b0000 12000 /data/app/com.example.app-2/base.apk!lib/libuser_manager.cr.so (offset 0x0)
+92e9e000-92ebcfff r-x d5c2000 1f000 /data/app/com.example.app-2/base.apk!lib/libuser_manager.cr.so (offset 0x12000)
+92ebd000-92ebefff rw- d5e1000 2000 /data/app/com.example.app-2/base.apk!lib/libuser_manager.cr.so (offset 0x31000)
+92ebf000-92ebffff rw- 0 1000
+'''
+
+# Example stack section, taken from the same tombstone that _TEST_MEMORY_MAP
+# was extracted from.
+_TEST_STACK = r'''stack:
+ bf89a070 b7439468 /system/lib/libc.so
+ bf89a074 bf89a1e4 [stack]
+ bf89a078 932d4000 /data/app/com.example.app-2/base.apk
+ bf89a07c b73bfbc9 /system/lib/libc.so (pthread_mutex_lock+65)
+ bf89a080 00000000
+ bf89a084 4000671c /dev/ashmem/dalvik-main space 1 (deleted)
+ bf89a088 932d1d86 /data/app/com.example.app-2/base.apk
+ bf89a08c b743671c /system/lib/libc.so
+ bf89a090 b77f8c00 /system/bin/linker
+ bf89a094 b743cc90
+ bf89a098 932d1d4a /data/app/com.example.app-2/base.apk
+ bf89a09c b73bf271 /system/lib/libc.so (__pthread_internal_find(long)+65)
+ bf89a0a0 b743cc90
+ bf89a0a4 bf89a0b0 [stack]
+ bf89a0a8 bf89a0b8 [stack]
+ bf89a0ac 00000008
+ ........ ........
+ #00 bf89a0b0 00000006
+ bf89a0b4 00000002
+ bf89a0b8 b743671c /system/lib/libc.so
+ bf89a0bc b73bf5d9 /system/lib/libc.so (pthread_kill+71)
+ #01 bf89a0c0 00006937
+ bf89a0c4 00006937
+ bf89a0c8 00000006
+ bf89a0cc b77fd3a9 /system/bin/app_process32 (sigprocmask+141)
+ bf89a0d0 00000002
+ bf89a0d4 bf89a0ec [stack]
+ bf89a0d8 00000000
+ bf89a0dc b743671c /system/lib/libc.so
+ bf89a0e0 bf89a12c [stack]
+ bf89a0e4 bf89a1e4 [stack]
+ bf89a0e8 932d1d4a /data/app/com.example.app-2/base.apk
+ bf89a0ec b7365206 /system/lib/libc.so (raise+37)
+ #02 bf89a0f0 b77f8c00 /system/bin/linker
+ bf89a0f4 00000006
+ bf89a0f8 b7439468 /system/lib/libc.so
+ bf89a0fc b743671c /system/lib/libc.so
+ bf89a100 bf89a12c [stack]
+ bf89a104 b743671c /system/lib/libc.so
+ bf89a108 bf89a12c [stack]
+ bf89a10c b735e9e5 /system/lib/libc.so (abort+81)
+ #03 bf89a110 00000006
+ bf89a114 bf89a12c [stack]
+ bf89a118 00000000
+ bf89a11c b55a3d3b /system/lib/libprotobuf-cpp-lite.so (google::protobuf::internal::DefaultLogHandler(google::protobuf::LogLevel, char const*, int, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)+99)
+ bf89a120 b7439468 /system/lib/libc.so
+ bf89a124 b55ba38d /system/lib/libprotobuf-cpp-lite.so
+ bf89a128 b55ba408 /system/lib/libprotobuf-cpp-lite.so
+ bf89a12c ffffffdf
+ bf89a130 0000003d
+ bf89a134 adfedf00 [anon:libc_malloc]
+ bf89a138 bf89a158 [stack]
+ #04 bf89a13c a0cee7f0 /data/app/com.example.app-2/base.apk
+ bf89a140 b55c1cb0 /system/lib/libprotobuf-cpp-lite.so
+ bf89a144 bf89a1e4 [stack]
+'''
+
+# Expected value of _TEST_STACK after translation of addresses in the APK
+# into offsets into libraries.
+_EXPECTED_STACK = r'''stack:
+ bf89a070 b7439468 /system/lib/libc.so
+ bf89a074 bf89a1e4 [stack]
+ bf89a078 932d4000 /data/app/com.example.app-2/base.apk
+ bf89a07c b73bfbc9 /system/lib/libc.so (pthread_mutex_lock+65)
+ bf89a080 00000000
+ bf89a084 4000671c /dev/ashmem/dalvik-main space 1 (deleted)
+ bf89a088 932d1d86 /data/app/com.example.app-2/base.apk
+ bf89a08c b743671c /system/lib/libc.so
+ bf89a090 b77f8c00 /system/bin/linker
+ bf89a094 b743cc90
+ bf89a098 932d1d4a /data/app/com.example.app-2/base.apk
+ bf89a09c b73bf271 /system/lib/libc.so (__pthread_internal_find(long)+65)
+ bf89a0a0 b743cc90
+ bf89a0a4 bf89a0b0 [stack]
+ bf89a0a8 bf89a0b8 [stack]
+ bf89a0ac 00000008
+ ........ ........
+ #00 bf89a0b0 00000006
+ bf89a0b4 00000002
+ bf89a0b8 b743671c /system/lib/libc.so
+ bf89a0bc b73bf5d9 /system/lib/libc.so (pthread_kill+71)
+ #01 bf89a0c0 00006937
+ bf89a0c4 00006937
+ bf89a0c8 00000006
+ bf89a0cc b77fd3a9 /system/bin/app_process32 (sigprocmask+141)
+ bf89a0d0 00000002
+ bf89a0d4 bf89a0ec [stack]
+ bf89a0d8 00000000
+ bf89a0dc b743671c /system/lib/libc.so
+ bf89a0e0 bf89a12c [stack]
+ bf89a0e4 bf89a1e4 [stack]
+ bf89a0e8 932d1d4a /data/app/com.example.app-2/base.apk
+ bf89a0ec b7365206 /system/lib/libc.so (raise+37)
+ #02 bf89a0f0 b77f8c00 /system/bin/linker
+ bf89a0f4 00000006
+ bf89a0f8 b7439468 /system/lib/libc.so
+ bf89a0fc b743671c /system/lib/libc.so
+ bf89a100 bf89a12c [stack]
+ bf89a104 b743671c /system/lib/libc.so
+ bf89a108 bf89a12c [stack]
+ bf89a10c b735e9e5 /system/lib/libc.so (abort+81)
+ #03 bf89a110 00000006
+ bf89a114 bf89a12c [stack]
+ bf89a118 00000000
+ bf89a11c b55a3d3b /system/lib/libprotobuf-cpp-lite.so (google::protobuf::internal::DefaultLogHandler(google::protobuf::LogLevel, char const*, int, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)+99)
+ bf89a120 b7439468 /system/lib/libc.so
+ bf89a124 b55ba38d /system/lib/libprotobuf-cpp-lite.so
+ bf89a128 b55ba408 /system/lib/libprotobuf-cpp-lite.so
+ bf89a12c ffffffdf
+ bf89a130 0000003d
+ bf89a134 adfedf00 [anon:libc_malloc]
+ bf89a138 bf89a158 [stack]
+ #04 bf89a13c a0cee7f0 /data/app/com.example.app-2/base.apk
+ bf89a140 b55c1cb0 /system/lib/libprotobuf-cpp-lite.so
+ bf89a144 bf89a1e4 [stack]
+'''
+
+_TEST_BACKTRACE = r'''backtrace:
+ #00 pc 00084126 /system/lib/libc.so (tgkill+22)
+ #01 pc 000815d8 /system/lib/libc.so (pthread_kill+70)
+ #02 pc 00027205 /system/lib/libc.so (raise+36)
+ #03 pc 000209e4 /system/lib/libc.so (abort+80)
+ #04 pc 0000cf73 /system/lib/libprotobuf-cpp-lite.so (google::protobuf::internal::LogMessage::Finish()+117)
+ #05 pc 0000cf8e /system/lib/libprotobuf-cpp-lite.so (google::protobuf::internal::LogFinisher::operator=(google::protobuf::internal::LogMessage&)+26)
+ #06 pc 0000d27f /system/lib/libprotobuf-cpp-lite.so (google::protobuf::internal::VerifyVersion(int, int, char const*)+574)
+ #07 pc 007cd236 /data/app/com.google.android.apps.chrome-2/base.apk (offset 0x598d000)
+ #08 pc 000111a9 /data/app/com.google.android.apps.chrome-2/base.apk (offset 0xbfc2000)
+ #09 pc 00013228 /data/app/com.google.android.apps.chrome-2/base.apk (offset 0xbfc2000)
+ #10 pc 000131de /data/app/com.google.android.apps.chrome-2/base.apk (offset 0xbfc2000)
+ #11 pc 007cd2d8 /data/app/com.google.android.apps.chrome-2/base.apk (offset 0x598d000)
+ #12 pc 007cd956 /data/app/com.google.android.apps.chrome-2/base.apk (offset 0x598d000)
+ #13 pc 007c2d4a /data/app/com.google.android.apps.chrome-2/base.apk (offset 0x598d000)
+ #14 pc 009fc9f1 /data/app/com.google.android.apps.chrome-2/base.apk (offset 0x598d000)
+ #15 pc 009fc8ea /data/app/com.google.android.apps.chrome-2/base.apk (offset 0x598d000)
+ #16 pc 00561c63 /data/app/com.google.android.apps.chrome-2/base.apk (offset 0x598d000)
+ #17 pc 0106fbdb /data/app/com.google.android.apps.chrome-2/base.apk (offset 0x598d000)
+ #18 pc 004d7371 /data/app/com.google.android.apps.chrome-2/base.apk (offset 0x598d000)
+ #19 pc 004d8159 /data/app/com.google.android.apps.chrome-2/base.apk (offset 0x598d000)
+ #20 pc 004d7b96 /data/app/com.google.android.apps.chrome-2/base.apk (offset 0x598d000)
+ #21 pc 004da4b6 /data/app/com.google.android.apps.chrome-2/base.apk (offset 0x598d000)
+ #22 pc 005ab66c /data/app/com.google.android.apps.chrome-2/base.apk (offset 0x7daa000)
+ #23 pc 005afca2 /data/app/com.google.android.apps.chrome-2/base.apk (offset 0x7daa000)
+ #24 pc 0000cae8 /data/app/com.google.android.apps.chrome-2/base.apk (offset 0x598d000)
+ #25 pc 00ce864f /data/app/com.google.android.apps.chrome-2/base.apk (offset 0x7daa000)
+ #26 pc 00ce8dfa /data/app/com.google.android.apps.chrome-2/base.apk (offset 0x7daa000)
+ #27 pc 00ce74c6 /data/app/com.google.android.apps.chrome-2/base.apk (offset 0x7daa000)
+ #28 pc 00004616 /data/app/com.google.android.apps.chrome-2/base.apk (offset 0x961e000)
+ #29 pc 00ce8215 /data/app/com.google.android.apps.chrome-2/base.apk (offset 0x7daa000)
+ #30 pc 0013d8c7 /system/lib/libart.so (art_quick_generic_jni_trampoline+71)
+ #31 pc 00137c52 /system/lib/libart.so (art_quick_invoke_static_stub+418)
+ #32 pc 00143651 /system/lib/libart.so (art::ArtMethod::Invoke(art::Thread*, unsigned int*, unsigned int, art::JValue*, char const*)+353)
+ #33 pc 005e06ae /system/lib/libart.so (artInterpreterToCompiledCodeBridge+190)
+ #34 pc 00328b5d /system/lib/libart.so (bool art::interpreter::DoCall<false, false>(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*)+445)
+ #35 pc 0032cfc0 /system/lib/libart.so (bool art::interpreter::DoInvoke<(art::InvokeType)0, false, false>(art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*)+160)
+ #36 pc 000fc703 /system/lib/libart.so (art::JValue art::interpreter::ExecuteGotoImpl<false, false>(art::Thread*, art::DexFile::CodeItem const*, art::ShadowFrame&, art::JValue)+29891)
+ #37 pc 00300af7 /system/lib/libart.so (artInterpreterToInterpreterBridge+188)
+ #38 pc 00328b5d /system/lib/libart.so (bool art::interpreter::DoCall<false, false>(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*)+445)
+ #39 pc 0032cfc0 /system/lib/libart.so (bool art::interpreter::DoInvoke<(art::InvokeType)0, false, false>(art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*)+160)
+ #40 pc 000fc703 /system/lib/libart.so (art::JValue art::interpreter::ExecuteGotoImpl<false, false>(art::Thread*, art::DexFile::CodeItem const*, art::ShadowFrame&, art::JValue)+29891)
+ #41 pc 00300af7 /system/lib/libart.so (artInterpreterToInterpreterBridge+188)
+ #42 pc 00328b5d /system/lib/libart.so (bool art::interpreter::DoCall<false, false>(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*)+445)
+ #43 pc 0032ebf9 /system/lib/libart.so (bool art::interpreter::DoInvoke<(art::InvokeType)2, false, false>(art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*)+297)
+ #44 pc 000fc955 /system/lib/libart.so (art::JValue art::interpreter::ExecuteGotoImpl<false, false>(art::Thread*, art::DexFile::CodeItem const*, art::ShadowFrame&, art::JValue)+30485)
+ #45 pc 00300af7 /system/lib/libart.so (artInterpreterToInterpreterBridge+188)
+ #46 pc 00328b5d /system/lib/libart.so (bool art::interpreter::DoCall<false, false>(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*)+445)
+ #47 pc 0033090c /system/lib/libart.so (bool art::interpreter::DoInvoke<(art::InvokeType)4, false, false>(art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*)+636)
+ #48 pc 000fc67f /system/lib/libart.so (art::JValue art::interpreter::ExecuteGotoImpl<false, false>(art::Thread*, art::DexFile::CodeItem const*, art::ShadowFrame&, art::JValue)+29759)
+ #49 pc 00300700 /system/lib/libart.so (art::interpreter::EnterInterpreterFromEntryPoint(art::Thread*, art::DexFile::CodeItem const*, art::ShadowFrame*)+128)
+ #50 pc 00667c73 /system/lib/libart.so (artQuickToInterpreterBridge+808)
+ #51 pc 0013d98d /system/lib/libart.so (art_quick_to_interpreter_bridge+77)
+ #52 pc 7264bc5b /data/dalvik-cache/x86/system@framework@boot.oat (offset 0x1eb2000)
+'''
+
+_EXPECTED_BACKTRACE = r'''backtrace:
+ #00 pc 00084126 /system/lib/libc.so (tgkill+22)
+ #01 pc 000815d8 /system/lib/libc.so (pthread_kill+70)
+ #02 pc 00027205 /system/lib/libc.so (raise+36)
+ #03 pc 000209e4 /system/lib/libc.so (abort+80)
+ #04 pc 0000cf73 /system/lib/libprotobuf-cpp-lite.so (google::protobuf::internal::LogMessage::Finish()+117)
+ #05 pc 0000cf8e /system/lib/libprotobuf-cpp-lite.so (google::protobuf::internal::LogFinisher::operator=(google::protobuf::internal::LogMessage&)+26)
+ #06 pc 0000d27f /system/lib/libprotobuf-cpp-lite.so (google::protobuf::internal::VerifyVersion(int, int, char const*)+574)
+ #07 pc 007cd236 /data/app/com.google.android.apps.chrome-2/base.apk!lib/libchrome.cr.so (offset 0x90e000)
+ #08 pc 000111a9 /data/app/com.google.android.apps.chrome-2/base.apk!lib/libprotobuf_lite.cr.so (offset 0x1c000)
+ #09 pc 00013228 /data/app/com.google.android.apps.chrome-2/base.apk!lib/libprotobuf_lite.cr.so (offset 0x1c000)
+ #10 pc 000131de /data/app/com.google.android.apps.chrome-2/base.apk!lib/libprotobuf_lite.cr.so (offset 0x1c000)
+ #11 pc 007cd2d8 /data/app/com.google.android.apps.chrome-2/base.apk!lib/libchrome.cr.so (offset 0x90e000)
+ #12 pc 007cd956 /data/app/com.google.android.apps.chrome-2/base.apk!lib/libchrome.cr.so (offset 0x90e000)
+ #13 pc 007c2d4a /data/app/com.google.android.apps.chrome-2/base.apk!lib/libchrome.cr.so (offset 0x90e000)
+ #14 pc 009fc9f1 /data/app/com.google.android.apps.chrome-2/base.apk!lib/libchrome.cr.so (offset 0x90e000)
+ #15 pc 009fc8ea /data/app/com.google.android.apps.chrome-2/base.apk!lib/libchrome.cr.so (offset 0x90e000)
+ #16 pc 00561c63 /data/app/com.google.android.apps.chrome-2/base.apk!lib/libchrome.cr.so (offset 0x90e000)
+ #17 pc 0106fbdb /data/app/com.google.android.apps.chrome-2/base.apk!lib/libchrome.cr.so (offset 0x90e000)
+ #18 pc 004d7371 /data/app/com.google.android.apps.chrome-2/base.apk!lib/libchrome.cr.so (offset 0x90e000)
+ #19 pc 004d8159 /data/app/com.google.android.apps.chrome-2/base.apk!lib/libchrome.cr.so (offset 0x90e000)
+ #20 pc 004d7b96 /data/app/com.google.android.apps.chrome-2/base.apk!lib/libchrome.cr.so (offset 0x90e000)
+ #21 pc 004da4b6 /data/app/com.google.android.apps.chrome-2/base.apk!lib/libchrome.cr.so (offset 0x90e000)
+ #22 pc 005ab66c /data/app/com.google.android.apps.chrome-2/base.apk!lib/libcontent.cr.so (offset 0xc2d000)
+ #23 pc 005afca2 /data/app/com.google.android.apps.chrome-2/base.apk!lib/libcontent.cr.so (offset 0xc2d000)
+ #24 pc 0000cae8 /data/app/com.google.android.apps.chrome-2/base.apk!lib/libchrome.cr.so (offset 0x90e000)
+ #25 pc 00ce864f /data/app/com.google.android.apps.chrome-2/base.apk!lib/libcontent.cr.so (offset 0xc2d000)
+ #26 pc 00ce8dfa /data/app/com.google.android.apps.chrome-2/base.apk!lib/libcontent.cr.so (offset 0xc2d000)
+ #27 pc 00ce74c6 /data/app/com.google.android.apps.chrome-2/base.apk!lib/libcontent.cr.so (offset 0xc2d000)
+ #28 pc 00004616 /data/app/com.google.android.apps.chrome-2/base.apk!lib/libembedder.cr.so (offset 0x28000)
+ #29 pc 00ce8215 /data/app/com.google.android.apps.chrome-2/base.apk!lib/libcontent.cr.so (offset 0xc2d000)
+ #30 pc 0013d8c7 /system/lib/libart.so (art_quick_generic_jni_trampoline+71)
+ #31 pc 00137c52 /system/lib/libart.so (art_quick_invoke_static_stub+418)
+ #32 pc 00143651 /system/lib/libart.so (art::ArtMethod::Invoke(art::Thread*, unsigned int*, unsigned int, art::JValue*, char const*)+353)
+ #33 pc 005e06ae /system/lib/libart.so (artInterpreterToCompiledCodeBridge+190)
+ #34 pc 00328b5d /system/lib/libart.so (bool art::interpreter::DoCall<false, false>(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*)+445)
+ #35 pc 0032cfc0 /system/lib/libart.so (bool art::interpreter::DoInvoke<(art::InvokeType)0, false, false>(art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*)+160)
+ #36 pc 000fc703 /system/lib/libart.so (art::JValue art::interpreter::ExecuteGotoImpl<false, false>(art::Thread*, art::DexFile::CodeItem const*, art::ShadowFrame&, art::JValue)+29891)
+ #37 pc 00300af7 /system/lib/libart.so (artInterpreterToInterpreterBridge+188)
+ #38 pc 00328b5d /system/lib/libart.so (bool art::interpreter::DoCall<false, false>(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*)+445)
+ #39 pc 0032cfc0 /system/lib/libart.so (bool art::interpreter::DoInvoke<(art::InvokeType)0, false, false>(art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*)+160)
+ #40 pc 000fc703 /system/lib/libart.so (art::JValue art::interpreter::ExecuteGotoImpl<false, false>(art::Thread*, art::DexFile::CodeItem const*, art::ShadowFrame&, art::JValue)+29891)
+ #41 pc 00300af7 /system/lib/libart.so (artInterpreterToInterpreterBridge+188)
+ #42 pc 00328b5d /system/lib/libart.so (bool art::interpreter::DoCall<false, false>(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*)+445)
+ #43 pc 0032ebf9 /system/lib/libart.so (bool art::interpreter::DoInvoke<(art::InvokeType)2, false, false>(art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*)+297)
+ #44 pc 000fc955 /system/lib/libart.so (art::JValue art::interpreter::ExecuteGotoImpl<false, false>(art::Thread*, art::DexFile::CodeItem const*, art::ShadowFrame&, art::JValue)+30485)
+ #45 pc 00300af7 /system/lib/libart.so (artInterpreterToInterpreterBridge+188)
+ #46 pc 00328b5d /system/lib/libart.so (bool art::interpreter::DoCall<false, false>(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*)+445)
+ #47 pc 0033090c /system/lib/libart.so (bool art::interpreter::DoInvoke<(art::InvokeType)4, false, false>(art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*)+636)
+ #48 pc 000fc67f /system/lib/libart.so (art::JValue art::interpreter::ExecuteGotoImpl<false, false>(art::Thread*, art::DexFile::CodeItem const*, art::ShadowFrame&, art::JValue)+29759)
+ #49 pc 00300700 /system/lib/libart.so (art::interpreter::EnterInterpreterFromEntryPoint(art::Thread*, art::DexFile::CodeItem const*, art::ShadowFrame*)+128)
+ #50 pc 00667c73 /system/lib/libart.so (artQuickToInterpreterBridge+808)
+ #51 pc 0013d98d /system/lib/libart.so (art_quick_to_interpreter_bridge+77)
+ #52 pc 7264bc5b /data/dalvik-cache/x86/system@framework@boot.oat (offset 0x1eb2000)
+'''
+
+_EXPECTED_BACKTRACE_OFFSETS_MAP = {
+ '/data/app/com.google.android.apps.chrome-2/base.apk!lib/libprotobuf_lite.cr.so':
+ set([
+ 0x1c000 + 0x111a9,
+ 0x1c000 + 0x13228,
+ 0x1c000 + 0x131de,
+ ]),
+
+ '/data/app/com.google.android.apps.chrome-2/base.apk!lib/libchrome.cr.so':
+ set([
+ 0x90e000 + 0x7cd236,
+ 0x90e000 + 0x7cd2d8,
+ 0x90e000 + 0x7cd956,
+ 0x90e000 + 0x7c2d4a,
+ 0x90e000 + 0x9fc9f1,
+ 0x90e000 + 0x9fc8ea,
+ 0x90e000 + 0x561c63,
+ 0x90e000 + 0x106fbdb,
+ 0x90e000 + 0x4d7371,
+ 0x90e000 + 0x4d8159,
+ 0x90e000 + 0x4d7b96,
+ 0x90e000 + 0x4da4b6,
+ 0x90e000 + 0xcae8,
+ ]),
+ '/data/app/com.google.android.apps.chrome-2/base.apk!lib/libcontent.cr.so':
+ set([
+ 0xc2d000 + 0x5ab66c,
+ 0xc2d000 + 0x5afca2,
+ 0xc2d000 + 0xce864f,
+ 0xc2d000 + 0xce8dfa,
+ 0xc2d000 + 0xce74c6,
+ 0xc2d000 + 0xce8215,
+ ]),
+ '/data/app/com.google.android.apps.chrome-2/base.apk!lib/libembedder.cr.so':
+ set([
+ 0x28000 + 0x4616,
+ ])
+}
+
+# pylint: enable=line-too-long
+
+_ONE_MB = 1024 * 1024
+_TEST_SYMBOL_DATA = {
+ # Regular symbols
+ 0: 'mock_sym_for_addr_0 [mock_src/libmock1.so.c:0]',
+ 0x1000: 'mock_sym_for_addr_4096 [mock_src/libmock1.so.c:4096]',
+
+ # Symbols without source file path.
+ _ONE_MB: 'mock_sym_for_addr_1048576 [??:0]',
+ _ONE_MB + 0x8234: 'mock_sym_for_addr_1081908 [??:0]',
+
+ # Unknown symbol.
+ 2 * _ONE_MB: '?? [??:0]',
+
+ # Inlined symbol.
+ 3 * _ONE_MB:
+ 'mock_sym_for_addr_3145728_inner [mock_src/libmock1.so.c:3145728]',
+}
+
+@contextlib.contextmanager
+def _TempDir():
+ dirname = tempfile.mkdtemp()
+ try:
+ yield dirname
+ finally:
+ shutil.rmtree(dirname)
+
+
+def _TouchFile(path):
+ # Create parent directories.
+ try:
+ os.makedirs(os.path.dirname(path))
+ except OSError:
+ pass
+ with open(path, 'a'):
+ os.utime(path, None)
+
+class MockApkTranslator(object):
+ """A mock ApkLibraryPathTranslator object used for testing."""
+
+ # Regex that matches the content of APK native library map files generated
+ # with apk_lib_dump.py.
+ _RE_MAP_FILE = re.compile(
+ r'0x(?P<file_start>[0-9a-f]+)\s+' +
+ r'0x(?P<file_end>[0-9a-f]+)\s+' +
+ r'0x(?P<file_size>[0-9a-f]+)\s+' +
+ r'0x(?P<lib_path>[0-9a-f]+)\s+')
+
+ def __init__(self, test_apk_libs=None):
+ """Initialize instance.
+
+ Args:
+ test_apk_libs: Optional list of (file_start, file_end, size, lib_path)
+ tuples, like _TEST_APK_LIBS for example. This will be used to
+ implement TranslatePath().
+ """
+ self._apk_libs = []
+ if test_apk_libs:
+ self._AddLibEntries(test_apk_libs)
+
+ def _AddLibEntries(self, entries):
+ self._apk_libs = sorted(self._apk_libs + entries, key=lambda x: x[0])
+
+ def ReadMapFile(self, file_path):
+ """Read an .apk.native-libs file that was produced with apk_lib_dump.py.
+
+ Args:
+ file_path: input path to .apk.native-libs file. Its format is
+ essentially: 0x<start> 0x<end> 0x<size> <library-path>
+ """
+ new_libs = []
+ with open(file_path) as f:
+ for line in f.readlines():
+ m = MockApkTranslator._RE_MAP_FILE.match(line)
+ if m:
+ file_start = int(m.group('file_start'), 16)
+ file_end = int(m.group('file_end'), 16)
+ file_size = int(m.group('file_size'), 16)
+ lib_path = m.group('lib_path')
+ # Sanity check
+ if file_start + file_size != file_end:
+ logging.warning('%s: Inconsistent (start, end, size) values '
+ '(0x%x, 0x%x, 0x%x)',
+ file_path, file_start, file_end, file_size)
+ else:
+ new_libs.append((file_start, file_end, file_size, lib_path))
+
+ self._AddLibEntries(new_libs)
+
+ def TranslatePath(self, lib_path, lib_offset):
+ """Translate an APK file path + offset into a library path + offset."""
+ min_pos = 0
+ max_pos = len(self._apk_libs)
+ while min_pos < max_pos:
+ mid_pos = (min_pos + max_pos) // 2
+ mid_entry = self._apk_libs[mid_pos]
+ mid_offset = mid_entry[0]
+ mid_size = mid_entry[2]
+ if lib_offset < mid_offset:
+ max_pos = mid_pos
+ elif lib_offset >= mid_offset + mid_size:
+ min_pos = mid_pos + 1
+ else:
+ # Found it
+ new_path = '%s!lib/%s' % (lib_path, mid_entry[3])
+ new_offset = lib_offset - mid_offset
+ return (new_path, new_offset)
+
+ return lib_path, lib_offset
+
+
+class HostLibraryFinderTest(unittest.TestCase):
+
+ def testEmpty(self):
+ finder = symbol_utils.HostLibraryFinder()
+ self.assertIsNone(finder.Find('/data/data/com.example.app-1/lib/libfoo.so'))
+ self.assertIsNone(
+ finder.Find('/data/data/com.example.app-1/base.apk!lib/libfoo.so'))
+
+
+ def testSimpleDirectory(self):
+ finder = symbol_utils.HostLibraryFinder()
+ with _TempDir() as tmp_dir:
+ host_libfoo_path = os.path.join(tmp_dir, 'libfoo.so')
+ host_libbar_path = os.path.join(tmp_dir, 'libbar.so')
+ _TouchFile(host_libfoo_path)
+ _TouchFile(host_libbar_path)
+
+ finder.AddSearchDir(tmp_dir)
+
+ # Regular library path (extracted at installation by the PackageManager).
+ # Note that the extraction path has changed between Android releases,
+ # i.e. it can be /data/app/, /data/data/ or /data/app-lib/ depending
+ # on the system.
+ self.assertEqual(
+ host_libfoo_path,
+ finder.Find('/data/app-lib/com.example.app-1/lib/libfoo.so'))
+
+ # Verify that the path doesn't really matter
+ self.assertEqual(
+ host_libfoo_path,
+ finder.Find('/whatever/what.apk!lib/libfoo.so'))
+
+ self.assertEqual(
+ host_libbar_path,
+ finder.Find('/data/data/com.example.app-1/lib/libbar.so'))
+
+ self.assertIsNone(
+ finder.Find('/data/data/com.example.app-1/lib/libunknown.so'))
+
+
+ def testMultipleDirectories(self):
+ with _TempDir() as tmp_dir:
+ # Create the following files:
+ # <tmp_dir>/aaa/
+ # libfoo.so
+ # <tmp_dir>/bbb/
+ # libbar.so
+ # libfoo.so (this one should never be seen because 'aaa'
+ # shall be first in the search path list).
+ #
+ aaa_dir = os.path.join(tmp_dir, 'aaa')
+ bbb_dir = os.path.join(tmp_dir, 'bbb')
+ os.makedirs(aaa_dir)
+ os.makedirs(bbb_dir)
+
+ host_libfoo_path = os.path.join(aaa_dir, 'libfoo.so')
+ host_libbar_path = os.path.join(bbb_dir, 'libbar.so')
+ host_libfoo2_path = os.path.join(bbb_dir, 'libfoo.so')
+
+ _TouchFile(host_libfoo_path)
+ _TouchFile(host_libbar_path)
+ _TouchFile(host_libfoo2_path)
+
+ finder = symbol_utils.HostLibraryFinder()
+ finder.AddSearchDir(aaa_dir)
+ finder.AddSearchDir(bbb_dir)
+
+ self.assertEqual(
+ host_libfoo_path,
+ finder.Find('/data/data/com.example.app-1/lib/libfoo.so'))
+
+ self.assertEqual(
+ host_libfoo_path,
+ finder.Find('/data/whatever/base.apk!lib/libfoo.so'))
+
+ self.assertEqual(
+ host_libbar_path,
+ finder.Find('/data/data/com.example.app-1/lib/libbar.so'))
+
+ self.assertIsNone(
+ finder.Find('/data/data/com.example.app-1/lib/libunknown.so'))
+
+
+class ElfSymbolResolverTest(unittest.TestCase):
+
+ def testCreation(self):
+ resolver = symbol_utils.ElfSymbolResolver(
+ addr2line_path_for_tests=_MOCK_A2L_PATH)
+ self.assertTrue(resolver)
+
+ def testWithSimpleOffsets(self):
+ resolver = symbol_utils.ElfSymbolResolver(
+ addr2line_path_for_tests=_MOCK_A2L_PATH)
+ resolver.SetAndroidAbi('ignored-abi')
+
+ for addr, expected_sym in _TEST_SYMBOL_DATA.items():
+ self.assertEqual(resolver.FindSymbolInfo('/some/path/libmock1.so', addr),
+ expected_sym)
+
+ def testWithPreResolvedSymbols(self):
+ resolver = symbol_utils.ElfSymbolResolver(
+ addr2line_path_for_tests=_MOCK_A2L_PATH)
+ resolver.SetAndroidAbi('ignored-abi')
+ resolver.AddLibraryOffsets('/some/path/libmock1.so',
+ list(_TEST_SYMBOL_DATA.keys()))
+
+ resolver.DisallowSymbolizerForTesting()
+
+ for addr, expected_sym in _TEST_SYMBOL_DATA.items():
+ sym_info = resolver.FindSymbolInfo('/some/path/libmock1.so', addr)
+ self.assertIsNotNone(sym_info, 'None symbol info for addr %x' % addr)
+ self.assertEqual(
+ sym_info, expected_sym,
+ 'Invalid symbol info for addr %x [%s] expected [%s]' % (
+ addr, sym_info, expected_sym))
+
+
+class MemoryMapTest(unittest.TestCase):
+
+ def testCreation(self):
+ mem_map = symbol_utils.MemoryMap('test-abi32')
+ self.assertIsNone(mem_map.FindSectionForAddress(0))
+
+ def testParseLines(self):
+ mem_map = symbol_utils.MemoryMap('test-abi32')
+ mem_map.ParseLines(_TEST_MEMORY_MAP.splitlines())
+ for exp_addr, exp_size, exp_path, exp_offset in _TEST_MEMORY_MAP_SECTIONS:
+ text = '(addr:%x, size:%x, path:%s, offset=%x)' % (
+ exp_addr, exp_size, exp_path, exp_offset)
+
+ t = mem_map.FindSectionForAddress(exp_addr)
+ self.assertTrue(t, 'Could not find %s' % text)
+ self.assertEqual(t.address, exp_addr)
+ self.assertEqual(t.size, exp_size)
+ self.assertEqual(t.offset, exp_offset)
+ self.assertEqual(t.path, exp_path)
+
+ def testTranslateLine(self):
+ android_abi = 'test-abi'
+ apk_translator = MockApkTranslator(_TEST_APK_LIBS)
+ mem_map = symbol_utils.MemoryMap(android_abi)
+ for line, expected_line in zip(_TEST_MEMORY_MAP.splitlines(),
+ _EXPECTED_TEST_MEMORY_MAP.splitlines()):
+ self.assertEqual(mem_map.TranslateLine(line, apk_translator),
+ expected_line)
+
+class StackTranslatorTest(unittest.TestCase):
+
+ def testSimpleStack(self):
+ android_abi = 'test-abi32'
+ mem_map = symbol_utils.MemoryMap(android_abi)
+ mem_map.ParseLines(_TEST_MEMORY_MAP)
+ apk_translator = MockApkTranslator(_TEST_APK_LIBS)
+ stack_translator = symbol_utils.StackTranslator(android_abi, mem_map,
+ apk_translator)
+ input_stack = _TEST_STACK.splitlines()
+ expected_stack = _EXPECTED_STACK.splitlines()
+ self.assertEqual(len(input_stack), len(expected_stack))
+ for stack_line, expected_line in zip(input_stack, expected_stack):
+ new_line = stack_translator.TranslateLine(stack_line)
+ self.assertEqual(new_line, expected_line)
+
+
+class MockSymbolResolver(symbol_utils.SymbolResolver):
+
+ # A regex matching a symbol definition as it appears in a test symbol file.
+ # Format is: <hex-offset> <whitespace> <symbol-string>
+ _RE_SYMBOL_DEFINITION = re.compile(
+ r'(?P<offset>[0-9a-f]+)\s+(?P<symbol>.*)')
+
+ def __init__(self):
+ super(MockSymbolResolver, self).__init__()
+ self._map = collections.defaultdict(dict)
+
+ def AddTestLibrarySymbols(self, lib_name, offsets_map):
+ """Add a new test entry for a given library name.
+
+ Args:
+ lib_name: Library name (e.g. 'libfoo.so')
+ offsets_map: A mapping from offsets to symbol info strings.
+ """
+ self._map[lib_name] = offsets_map
+
+ def ReadTestFile(self, file_path, lib_name):
+ """Read a single test symbol file, matching a given library.
+
+ Args:
+ file_path: Input file path.
+ lib_name: Library name these symbols correspond to (e.g. 'libfoo.so')
+ """
+ with open(file_path) as f:
+ for line in f.readlines():
+ line = line.rstrip()
+ m = MockSymbolResolver._RE_SYMBOL_DEFINITION.match(line)
+ if m:
+ offset = int(m.group('offset'))
+ symbol = m.group('symbol')
+ self._map[lib_name][offset] = symbol
+
+ def ReadTestFilesInDir(self, dir_path, file_suffix):
+ """Read all symbol test files in a given directory.
+
+ Args:
+ dir_path: Directory path.
+ file_suffix: File suffix used to detect test symbol files.
+ """
+ for filename in os.listdir(dir_path):
+ if filename.endswith(file_suffix):
+ lib_name = filename[:-len(file_suffix)]
+ self.ReadTestFile(os.path.join(dir_path, filename), lib_name)
+
+ def FindSymbolInfo(self, device_path, device_offset):
+ """Implement SymbolResolver.FindSymbolInfo."""
+ lib_name = os.path.basename(device_path)
+ offsets = self._map.get(lib_name)
+ if not offsets:
+ return None
+
+ return offsets.get(device_offset)
+
+
+class BacktraceTranslatorTest(unittest.TestCase):
+
+ def testEmpty(self):
+ android_abi = 'test-abi'
+ apk_translator = MockApkTranslator()
+ backtrace_translator = symbol_utils.BacktraceTranslator(android_abi,
+ apk_translator)
+ self.assertTrue(backtrace_translator)
+
+ def testFindLibraryOffsets(self):
+ android_abi = 'test-abi'
+ apk_translator = MockApkTranslator(_TEST_APK_LIBS)
+ backtrace_translator = symbol_utils.BacktraceTranslator(android_abi,
+ apk_translator)
+ input_backtrace = _EXPECTED_BACKTRACE.splitlines()
+ expected_lib_offsets_map = _EXPECTED_BACKTRACE_OFFSETS_MAP
+ offset_map = backtrace_translator.FindLibraryOffsets(input_backtrace)
+ for lib_path, offsets in offset_map.items():
+ self.assertTrue(lib_path in expected_lib_offsets_map,
+ '%s is not in expected library-offsets map!' % lib_path)
+ sorted_offsets = sorted(offsets)
+ sorted_expected_offsets = sorted(expected_lib_offsets_map[lib_path])
+ self.assertEqual(sorted_offsets, sorted_expected_offsets,
+ '%s has invalid offsets %s expected %s' % (
+ lib_path, sorted_offsets, sorted_expected_offsets))
+
+ def testTranslateLine(self):
+ android_abi = 'test-abi'
+ apk_translator = MockApkTranslator(_TEST_APK_LIBS)
+ backtrace_translator = symbol_utils.BacktraceTranslator(android_abi,
+ apk_translator)
+ input_backtrace = _TEST_BACKTRACE.splitlines()
+ expected_backtrace = _EXPECTED_BACKTRACE.splitlines()
+ self.assertEqual(len(input_backtrace), len(expected_backtrace))
+ for trace_line, expected_line in zip(input_backtrace, expected_backtrace):
+ line = backtrace_translator.TranslateLine(trace_line,
+ MockSymbolResolver())
+ self.assertEqual(line, expected_line)
+
+
+if __name__ == '__main__':
+ unittest.main()