Home | History | Annotate | Download | only in scripts
      1 #!/usr/bin/python
      2 #
      3 # Copyright 2006 Google Inc. All Rights Reserved.
      4 
      5 """Module for looking up symbolic debugging information.
      6 
      7 The information can include symbol names, offsets, and source locations.
      8 """
      9 
     10 import os
     11 import re
     12 import subprocess
     13 
     14 ANDROID_BUILD_TOP = os.environ["ANDROID_BUILD_TOP"]
     15 if not ANDROID_BUILD_TOP:
     16   ANDROID_BUILD_TOP = "."
     17 
     18 def FindSymbolsDir():
     19   saveddir = os.getcwd()
     20   os.chdir(ANDROID_BUILD_TOP)
     21   try:
     22     cmd = ("CALLED_FROM_SETUP=true BUILD_SYSTEM=build/core "
     23            "SRC_TARGET_DIR=build/target make -f build/core/config.mk "
     24            "dumpvar-abs-TARGET_OUT_UNSTRIPPED")
     25     stream = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True).stdout
     26     return os.path.join(ANDROID_BUILD_TOP, stream.read().strip())
     27   finally:
     28     os.chdir(saveddir)
     29 
     30 SYMBOLS_DIR = FindSymbolsDir()
     31 
     32 def Uname():
     33   """'uname' for constructing prebuilt/<...> and out/host/<...> paths."""
     34   uname = os.uname()[0]
     35   if uname == "Darwin":
     36     proc = os.uname()[-1]
     37     if proc == "i386" or proc == "x86_64":
     38       return "darwin-x86"
     39     return "darwin-ppc"
     40   if uname == "Linux":
     41     return "linux-x86"
     42   return uname
     43 
     44 def ToolPath(tool, toolchain_info=None):
     45   """Return a full qualified path to the specified tool"""
     46   if not toolchain_info:
     47     toolchain_info = TOOLCHAIN_INFO
     48   (label, target) = toolchain_info
     49   return os.path.join(ANDROID_BUILD_TOP, "prebuilts", "gcc", Uname(), "arm", label, "bin",
     50                      target + "-" + tool)
     51 
     52 def FindToolchain():
     53   """Look for the latest available toolchain
     54 
     55   Args:
     56     None
     57 
     58   Returns:
     59     A pair of strings containing toolchain label and target prefix.
     60   """
     61 
     62   ## Known toolchains, newer ones in the front.
     63   known_toolchains = [
     64     ("arm-eabi-4.6", "arm-eabi"),
     65     ("arm-linux-androideabi-4.4.x", "arm-linux-androideabi"),
     66     ("arm-eabi-4.4.3", "arm-eabi"),
     67     ("arm-eabi-4.4.0", "arm-eabi"),
     68     ("arm-eabi-4.3.1", "arm-eabi"),
     69     ("arm-eabi-4.2.1", "arm-eabi")
     70   ]
     71 
     72   # Look for addr2line to check for valid toolchain path.
     73   for (label, target) in known_toolchains:
     74     toolchain_info = (label, target);
     75     if os.path.exists(ToolPath("addr2line", toolchain_info)):
     76       return toolchain_info
     77 
     78   raise Exception("Could not find tool chain")
     79 
     80 TOOLCHAIN_INFO = FindToolchain()
     81 
     82 def SymbolInformation(lib, addr):
     83   """Look up symbol information about an address.
     84 
     85   Args:
     86     lib: library (or executable) pathname containing symbols
     87     addr: string hexidecimal address
     88 
     89   Returns:
     90     For a given library and address, return tuple of: (source_symbol,
     91     source_location, object_symbol_with_offset) the values may be None
     92     if the information was unavailable.
     93 
     94     source_symbol may not be a prefix of object_symbol_with_offset if
     95     the source function was inlined in the object code of another
     96     function.
     97 
     98     usually you want to display the object_symbol_with_offset and
     99     source_location, the source_symbol is only useful to show if the
    100     address was from an inlined function.
    101   """
    102   info = SymbolInformationForSet(lib, set([addr]))
    103   return (info and info.get(addr)) or (None, None, None)
    104 
    105 
    106 def SymbolInformationForSet(lib, unique_addrs):
    107   """Look up symbol information for a set of addresses from the given library.
    108 
    109   Args:
    110     lib: library (or executable) pathname containing symbols
    111     unique_addrs: set of hexidecimal addresses
    112 
    113   Returns:
    114     For a given library and set of addresses, returns a dictionary of the form
    115     {addr: (source_symbol, source_location, object_symbol_with_offset)}. The
    116     values may be None if the information was unavailable.
    117 
    118     For a given address, source_symbol may not be a prefix of
    119     object_symbol_with_offset if the source function was inlined in the
    120     object code of another function.
    121 
    122     Usually you want to display the object_symbol_with_offset and
    123     source_location; the source_symbol is only useful to show if the
    124     address was from an inlined function.
    125   """
    126   if not lib:
    127     return None
    128 
    129   addr_to_line = CallAddr2LineForSet(lib, unique_addrs)
    130   if not addr_to_line:
    131     return None
    132 
    133   addr_to_objdump = CallObjdumpForSet(lib, unique_addrs)
    134   if not addr_to_objdump:
    135     return None
    136 
    137   result = {}
    138   for addr in unique_addrs:
    139     (source_symbol, source_location) = addr_to_line.get(addr, (None, None))
    140     if addr in addr_to_objdump:
    141       (object_symbol, object_offset) = addr_to_objdump.get(addr)
    142       object_symbol_with_offset = FormatSymbolWithOffset(object_symbol,
    143                                                          object_offset)
    144     else:
    145       object_symbol_with_offset = None
    146     result[addr] = (source_symbol, source_location, object_symbol_with_offset)
    147 
    148   return result
    149 
    150 
    151 def CallAddr2LineForSet(lib, unique_addrs):
    152   """Look up line and symbol information for a set of addresses.
    153 
    154   Args:
    155     lib: library (or executable) pathname containing symbols
    156     unique_addrs: set of string hexidecimal addresses look up.
    157 
    158   Returns:
    159     A dictionary of the form {addr: (symbol, file:line)}. The values may
    160     be (None, None) if the address could not be looked up.
    161   """
    162   if not lib:
    163     return None
    164 
    165 
    166   symbols = SYMBOLS_DIR + lib
    167   if not os.path.exists(symbols):
    168     return None
    169 
    170   (label, target) = TOOLCHAIN_INFO
    171   cmd = [ToolPath("addr2line"), "--functions", "--demangle", "--exe=" + symbols]
    172   child = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
    173 
    174   result = {}
    175   addrs = sorted(unique_addrs)
    176   for addr in addrs:
    177     child.stdin.write("0x%s\n" % addr)
    178     child.stdin.flush()
    179     symbol = child.stdout.readline().strip()
    180     if symbol == "??":
    181       symbol = None
    182     location = child.stdout.readline().strip()
    183     if location == "??:0":
    184       location = None
    185     result[addr] = (symbol, location)
    186   child.stdin.close()
    187   child.stdout.close()
    188   return result
    189 
    190 
    191 def CallObjdumpForSet(lib, unique_addrs):
    192   """Use objdump to find out the names of the containing functions.
    193 
    194   Args:
    195     lib: library (or executable) pathname containing symbols
    196     unique_addrs: set of string hexidecimal addresses to find the functions for.
    197 
    198   Returns:
    199     A dictionary of the form {addr: (string symbol, offset)}.
    200   """
    201   if not lib:
    202     return None
    203 
    204   symbols = SYMBOLS_DIR + lib
    205   if not os.path.exists(symbols):
    206     return None
    207 
    208   symbols = SYMBOLS_DIR + lib
    209   if not os.path.exists(symbols):
    210     return None
    211 
    212   addrs = sorted(unique_addrs)
    213   start_addr_hex = addrs[0]
    214   stop_addr_dec = str(int(addrs[-1], 16) + 8)
    215   cmd = [ToolPath("objdump"),
    216          "--section=.text",
    217          "--demangle",
    218          "--disassemble",
    219          "--start-address=0x" + start_addr_hex,
    220          "--stop-address=" + stop_addr_dec,
    221          symbols]
    222 
    223   # Function lines look like:
    224   #   000177b0 <android::IBinder::~IBinder()+0x2c>:
    225   # We pull out the address and function first. Then we check for an optional
    226   # offset. This is tricky due to functions that look like "operator+(..)+0x2c"
    227   func_regexp = re.compile("(^[a-f0-9]*) \<(.*)\>:$")
    228   offset_regexp = re.compile("(.*)\+0x([a-f0-9]*)")
    229 
    230   # A disassembly line looks like:
    231   #   177b2:	b510      	push	{r4, lr}
    232   asm_regexp = re.compile("(^[ a-f0-9]*):[ a-f0-0]*.*$")
    233 
    234   current_symbol = None    # The current function symbol in the disassembly.
    235   current_symbol_addr = 0  # The address of the current function.
    236   addr_index = 0  # The address that we are currently looking for.
    237 
    238   stream = subprocess.Popen(cmd, stdout=subprocess.PIPE).stdout
    239   result = {}
    240   for line in stream:
    241     # Is it a function line like:
    242     #   000177b0 <android::IBinder::~IBinder()>:
    243     components = func_regexp.match(line)
    244     if components:
    245       # This is a new function, so record the current function and its address.
    246       current_symbol_addr = int(components.group(1), 16)
    247       current_symbol = components.group(2)
    248 
    249       # Does it have an optional offset like: "foo(..)+0x2c"?
    250       components = offset_regexp.match(current_symbol)
    251       if components:
    252         current_symbol = components.group(1)
    253         offset = components.group(2)
    254         if offset:
    255           current_symbol_addr -= int(offset, 16)
    256 
    257     # Is it an disassembly line like:
    258     #   177b2:	b510      	push	{r4, lr}
    259     components = asm_regexp.match(line)
    260     if components:
    261       addr = components.group(1)
    262       target_addr = addrs[addr_index]
    263       i_addr = int(addr, 16)
    264       i_target = int(target_addr, 16)
    265       if i_addr == i_target:
    266         result[target_addr] = (current_symbol, i_target - current_symbol_addr)
    267         addr_index += 1
    268         if addr_index >= len(addrs):
    269           break
    270   stream.close()
    271 
    272   return result
    273 
    274 
    275 def CallCppFilt(mangled_symbol):
    276   cmd = [ToolPath("c++filt")]
    277   process = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
    278   process.stdin.write(mangled_symbol)
    279   process.stdin.write("\n")
    280   process.stdin.close()
    281   demangled_symbol = process.stdout.readline().strip()
    282   process.stdout.close()
    283   return demangled_symbol
    284 
    285 def FormatSymbolWithOffset(symbol, offset):
    286   if offset == 0:
    287     return symbol
    288   return "%s+%d" % (symbol, offset)
    289