| 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..0f58df64cd04e15e08583f044a3c589dc8dbcf2b
|
| --- /dev/null
|
| +++ b/third_party/android_platform/development/scripts/symbol.py
|
| @@ -0,0 +1,344 @@
|
| +#!/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 os
|
| +import re
|
| +import subprocess
|
| +
|
| +ANDROID_BUILD_TOP = os.environ["ANDROID_BUILD_TOP"]
|
| +if not ANDROID_BUILD_TOP:
|
| + ANDROID_BUILD_TOP = "."
|
| +
|
| +def FindSymbolsDir():
|
| + saveddir = os.getcwd()
|
| + os.chdir(ANDROID_BUILD_TOP)
|
| + try:
|
| + cmd = ("CALLED_FROM_SETUP=true BUILD_SYSTEM=build/core "
|
| + "SRC_TARGET_DIR=build/target make -f build/core/config.mk "
|
| + "dumpvar-abs-TARGET_OUT_UNSTRIPPED")
|
| + stream = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True).stdout
|
| + return os.path.join(ANDROID_BUILD_TOP, stream.read().strip())
|
| + finally:
|
| + os.chdir(saveddir)
|
| +
|
| +SYMBOLS_DIR = FindSymbolsDir()
|
| +
|
| +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"""
|
| + if not toolchain_info:
|
| + toolchain_info = FindToolchain()
|
| + (label, platform, target) = toolchain_info
|
| + return os.path.join(ANDROID_BUILD_TOP, "prebuilts/gcc", Uname(), platform, label, "bin",
|
| + target + "-" + 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.
|
| + if ARCH == "arm":
|
| + gcc_version = os.environ["TARGET_GCC_VERSION"]
|
| + known_toolchains = [
|
| + ("arm-linux-androideabi-" + gcc_version, "arm", "arm-linux-androideabi"),
|
| + ]
|
| + elif ARCH =="x86":
|
| + known_toolchains = [
|
| + ("i686-android-linux-4.4.3", "x86", "i686-android-linux")
|
| + ]
|
| + 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
|
| + return toolchain_info
|
| +
|
| + raise Exception("Could not find tool chain")
|
| +
|
| +def SymbolInformation(lib, addr):
|
| + """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.
|
| + """
|
| + info = SymbolInformationForSet(lib, set([addr]))
|
| + return (info and info.get(addr)) or [(None, None, None)]
|
| +
|
| +
|
| +def SymbolInformationForSet(lib, unique_addrs):
|
| + """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
|
| +
|
| + addr_to_objdump = CallObjdumpForSet(lib, unique_addrs)
|
| + if not addr_to_objdump:
|
| + return None
|
| +
|
| + 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
|
| +
|
| +
|
| +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.exists(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
|
| +
|
| +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
|
| +
|
| + addrs = sorted(unique_addrs)
|
| + start_addr_dec = str(StripPC(int(addrs[0], 16)))
|
| + stop_addr_dec = str(StripPC(int(addrs[-1], 16)) + 8)
|
| + cmd = [ToolPath("objdump"),
|
| + "--section=.text",
|
| + "--demangle",
|
| + "--disassemble",
|
| + "--start-address=" + start_addr_dec,
|
| + "--stop-address=" + stop_addr_dec,
|
| + symbols]
|
| +
|
| + # 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]*.*$")
|
| +
|
| + current_symbol = None # The current function symbol in the disassembly.
|
| + current_symbol_addr = 0 # The address of the current function.
|
| + addr_index = 0 # The address that we are currently looking for.
|
| +
|
| + stream = subprocess.Popen(cmd, stdout=subprocess.PIPE).stdout
|
| + result = {}
|
| + 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)
|
| + target_addr = addrs[addr_index]
|
| + 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)
|
| + addr_index += 1
|
| + if addr_index >= len(addrs):
|
| + break
|
| + 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)
|
|
|