3 # Copyright (C) 2013 The Android Open Source Project
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
9 # http://www.apache.org/licenses/LICENSE-2.0
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.
17 """Module for looking up symbolic debugging information.
19 The information can include symbol names, offsets, and source locations.
26 CHROME_SRC = os.path.join(os.path.realpath(os.path.dirname(__file__)),
27 os.pardir, os.pardir, os.pardir, os.pardir)
28 ANDROID_BUILD_TOP = CHROME_SRC
29 SYMBOLS_DIR = CHROME_SRC
30 CHROME_SYMBOLS_DIR = CHROME_SRC
37 """'uname' for constructing prebuilt/<...> and out/host/<...> paths."""
41 if proc == "i386" or proc == "x86_64":
48 def ToolPath(tool, toolchain_info=None):
49 """Return a full qualified path to the specified tool"""
50 # ToolPath looks for the tools in the completely incorrect directory.
51 # This looks in the checked in android_tools.
53 toolchain_source = "arm-linux-androideabi-4.6"
54 toolchain_prefix = "arm-linux-androideabi"
57 toolchain_source = "aarch64-linux-android-4.9"
58 toolchain_prefix = "aarch64-linux-android"
61 toolchain_source = "x86-4.6"
62 toolchain_prefix = "i686-android-linux"
64 elif ARCH == "x86_64":
65 toolchain_source = "x86_64-4.9"
66 toolchain_prefix = "x86_64-linux-android"
69 toolchain_source = "mipsel-linux-android-4.6"
70 toolchain_prefix = "mipsel-linux-android"
73 raise Exception("Could not find tool chain")
76 "third_party/android_tools/%s/toolchains/%s/prebuilt/linux-x86_64/bin" %
77 (ndk, toolchain_source))
79 return os.path.join(CHROME_SRC,
81 toolchain_prefix + "-" + tool)
84 """Look for the latest available toolchain
90 A pair of strings containing toolchain label and target prefix.
93 if TOOLCHAIN_INFO is not None:
96 ## Known toolchains, newer ones in the front.
100 ("aarch64-linux-android-" + gcc_version, "aarch64", "aarch64-linux-android")
105 ("arm-linux-androideabi-" + gcc_version, "arm", "arm-linux-androideabi"),
109 ("i686-android-linux-4.4.3", "x86", "i686-android-linux")
111 elif ARCH =="x86_64":
113 ("x86_64-linux-android-4.9", "x86_64", "x86_64-linux-android")
118 ("mipsel-linux-android-" + gcc_version, "mips", "mipsel-linux-android")
121 known_toolchains = []
123 # Look for addr2line to check for valid toolchain path.
124 for (label, platform, target) in known_toolchains:
125 toolchain_info = (label, platform, target);
126 if os.path.exists(ToolPath("addr2line", toolchain_info)):
127 TOOLCHAIN_INFO = toolchain_info
128 print "Using toolchain from :" + ToolPath("", TOOLCHAIN_INFO)
129 return toolchain_info
131 raise Exception("Could not find tool chain")
133 def TranslateLibPath(lib):
134 # SymbolInformation(lib, addr) receives lib as the path from symbols
135 # root to the symbols file. This needs to be translated to point to the
136 # correct .so path. If the user doesn't explicitly specify which directory to
137 # use, then use the most recently updated one in one of the known directories.
138 # If the .so is not found somewhere in CHROME_SYMBOLS_DIR, leave it
139 # untranslated in case it is an Android symbol in SYMBOLS_DIR.
140 library_name = os.path.basename(lib)
141 out_dir = os.environ.get('CHROMIUM_OUT_DIR', 'out')
142 candidate_dirs = ['.',
143 os.path.join(out_dir, 'Debug', 'lib'),
144 os.path.join(out_dir, 'Debug', 'lib.target'),
145 os.path.join(out_dir, 'Release', 'lib'),
146 os.path.join(out_dir, 'Release', 'lib.target'),
149 candidate_libraries = map(
150 lambda d: ('%s/%s/%s' % (CHROME_SYMBOLS_DIR, d, library_name)),
152 candidate_libraries = filter(os.path.exists, candidate_libraries)
153 candidate_libraries = sorted(candidate_libraries,
154 key=os.path.getmtime, reverse=True)
156 if not candidate_libraries:
159 library_path = os.path.relpath(candidate_libraries[0], SYMBOLS_DIR)
160 return '/' + library_path
162 def SymbolInformation(lib, addr, get_detailed_info):
163 """Look up symbol information about an address.
166 lib: library (or executable) pathname containing symbols
167 addr: string hexidecimal address
170 A list of the form [(source_symbol, source_location,
171 object_symbol_with_offset)].
173 If the function has been inlined then the list may contain
174 more than one element with the symbols for the most deeply
175 nested inlined location appearing first. The list is
176 always non-empty, even if no information is available.
178 Usually you want to display the source_location and
179 object_symbol_with_offset from the last element in the list.
181 lib = TranslateLibPath(lib)
182 info = SymbolInformationForSet(lib, set([addr]), get_detailed_info)
183 return (info and info.get(addr)) or [(None, None, None)]
186 def SymbolInformationForSet(lib, unique_addrs, get_detailed_info):
187 """Look up symbol information for a set of addresses from the given library.
190 lib: library (or executable) pathname containing symbols
191 unique_addrs: set of hexidecimal addresses
194 A dictionary of the form {addr: [(source_symbol, source_location,
195 object_symbol_with_offset)]} where each address has a list of
196 associated symbols and locations. The list is always non-empty.
198 If the function has been inlined then the list may contain
199 more than one element with the symbols for the most deeply
200 nested inlined location appearing first. The list is
201 always non-empty, even if no information is available.
203 Usually you want to display the source_location and
204 object_symbol_with_offset from the last element in the list.
209 addr_to_line = CallAddr2LineForSet(lib, unique_addrs)
213 if get_detailed_info:
214 addr_to_objdump = CallObjdumpForSet(lib, unique_addrs)
215 if not addr_to_objdump:
218 addr_to_objdump = dict((addr, ("", 0)) for addr in unique_addrs)
221 for addr in unique_addrs:
222 source_info = addr_to_line.get(addr)
224 source_info = [(None, None)]
225 if addr in addr_to_objdump:
226 (object_symbol, object_offset) = addr_to_objdump.get(addr)
227 object_symbol_with_offset = FormatSymbolWithOffset(object_symbol,
230 object_symbol_with_offset = None
231 result[addr] = [(source_symbol, source_location, object_symbol_with_offset)
232 for (source_symbol, source_location) in source_info]
237 class MemoizedForSet(object):
238 def __init__(self, fn):
242 def __call__(self, lib, unique_addrs):
243 lib_cache = self.cache.setdefault(lib, {})
245 no_cache = filter(lambda x: x not in lib_cache, unique_addrs)
247 lib_cache.update((k, None) for k in no_cache)
248 result = self.fn(lib, no_cache)
250 lib_cache.update(result)
252 return dict((k, lib_cache[k]) for k in unique_addrs if lib_cache[k])
256 def CallAddr2LineForSet(lib, unique_addrs):
257 """Look up line and symbol information for a set of addresses.
260 lib: library (or executable) pathname containing symbols
261 unique_addrs: set of string hexidecimal addresses look up.
264 A dictionary of the form {addr: [(symbol, file:line)]} where
265 each address has a list of associated symbols and locations
266 or an empty list if no symbol information was found.
268 If the function has been inlined then the list may contain
269 more than one element with the symbols for the most deeply
270 nested inlined location appearing first.
276 symbols = SYMBOLS_DIR + lib
277 if not os.path.isfile(symbols):
280 (label, platform, target) = FindToolchain()
281 cmd = [ToolPath("addr2line"), "--functions", "--inlines",
282 "--demangle", "--exe=" + symbols]
283 child = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
286 addrs = sorted(unique_addrs)
288 child.stdin.write("0x%s\n" % addr)
293 symbol = child.stdout.readline().strip()
296 location = child.stdout.readline().strip()
297 if location == "??:0":
299 if symbol is None and location is None:
301 records.append((symbol, location))
303 # Write a blank line as a sentinel so we know when to stop
304 # reading inlines from the output.
305 # The blank line will cause addr2line to emit "??\n??:0\n".
306 child.stdin.write("\n")
308 result[addr] = records
315 """Strips the Thumb bit a program counter address when appropriate.
318 addr: the program counter address
321 The stripped program counter address.
330 def CallObjdumpForSet(lib, unique_addrs):
331 """Use objdump to find out the names of the containing functions.
334 lib: library (or executable) pathname containing symbols
335 unique_addrs: set of string hexidecimal addresses to find the functions for.
338 A dictionary of the form {addr: (string symbol, offset)}.
343 symbols = SYMBOLS_DIR + lib
344 if not os.path.exists(symbols):
347 symbols = SYMBOLS_DIR + lib
348 if not os.path.exists(symbols):
353 # Function lines look like:
354 # 000177b0 <android::IBinder::~IBinder()+0x2c>:
355 # We pull out the address and function first. Then we check for an optional
356 # offset. This is tricky due to functions that look like "operator+(..)+0x2c"
357 func_regexp = re.compile("(^[a-f0-9]*) \<(.*)\>:$")
358 offset_regexp = re.compile("(.*)\+0x([a-f0-9]*)")
360 # A disassembly line looks like:
361 # 177b2: b510 push {r4, lr}
362 asm_regexp = re.compile("(^[ a-f0-9]*):[ a-f0-0]*.*$")
364 for target_addr in unique_addrs:
365 start_addr_dec = str(StripPC(int(target_addr, 16)))
366 stop_addr_dec = str(StripPC(int(target_addr, 16)) + 8)
367 cmd = [ToolPath("objdump"),
371 "--start-address=" + start_addr_dec,
372 "--stop-address=" + stop_addr_dec,
375 current_symbol = None # The current function symbol in the disassembly.
376 current_symbol_addr = 0 # The address of the current function.
378 stream = subprocess.Popen(cmd, stdout=subprocess.PIPE).stdout
380 # Is it a function line like:
381 # 000177b0 <android::IBinder::~IBinder()>:
382 components = func_regexp.match(line)
384 # This is a new function, so record the current function and its address.
385 current_symbol_addr = int(components.group(1), 16)
386 current_symbol = components.group(2)
388 # Does it have an optional offset like: "foo(..)+0x2c"?
389 components = offset_regexp.match(current_symbol)
391 current_symbol = components.group(1)
392 offset = components.group(2)
394 current_symbol_addr -= int(offset, 16)
396 # Is it an disassembly line like:
397 # 177b2: b510 push {r4, lr}
398 components = asm_regexp.match(line)
400 addr = components.group(1)
401 i_addr = int(addr, 16)
402 i_target = StripPC(int(target_addr, 16))
403 if i_addr == i_target:
404 result[target_addr] = (current_symbol, i_target - current_symbol_addr)
410 def CallCppFilt(mangled_symbol):
411 cmd = [ToolPath("c++filt")]
412 process = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
413 process.stdin.write(mangled_symbol)
414 process.stdin.write("\n")
415 process.stdin.close()
416 demangled_symbol = process.stdout.readline().strip()
417 process.stdout.close()
418 return demangled_symbol
420 def FormatSymbolWithOffset(symbol, offset):
423 return "%s+%d" % (symbol, offset)