blob: 0f58df64cd04e15e08583f044a3c589dc8dbcf2b [file] [log] [blame]
Iliyan Malchev4929d6a2011-08-04 17:44:40 -07001#!/usr/bin/python
2#
Ben Chengb42dad02013-04-25 15:14:04 -07003# Copyright (C) 2013 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
Iliyan Malchev4929d6a2011-08-04 17:44:40 -070016
17"""Module for looking up symbolic debugging information.
18
19The information can include symbol names, offsets, and source locations.
20"""
21
22import os
23import re
24import subprocess
25
26ANDROID_BUILD_TOP = os.environ["ANDROID_BUILD_TOP"]
27if not ANDROID_BUILD_TOP:
28 ANDROID_BUILD_TOP = "."
29
30def FindSymbolsDir():
31 saveddir = os.getcwd()
32 os.chdir(ANDROID_BUILD_TOP)
33 try:
34 cmd = ("CALLED_FROM_SETUP=true BUILD_SYSTEM=build/core "
35 "SRC_TARGET_DIR=build/target make -f build/core/config.mk "
36 "dumpvar-abs-TARGET_OUT_UNSTRIPPED")
37 stream = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True).stdout
38 return os.path.join(ANDROID_BUILD_TOP, stream.read().strip())
39 finally:
40 os.chdir(saveddir)
41
42SYMBOLS_DIR = FindSymbolsDir()
43
Ben Chengb42dad02013-04-25 15:14:04 -070044ARCH = "arm"
45
46TOOLCHAIN_INFO = None
47
Iliyan Malchev4929d6a2011-08-04 17:44:40 -070048def Uname():
49 """'uname' for constructing prebuilt/<...> and out/host/<...> paths."""
50 uname = os.uname()[0]
51 if uname == "Darwin":
52 proc = os.uname()[-1]
53 if proc == "i386" or proc == "x86_64":
54 return "darwin-x86"
55 return "darwin-ppc"
56 if uname == "Linux":
57 return "linux-x86"
58 return uname
59
60def ToolPath(tool, toolchain_info=None):
61 """Return a full qualified path to the specified tool"""
62 if not toolchain_info:
Ben Chengb42dad02013-04-25 15:14:04 -070063 toolchain_info = FindToolchain()
64 (label, platform, target) = toolchain_info
65 return os.path.join(ANDROID_BUILD_TOP, "prebuilts/gcc", Uname(), platform, label, "bin",
Iliyan Malchev4929d6a2011-08-04 17:44:40 -070066 target + "-" + tool)
67
68def FindToolchain():
69 """Look for the latest available toolchain
70
71 Args:
72 None
73
74 Returns:
75 A pair of strings containing toolchain label and target prefix.
76 """
Ben Chengb42dad02013-04-25 15:14:04 -070077 global TOOLCHAIN_INFO
78 if TOOLCHAIN_INFO is not None:
79 return TOOLCHAIN_INFO
Iliyan Malchev4929d6a2011-08-04 17:44:40 -070080
81 ## Known toolchains, newer ones in the front.
Ben Chengb42dad02013-04-25 15:14:04 -070082 if ARCH == "arm":
83 gcc_version = os.environ["TARGET_GCC_VERSION"]
84 known_toolchains = [
85 ("arm-linux-androideabi-" + gcc_version, "arm", "arm-linux-androideabi"),
86 ]
87 elif ARCH =="x86":
88 known_toolchains = [
89 ("i686-android-linux-4.4.3", "x86", "i686-android-linux")
90 ]
91 else:
92 known_toolchains = []
Iliyan Malchev4929d6a2011-08-04 17:44:40 -070093
94 # Look for addr2line to check for valid toolchain path.
Ben Chengb42dad02013-04-25 15:14:04 -070095 for (label, platform, target) in known_toolchains:
96 toolchain_info = (label, platform, target);
Iliyan Malchev4929d6a2011-08-04 17:44:40 -070097 if os.path.exists(ToolPath("addr2line", toolchain_info)):
Ben Chengb42dad02013-04-25 15:14:04 -070098 TOOLCHAIN_INFO = toolchain_info
Iliyan Malchev4929d6a2011-08-04 17:44:40 -070099 return toolchain_info
100
101 raise Exception("Could not find tool chain")
102
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700103def SymbolInformation(lib, addr):
104 """Look up symbol information about an address.
105
106 Args:
107 lib: library (or executable) pathname containing symbols
108 addr: string hexidecimal address
109
110 Returns:
Ben Chengb42dad02013-04-25 15:14:04 -0700111 A list of the form [(source_symbol, source_location,
112 object_symbol_with_offset)].
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700113
Ben Chengb42dad02013-04-25 15:14:04 -0700114 If the function has been inlined then the list may contain
115 more than one element with the symbols for the most deeply
116 nested inlined location appearing first. The list is
117 always non-empty, even if no information is available.
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700118
Ben Chengb42dad02013-04-25 15:14:04 -0700119 Usually you want to display the source_location and
120 object_symbol_with_offset from the last element in the list.
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700121 """
122 info = SymbolInformationForSet(lib, set([addr]))
Ben Chengb42dad02013-04-25 15:14:04 -0700123 return (info and info.get(addr)) or [(None, None, None)]
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700124
125
126def SymbolInformationForSet(lib, unique_addrs):
127 """Look up symbol information for a set of addresses from the given library.
128
129 Args:
130 lib: library (or executable) pathname containing symbols
131 unique_addrs: set of hexidecimal addresses
132
133 Returns:
Ben Chengb42dad02013-04-25 15:14:04 -0700134 A dictionary of the form {addr: [(source_symbol, source_location,
135 object_symbol_with_offset)]} where each address has a list of
136 associated symbols and locations. The list is always non-empty.
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700137
Ben Chengb42dad02013-04-25 15:14:04 -0700138 If the function has been inlined then the list may contain
139 more than one element with the symbols for the most deeply
140 nested inlined location appearing first. The list is
141 always non-empty, even if no information is available.
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700142
Ben Chengb42dad02013-04-25 15:14:04 -0700143 Usually you want to display the source_location and
144 object_symbol_with_offset from the last element in the list.
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700145 """
146 if not lib:
147 return None
148
149 addr_to_line = CallAddr2LineForSet(lib, unique_addrs)
150 if not addr_to_line:
151 return None
152
153 addr_to_objdump = CallObjdumpForSet(lib, unique_addrs)
154 if not addr_to_objdump:
155 return None
156
157 result = {}
158 for addr in unique_addrs:
Ben Chengb42dad02013-04-25 15:14:04 -0700159 source_info = addr_to_line.get(addr)
160 if not source_info:
161 source_info = [(None, None)]
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700162 if addr in addr_to_objdump:
163 (object_symbol, object_offset) = addr_to_objdump.get(addr)
164 object_symbol_with_offset = FormatSymbolWithOffset(object_symbol,
165 object_offset)
166 else:
167 object_symbol_with_offset = None
Ben Chengb42dad02013-04-25 15:14:04 -0700168 result[addr] = [(source_symbol, source_location, object_symbol_with_offset)
169 for (source_symbol, source_location) in source_info]
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700170
171 return result
172
173
174def CallAddr2LineForSet(lib, unique_addrs):
175 """Look up line and symbol information for a set of addresses.
176
177 Args:
178 lib: library (or executable) pathname containing symbols
179 unique_addrs: set of string hexidecimal addresses look up.
180
181 Returns:
Ben Chengb42dad02013-04-25 15:14:04 -0700182 A dictionary of the form {addr: [(symbol, file:line)]} where
183 each address has a list of associated symbols and locations
184 or an empty list if no symbol information was found.
185
186 If the function has been inlined then the list may contain
187 more than one element with the symbols for the most deeply
188 nested inlined location appearing first.
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700189 """
190 if not lib:
191 return None
192
193
194 symbols = SYMBOLS_DIR + lib
195 if not os.path.exists(symbols):
196 return None
197
Ben Chengb42dad02013-04-25 15:14:04 -0700198 (label, platform, target) = FindToolchain()
199 cmd = [ToolPath("addr2line"), "--functions", "--inlines",
200 "--demangle", "--exe=" + symbols]
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700201 child = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
202
203 result = {}
204 addrs = sorted(unique_addrs)
205 for addr in addrs:
206 child.stdin.write("0x%s\n" % addr)
207 child.stdin.flush()
Ben Chengb42dad02013-04-25 15:14:04 -0700208 records = []
209 first = True
210 while True:
211 symbol = child.stdout.readline().strip()
212 if symbol == "??":
213 symbol = None
214 location = child.stdout.readline().strip()
215 if location == "??:0":
216 location = None
217 if symbol is None and location is None:
218 break
219 records.append((symbol, location))
220 if first:
221 # Write a blank line as a sentinel so we know when to stop
222 # reading inlines from the output.
223 # The blank line will cause addr2line to emit "??\n??:0\n".
224 child.stdin.write("\n")
225 first = False
226 result[addr] = records
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700227 child.stdin.close()
228 child.stdout.close()
229 return result
230
231
Ben Chengb42dad02013-04-25 15:14:04 -0700232def StripPC(addr):
233 """Strips the Thumb bit a program counter address when appropriate.
234
235 Args:
236 addr: the program counter address
237
238 Returns:
239 The stripped program counter address.
240 """
241 global ARCH
242
243 if ARCH == "arm":
244 return addr & ~1
245 return addr
246
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700247def CallObjdumpForSet(lib, unique_addrs):
248 """Use objdump to find out the names of the containing functions.
249
250 Args:
251 lib: library (or executable) pathname containing symbols
252 unique_addrs: set of string hexidecimal addresses to find the functions for.
253
254 Returns:
255 A dictionary of the form {addr: (string symbol, offset)}.
256 """
257 if not lib:
258 return None
259
260 symbols = SYMBOLS_DIR + lib
261 if not os.path.exists(symbols):
262 return None
263
264 symbols = SYMBOLS_DIR + lib
265 if not os.path.exists(symbols):
266 return None
267
268 addrs = sorted(unique_addrs)
Ben Chengb42dad02013-04-25 15:14:04 -0700269 start_addr_dec = str(StripPC(int(addrs[0], 16)))
270 stop_addr_dec = str(StripPC(int(addrs[-1], 16)) + 8)
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700271 cmd = [ToolPath("objdump"),
272 "--section=.text",
273 "--demangle",
274 "--disassemble",
Ben Chengb42dad02013-04-25 15:14:04 -0700275 "--start-address=" + start_addr_dec,
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700276 "--stop-address=" + stop_addr_dec,
277 symbols]
278
279 # Function lines look like:
280 # 000177b0 <android::IBinder::~IBinder()+0x2c>:
281 # We pull out the address and function first. Then we check for an optional
282 # offset. This is tricky due to functions that look like "operator+(..)+0x2c"
283 func_regexp = re.compile("(^[a-f0-9]*) \<(.*)\>:$")
284 offset_regexp = re.compile("(.*)\+0x([a-f0-9]*)")
285
286 # A disassembly line looks like:
287 # 177b2: b510 push {r4, lr}
288 asm_regexp = re.compile("(^[ a-f0-9]*):[ a-f0-0]*.*$")
289
290 current_symbol = None # The current function symbol in the disassembly.
291 current_symbol_addr = 0 # The address of the current function.
292 addr_index = 0 # The address that we are currently looking for.
293
294 stream = subprocess.Popen(cmd, stdout=subprocess.PIPE).stdout
295 result = {}
296 for line in stream:
297 # Is it a function line like:
298 # 000177b0 <android::IBinder::~IBinder()>:
299 components = func_regexp.match(line)
300 if components:
301 # This is a new function, so record the current function and its address.
302 current_symbol_addr = int(components.group(1), 16)
303 current_symbol = components.group(2)
304
305 # Does it have an optional offset like: "foo(..)+0x2c"?
306 components = offset_regexp.match(current_symbol)
307 if components:
308 current_symbol = components.group(1)
309 offset = components.group(2)
310 if offset:
311 current_symbol_addr -= int(offset, 16)
312
313 # Is it an disassembly line like:
314 # 177b2: b510 push {r4, lr}
315 components = asm_regexp.match(line)
316 if components:
317 addr = components.group(1)
318 target_addr = addrs[addr_index]
319 i_addr = int(addr, 16)
Ben Chengb42dad02013-04-25 15:14:04 -0700320 i_target = StripPC(int(target_addr, 16))
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700321 if i_addr == i_target:
322 result[target_addr] = (current_symbol, i_target - current_symbol_addr)
323 addr_index += 1
324 if addr_index >= len(addrs):
325 break
326 stream.close()
327
328 return result
329
330
331def CallCppFilt(mangled_symbol):
332 cmd = [ToolPath("c++filt")]
333 process = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
334 process.stdin.write(mangled_symbol)
335 process.stdin.write("\n")
336 process.stdin.close()
337 demangled_symbol = process.stdout.readline().strip()
338 process.stdout.close()
339 return demangled_symbol
340
341def FormatSymbolWithOffset(symbol, offset):
342 if offset == 0:
343 return symbol
344 return "%s+%d" % (symbol, offset)