Index: third_party/android_platform/development/scripts/symbol.py |
diff --git a/third_party/android_platform/development/scripts/symbol.py b/third_party/android_platform/development/scripts/symbol.py |
index df7f2c4b138ee1e6b2570d2f59eceb83e2c16855..44d0238391329d49e8fc952c79883172e0f24729 100755 |
--- a/third_party/android_platform/development/scripts/symbol.py |
+++ b/third_party/android_platform/development/scripts/symbol.py |
@@ -19,9 +19,12 @@ |
The information can include symbol names, offsets, and source locations. |
""" |
+import glob |
+import itertools |
import os |
import re |
import subprocess |
+import zipfile |
CHROME_SRC = os.path.join(os.path.realpath(os.path.dirname(__file__)), |
os.pardir, os.pardir, os.pardir, os.pardir) |
@@ -130,6 +133,158 @@ def FindToolchain(): |
raise Exception("Could not find tool chain") |
+def GetAapt(): |
+ """Returns the path to aapt. |
+ |
+ Args: |
+ None |
+ |
+ Returns: |
+ the pathname of the 'aapt' executable. |
+ """ |
+ sdk_home = os.path.join('third_party', 'android_tools', 'sdk') |
+ sdk_home = os.environ.get('SDK_HOME', sdk_home) |
+ aapt_exe = glob.glob(os.path.join(sdk_home, 'build-tools', '*', 'aapt')) |
+ if not aapt_exe: |
+ return None |
+ return sorted(aapt_exe, key=os.path.getmtime, reverse=True)[0] |
+ |
+def ApkMatchPackageName(aapt, apk_path, package_name): |
+ """Returns true the APK's package name matches package_name. |
+ |
+ Args: |
+ aapt: pathname for the 'aapt' executable. |
+ apk_path: pathname of the APK file. |
+ package_name: package name to match. |
+ |
+ Returns: |
+ True if the package name matches or aapt is None, False otherwise. |
+ """ |
+ if not aapt: |
+ # Allow false positives |
+ return True |
+ aapt_output = subprocess.check_output( |
+ [aapt, 'dump', 'badging', apk_path]).split('\n') |
+ package_name_re = re.compile(r'package: .*name=\'(\S*)\'') |
+ for line in aapt_output: |
+ match = package_name_re.match(line) |
+ if match: |
+ return package_name == match.group(1) |
+ return False |
+ |
+def PathListJoin(prefix_list, suffix_list): |
+ """Returns each prefix in prefix_list joined with each suffix in suffix list. |
+ |
+ Args: |
+ prefix_list: list of path prefixes. |
+ suffix_list: list of path suffixes. |
+ |
+ Returns: |
+ List of paths each of which joins a prefix with a suffix. |
+ """ |
+ return [ |
+ os.path.join(prefix, suffix) |
+ for prefix in prefix_list for suffix in suffix_list ] |
+ |
+def GetCandidates(dirs, filepart, candidate_fun): |
+ """Returns a list of candidate filenames. |
+ |
+ Args: |
+ dirs: a list of the directory part of the pathname. |
+ filepart: the file part of the pathname. |
+ candidate_fun: a function to apply to each candidate, returns a list. |
+ |
+ Returns: |
+ A list of candidate files ordered by modification time, newest first. |
+ """ |
+ out_dir = os.environ.get('CHROMIUM_OUT_DIR', 'out') |
+ out_dir = os.path.join(CHROME_SYMBOLS_DIR, out_dir) |
+ buildtype = os.environ.get('BUILDTYPE') |
+ if buildtype: |
+ buildtype_list = [ buildtype ] |
+ else: |
+ buildtype_list = [ 'Debug', 'Release' ] |
+ |
+ candidates = PathListJoin([out_dir], buildtype_list) + [CHROME_SYMBOLS_DIR] |
+ candidates = PathListJoin(candidates, dirs) |
+ candidates = PathListJoin(candidates, [filepart]) |
+ candidates = list( |
+ itertools.chain.from_iterable(map(candidate_fun, candidates))) |
+ candidates = sorted(candidates, key=os.path.getmtime, reverse=True) |
+ return candidates |
+ |
+def GetCandidateApks(): |
+ """Returns a list of APKs which could contain the library. |
+ |
+ Args: |
+ None |
+ |
+ Returns: |
+ list of APK filename which could contain the library. |
+ """ |
+ return GetCandidates(['apks'], '*.apk', glob.glob) |
+ |
+def GetCrazyLib(apk_filename): |
+ """Returns the name of the first crazy library from this APK. |
+ |
+ Args: |
+ apk_filename: name of an APK file. |
+ |
+ Returns: |
+ Name of the first library which would be crazy loaded from this APK. |
+ """ |
+ zip_file = zipfile.ZipFile(apk_filename, 'r') |
+ for filename in zip_file.namelist(): |
+ match = re.match('lib/[^/]*/crazy.(lib.*[.]so)', filename) |
+ if match: |
+ return match.group(1) |
+ |
+def GetMatchingApks(device_apk_name): |
+ """Find any APKs which match the package indicated by the device_apk_name. |
+ |
+ Args: |
+ device_apk_name: name of the APK on the device. |
+ |
+ Returns: |
+ A list of APK filenames which could contain the desired library. |
+ """ |
+ match = re.match('(.*)-[0-9]+[.]apk$', device_apk_name) |
+ if not match: |
+ return None |
+ package_name = match.group(1) |
+ return filter( |
+ lambda candidate_apk: |
+ ApkMatchPackageName(GetAapt(), candidate_apk, package_name), |
+ GetCandidateApks()) |
+ |
+def MapDeviceApkToLibrary(device_apk_name): |
+ """Provide a library name which corresponds with device_apk_name. |
+ |
+ Args: |
+ device_apk_name: name of the APK on the device. |
+ |
+ Returns: |
+ Name of the library which corresponds to that APK. |
+ """ |
+ matching_apks = GetMatchingApks(device_apk_name) |
+ for matching_apk in matching_apks: |
+ crazy_lib = GetCrazyLib(matching_apk) |
+ if crazy_lib: |
+ return crazy_lib |
+ |
+def GetCandidateLibraries(library_name): |
+ """Returns a list of candidate library filenames. |
+ |
+ Args: |
+ library_name: basename of the library to match. |
+ |
+ Returns: |
+ A list of matching library filenames for library_name. |
+ """ |
+ return GetCandidates( |
+ ['lib', 'lib.target'], library_name, |
+ lambda filename: filter(os.path.exists, [filename])) |
+ |
def TranslateLibPath(lib): |
# SymbolInformation(lib, addr) receives lib as the path from symbols |
# root to the symbols file. This needs to be translated to point to the |
@@ -138,21 +293,20 @@ def TranslateLibPath(lib): |
# If the .so is not found somewhere in CHROME_SYMBOLS_DIR, leave it |
# untranslated in case it is an Android symbol in SYMBOLS_DIR. |
library_name = os.path.basename(lib) |
- out_dir = os.environ.get('CHROMIUM_OUT_DIR', 'out') |
- candidate_dirs = ['.', |
- os.path.join(out_dir, 'Debug', 'lib'), |
- os.path.join(out_dir, 'Debug', 'lib.target'), |
- os.path.join(out_dir, 'Release', 'lib'), |
- os.path.join(out_dir, 'Release', 'lib.target'), |
- ] |
- |
- candidate_libraries = map( |
- lambda d: ('%s/%s/%s' % (CHROME_SYMBOLS_DIR, d, library_name)), |
- candidate_dirs) |
- candidate_libraries = filter(os.path.exists, candidate_libraries) |
- candidate_libraries = sorted(candidate_libraries, |
- key=os.path.getmtime, reverse=True) |
+ # The filename in the stack trace maybe an APK name rather than a library |
+ # name. This happens when the library was loaded directly from inside the |
+ # APK. If this is the case we try to figure out the library name by looking |
+ # for a matching APK file and finding the name of the library in contains. |
+ # The name of the APK file on the device is of the form |
+ # <package_name>-<number>.apk. The APK file on the host may have any name |
+ # so we look at the APK badging to see if the package name matches. |
+ if re.search('-[0-9]+[.]apk$', library_name): |
+ mapping = MapDeviceApkToLibrary(library_name) |
+ if mapping: |
+ library_name = mapping |
+ |
+ candidate_libraries = GetCandidateLibraries(library_name) |
if not candidate_libraries: |
return lib |