blob: b0e94d825e9468588b92812f47f96fdb32c4fc6e [file] [log] [blame]
Iliyan Malchev4929d6a2011-08-04 17:44:40 -07001#!/usr/bin/python
2#
3# Copyright 2006 Google Inc. All Rights Reserved.
4
5"""Module for looking up symbolic debugging information.
6
7The information can include symbol names, offsets, and source locations.
8"""
9
10import os
11import re
12import subprocess
13
14ANDROID_BUILD_TOP = os.environ["ANDROID_BUILD_TOP"]
15if not ANDROID_BUILD_TOP:
16 ANDROID_BUILD_TOP = "."
17
18def 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
30SYMBOLS_DIR = FindSymbolsDir()
31
32def 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
44def 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
Romain Guyfdc35192012-11-30 14:55:28 -080049 return os.path.join(ANDROID_BUILD_TOP, "prebuilts", "gcc", Uname(), "arm", label, "bin",
Iliyan Malchev4929d6a2011-08-04 17:44:40 -070050 target + "-" + tool)
51
52def 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 = [
Romain Guyfdc35192012-11-30 14:55:28 -080064 ("arm-eabi-4.6", "arm-eabi"),
Iliyan Malchev4929d6a2011-08-04 17:44:40 -070065 ("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
80TOOLCHAIN_INFO = FindToolchain()
81
82def 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
106def 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
151def 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
191def 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
275def 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
285def FormatSymbolWithOffset(symbol, offset):
286 if offset == 0:
287 return symbol
288 return "%s+%d" % (symbol, offset)