Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(301)

Unified Diff: third_party/android_platform/development/scripts/symbol.py

Issue 1399343002: Roll Mojo to b88737ed62969ce3203085748f0d53ff4f09ba5b. (Closed) Base URL: https://github.com/domokit/monet.git@master
Patch Set: Created 5 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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
new file mode 100755
index 0000000000000000000000000000000000000000..5069d9e0f350687a4e60437940bb838c602ffaa9
--- /dev/null
+++ b/third_party/android_platform/development/scripts/symbol.py
@@ -0,0 +1,576 @@
+#!/usr/bin/python
+#
+# Copyright (C) 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Module for looking up symbolic debugging information.
+
+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)
+ANDROID_BUILD_TOP = CHROME_SRC
+SYMBOLS_DIR = CHROME_SRC
+CHROME_SYMBOLS_DIR = CHROME_SRC
+
+ARCH = "arm"
+
+TOOLCHAIN_INFO = None
+
+def Uname():
+ """'uname' for constructing prebuilt/<...> and out/host/<...> paths."""
+ uname = os.uname()[0]
+ if uname == "Darwin":
+ proc = os.uname()[-1]
+ if proc == "i386" or proc == "x86_64":
+ return "darwin-x86"
+ return "darwin-ppc"
+ if uname == "Linux":
+ return "linux-x86"
+ return uname
+
+def ToolPath(tool, toolchain_info=None):
+ """Return a full qualified path to the specified tool"""
+ # ToolPath looks for the tools in the completely incorrect directory.
+ # This looks in the checked in android_tools.
+ if ARCH == "arm":
+ toolchain_source = "arm-linux-androideabi-4.9"
+ toolchain_prefix = "arm-linux-androideabi"
+ ndk = "ndk"
+ elif ARCH == "arm64":
+ toolchain_source = "aarch64-linux-android-4.9"
+ toolchain_prefix = "aarch64-linux-android"
+ ndk = "ndk"
+ elif ARCH == "x86":
+ toolchain_source = "x86-4.9"
+ toolchain_prefix = "i686-linux-android"
+ ndk = "ndk"
+ elif ARCH == "x86_64":
+ toolchain_source = "x86_64-4.9"
+ toolchain_prefix = "x86_64-linux-android"
+ ndk = "ndk"
+ elif ARCH == "mips":
+ toolchain_source = "mipsel-linux-android-4.9"
+ toolchain_prefix = "mipsel-linux-android"
+ ndk = "ndk"
+ else:
+ raise Exception("Could not find tool chain")
+
+ toolchain_subdir = (
+ "third_party/android_tools/%s/toolchains/%s/prebuilt/linux-x86_64/bin" %
+ (ndk, toolchain_source))
+
+ return os.path.join(CHROME_SRC,
+ toolchain_subdir,
+ toolchain_prefix + "-" + tool)
+
+def FindToolchain():
+ """Look for the latest available toolchain
+
+ Args:
+ None
+
+ Returns:
+ A pair of strings containing toolchain label and target prefix.
+ """
+ global TOOLCHAIN_INFO
+ if TOOLCHAIN_INFO is not None:
+ return TOOLCHAIN_INFO
+
+ ## Known toolchains, newer ones in the front.
+ gcc_version = "4.9"
+ if ARCH == "arm64":
+ known_toolchains = [
+ ("aarch64-linux-android-" + gcc_version, "aarch64", "aarch64-linux-android")
+ ]
+ elif ARCH == "arm":
+ known_toolchains = [
+ ("arm-linux-androideabi-" + gcc_version, "arm", "arm-linux-androideabi")
+ ]
+ elif ARCH =="x86":
+ known_toolchains = [
+ ("x86-" + gcc_version, "x86", "i686-linux-android")
+ ]
+ elif ARCH =="x86_64":
+ known_toolchains = [
+ ("x86_64-" + gcc_version, "x86_64", "x86_64-linux-android")
+ ]
+ elif ARCH == "mips":
+ known_toolchains = [
+ ("mipsel-linux-android-" + gcc_version, "mips", "mipsel-linux-android")
+ ]
+ else:
+ known_toolchains = []
+
+ # Look for addr2line to check for valid toolchain path.
+ for (label, platform, target) in known_toolchains:
+ toolchain_info = (label, platform, target);
+ if os.path.exists(ToolPath("addr2line", toolchain_info)):
+ TOOLCHAIN_INFO = toolchain_info
+ print "Using toolchain from :" + ToolPath("", TOOLCHAIN_INFO)
+ return toolchain_info
+
+ 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)
+ # candidates = ['/usr/local/google/home/qsr/programmes/mojo/src/out/android_Debug/libmojo_shell.so']
+ 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
+ # correct .so path. If the user doesn't explicitly specify which directory to
+ # use, then use the most recently updated one in one of the known directories.
+ # 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)
+
+ # 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
+
+ library_path = os.path.relpath(candidate_libraries[0], SYMBOLS_DIR)
+ return '/' + library_path
+
+def SymbolInformation(lib, addr, get_detailed_info):
+ """Look up symbol information about an address.
+
+ Args:
+ lib: library (or executable) pathname containing symbols
+ addr: string hexidecimal address
+
+ Returns:
+ A list of the form [(source_symbol, source_location,
+ object_symbol_with_offset)].
+
+ If the function has been inlined then the list may contain
+ more than one element with the symbols for the most deeply
+ nested inlined location appearing first. The list is
+ always non-empty, even if no information is available.
+
+ Usually you want to display the source_location and
+ object_symbol_with_offset from the last element in the list.
+ """
+ lib = TranslateLibPath(lib)
+ info = SymbolInformationForSet(lib, set([addr]), get_detailed_info)
+ return (info and info.get(addr)) or [(None, None, None)]
+
+
+def SymbolInformationForSet(lib, unique_addrs, get_detailed_info):
+ """Look up symbol information for a set of addresses from the given library.
+
+ Args:
+ lib: library (or executable) pathname containing symbols
+ unique_addrs: set of hexidecimal addresses
+
+ Returns:
+ A dictionary of the form {addr: [(source_symbol, source_location,
+ object_symbol_with_offset)]} where each address has a list of
+ associated symbols and locations. The list is always non-empty.
+
+ If the function has been inlined then the list may contain
+ more than one element with the symbols for the most deeply
+ nested inlined location appearing first. The list is
+ always non-empty, even if no information is available.
+
+ Usually you want to display the source_location and
+ object_symbol_with_offset from the last element in the list.
+ """
+ if not lib:
+ return None
+
+ addr_to_line = CallAddr2LineForSet(lib, unique_addrs)
+ if not addr_to_line:
+ return None
+
+ if get_detailed_info:
+ addr_to_objdump = CallObjdumpForSet(lib, unique_addrs)
+ if not addr_to_objdump:
+ return None
+ else:
+ addr_to_objdump = dict((addr, ("", 0)) for addr in unique_addrs)
+
+ result = {}
+ for addr in unique_addrs:
+ source_info = addr_to_line.get(addr)
+ if not source_info:
+ source_info = [(None, None)]
+ if addr in addr_to_objdump:
+ (object_symbol, object_offset) = addr_to_objdump.get(addr)
+ object_symbol_with_offset = FormatSymbolWithOffset(object_symbol,
+ object_offset)
+ else:
+ object_symbol_with_offset = None
+ result[addr] = [(source_symbol, source_location, object_symbol_with_offset)
+ for (source_symbol, source_location) in source_info]
+
+ return result
+
+
+class MemoizedForSet(object):
+ def __init__(self, fn):
+ self.fn = fn
+ self.cache = {}
+
+ def __call__(self, lib, unique_addrs):
+ lib_cache = self.cache.setdefault(lib, {})
+
+ no_cache = filter(lambda x: x not in lib_cache, unique_addrs)
+ if no_cache:
+ lib_cache.update((k, None) for k in no_cache)
+ result = self.fn(lib, no_cache)
+ if result:
+ lib_cache.update(result)
+
+ return dict((k, lib_cache[k]) for k in unique_addrs if lib_cache[k])
+
+
+@MemoizedForSet
+def CallAddr2LineForSet(lib, unique_addrs):
+ """Look up line and symbol information for a set of addresses.
+
+ Args:
+ lib: library (or executable) pathname containing symbols
+ unique_addrs: set of string hexidecimal addresses look up.
+
+ Returns:
+ A dictionary of the form {addr: [(symbol, file:line)]} where
+ each address has a list of associated symbols and locations
+ or an empty list if no symbol information was found.
+
+ If the function has been inlined then the list may contain
+ more than one element with the symbols for the most deeply
+ nested inlined location appearing first.
+ """
+ if not lib:
+ return None
+
+
+ symbols = SYMBOLS_DIR + lib
+ if not os.path.isfile(symbols):
+ return None
+
+ (label, platform, target) = FindToolchain()
+ cmd = [ToolPath("addr2line"), "--functions", "--inlines",
+ "--demangle", "--exe=" + symbols]
+ child = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
+
+ result = {}
+ addrs = sorted(unique_addrs)
+ for addr in addrs:
+ child.stdin.write("0x%s\n" % addr)
+ child.stdin.flush()
+ records = []
+ first = True
+ while True:
+ symbol = child.stdout.readline().strip()
+ if symbol == "??":
+ symbol = None
+ location = child.stdout.readline().strip()
+ if location == "??:0":
+ location = None
+ if symbol is None and location is None:
+ break
+ records.append((symbol, location))
+ if first:
+ # Write a blank line as a sentinel so we know when to stop
+ # reading inlines from the output.
+ # The blank line will cause addr2line to emit "??\n??:0\n".
+ child.stdin.write("\n")
+ first = False
+ result[addr] = records
+ child.stdin.close()
+ child.stdout.close()
+ return result
+
+
+def StripPC(addr):
+ """Strips the Thumb bit a program counter address when appropriate.
+
+ Args:
+ addr: the program counter address
+
+ Returns:
+ The stripped program counter address.
+ """
+ global ARCH
+
+ if ARCH == "arm":
+ return addr & ~1
+ return addr
+
+@MemoizedForSet
+def CallObjdumpForSet(lib, unique_addrs):
+ """Use objdump to find out the names of the containing functions.
+
+ Args:
+ lib: library (or executable) pathname containing symbols
+ unique_addrs: set of string hexidecimal addresses to find the functions for.
+
+ Returns:
+ A dictionary of the form {addr: (string symbol, offset)}.
+ """
+ if not lib:
+ return None
+
+ symbols = SYMBOLS_DIR + lib
+ if not os.path.exists(symbols):
+ return None
+
+ symbols = SYMBOLS_DIR + lib
+ if not os.path.exists(symbols):
+ return None
+
+ result = {}
+
+ # Function lines look like:
+ # 000177b0 <android::IBinder::~IBinder()+0x2c>:
+ # We pull out the address and function first. Then we check for an optional
+ # offset. This is tricky due to functions that look like "operator+(..)+0x2c"
+ func_regexp = re.compile("(^[a-f0-9]*) \<(.*)\>:$")
+ offset_regexp = re.compile("(.*)\+0x([a-f0-9]*)")
+
+ # A disassembly line looks like:
+ # 177b2: b510 push {r4, lr}
+ asm_regexp = re.compile("(^[ a-f0-9]*):[ a-f0-0]*.*$")
+
+ for target_addr in unique_addrs:
+ start_addr_dec = str(StripPC(int(target_addr, 16)))
+ stop_addr_dec = str(StripPC(int(target_addr, 16)) + 8)
+ cmd = [ToolPath("objdump"),
+ "--section=.text",
+ "--demangle",
+ "--disassemble",
+ "--start-address=" + start_addr_dec,
+ "--stop-address=" + stop_addr_dec,
+ symbols]
+
+ current_symbol = None # The current function symbol in the disassembly.
+ current_symbol_addr = 0 # The address of the current function.
+
+ stream = subprocess.Popen(cmd, stdout=subprocess.PIPE).stdout
+ for line in stream:
+ # Is it a function line like:
+ # 000177b0 <android::IBinder::~IBinder()>:
+ components = func_regexp.match(line)
+ if components:
+ # This is a new function, so record the current function and its address.
+ current_symbol_addr = int(components.group(1), 16)
+ current_symbol = components.group(2)
+
+ # Does it have an optional offset like: "foo(..)+0x2c"?
+ components = offset_regexp.match(current_symbol)
+ if components:
+ current_symbol = components.group(1)
+ offset = components.group(2)
+ if offset:
+ current_symbol_addr -= int(offset, 16)
+
+ # Is it an disassembly line like:
+ # 177b2: b510 push {r4, lr}
+ components = asm_regexp.match(line)
+ if components:
+ addr = components.group(1)
+ i_addr = int(addr, 16)
+ i_target = StripPC(int(target_addr, 16))
+ if i_addr == i_target:
+ result[target_addr] = (current_symbol, i_target - current_symbol_addr)
+ stream.close()
+
+ return result
+
+
+def CallCppFilt(mangled_symbol):
+ cmd = [ToolPath("c++filt")]
+ process = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
+ process.stdin.write(mangled_symbol)
+ process.stdin.write("\n")
+ process.stdin.close()
+ demangled_symbol = process.stdout.readline().strip()
+ process.stdout.close()
+ return demangled_symbol
+
+def FormatSymbolWithOffset(symbol, offset):
+ if offset == 0:
+ return symbol
+ return "%s+%d" % (symbol, offset)

Powered by Google App Engine
This is Rietveld 408576698