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"
56 toolchain_source = "x86-4.6"
57 toolchain_prefix = "i686-android-linux"
60 "third_party/android_tools/ndk/toolchains/%s/prebuilt/linux-x86_64/bin" %
63 return os.path.join(CHROME_SRC,
65 toolchain_prefix + "-" + tool)
68 """Look for the latest available toolchain
74 A pair of strings containing toolchain label and target prefix.
77 if TOOLCHAIN_INFO is not None:
80 ## Known toolchains, newer ones in the front.
83 ("arm-linux-androideabi-4.6", "arm", "arm-linux-androideabi"),
87 ("i686-android-linux-4.4.3", "x86", "i686-android-linux")
92 # Look for addr2line to check for valid toolchain path.
93 for (label, platform, target) in known_toolchains:
94 toolchain_info = (label, platform, target);
95 if os.path.exists(ToolPath("addr2line", toolchain_info)):
96 TOOLCHAIN_INFO = toolchain_info
99 raise Exception("Could not find tool chain")
101 def TranslateLibPath(lib):
102 # SymbolInformation(lib, addr) receives lib as the path from symbols
103 # root to the symbols file. This needs to be translated to point to the
104 # correct .so path. If the user doesn't explicitly specify which directory to
105 # use, then use the most recently updated one in one of the known directories.
106 # If the .so is not found somewhere in CHROME_SYMBOLS_DIR, leave it
107 # untranslated in case it is an Android symbol in SYMBOLS_DIR.
108 library_name = os.path.basename(lib)
109 out_dir = os.environ.get('CHROMIUM_OUT_DIR', 'out')
110 candidate_dirs = ['.',
111 os.path.join(out_dir, 'Debug', 'lib'),
112 os.path.join(out_dir, 'Debug', 'lib.target'),
113 os.path.join(out_dir, 'Release', 'lib'),
114 os.path.join(out_dir, 'Release', 'lib.target'),
117 candidate_libraries = map(
118 lambda d: ('%s/%s/%s' % (CHROME_SYMBOLS_DIR, d, library_name)),
120 candidate_libraries = filter(os.path.exists, candidate_libraries)
121 candidate_libraries = sorted(candidate_libraries,
122 key=os.path.getmtime, reverse=True)
124 if not candidate_libraries:
127 library_path = os.path.relpath(candidate_libraries[0], SYMBOLS_DIR)
128 return '/' + library_path
130 def SymbolInformation(lib, addr, get_detailed_info):
131 """Look up symbol information about an address.
134 lib: library (or executable) pathname containing symbols
135 addr: string hexidecimal address
138 A list of the form [(source_symbol, source_location,
139 object_symbol_with_offset)].
141 If the function has been inlined then the list may contain
142 more than one element with the symbols for the most deeply
143 nested inlined location appearing first. The list is
144 always non-empty, even if no information is available.
146 Usually you want to display the source_location and
147 object_symbol_with_offset from the last element in the list.
149 lib = TranslateLibPath(lib)
150 info = SymbolInformationForSet(lib, set([addr]), get_detailed_info)
151 return (info and info.get(addr)) or [(None, None, None)]
154 def SymbolInformationForSet(lib, unique_addrs, get_detailed_info):
155 """Look up symbol information for a set of addresses from the given library.
158 lib: library (or executable) pathname containing symbols
159 unique_addrs: set of hexidecimal addresses
162 A dictionary of the form {addr: [(source_symbol, source_location,
163 object_symbol_with_offset)]} where each address has a list of
164 associated symbols and locations. The list is always non-empty.
166 If the function has been inlined then the list may contain
167 more than one element with the symbols for the most deeply
168 nested inlined location appearing first. The list is
169 always non-empty, even if no information is available.
171 Usually you want to display the source_location and
172 object_symbol_with_offset from the last element in the list.
177 addr_to_line = CallAddr2LineForSet(lib, unique_addrs)
181 if get_detailed_info:
182 addr_to_objdump = CallObjdumpForSet(lib, unique_addrs)
183 if not addr_to_objdump:
186 addr_to_objdump = dict((addr, ("", 0)) for addr in unique_addrs)
189 for addr in unique_addrs:
190 source_info = addr_to_line.get(addr)
192 source_info = [(None, None)]
193 if addr in addr_to_objdump:
194 (object_symbol, object_offset) = addr_to_objdump.get(addr)
195 object_symbol_with_offset = FormatSymbolWithOffset(object_symbol,
198 object_symbol_with_offset = None
199 result[addr] = [(source_symbol, source_location, object_symbol_with_offset)
200 for (source_symbol, source_location) in source_info]
205 class MemoizedForSet(object):
206 def __init__(self, fn):
210 def __call__(self, lib, unique_addrs):
211 lib_cache = self.cache.setdefault(lib, {})
213 no_cache = filter(lambda x: x not in lib_cache, unique_addrs)
215 lib_cache.update((k, None) for k in no_cache)
216 result = self.fn(lib, no_cache)
218 lib_cache.update(result)
220 return dict((k, lib_cache[k]) for k in unique_addrs if lib_cache[k])
224 def CallAddr2LineForSet(lib, unique_addrs):
225 """Look up line and symbol information for a set of addresses.
228 lib: library (or executable) pathname containing symbols
229 unique_addrs: set of string hexidecimal addresses look up.
232 A dictionary of the form {addr: [(symbol, file:line)]} where
233 each address has a list of associated symbols and locations
234 or an empty list if no symbol information was found.
236 If the function has been inlined then the list may contain
237 more than one element with the symbols for the most deeply
238 nested inlined location appearing first.
244 symbols = SYMBOLS_DIR + lib
245 if not os.path.isfile(symbols):
248 (label, platform, target) = FindToolchain()
249 cmd = [ToolPath("addr2line"), "--functions", "--inlines",
250 "--demangle", "--exe=" + symbols]
251 child = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
254 addrs = sorted(unique_addrs)
256 child.stdin.write("0x%s\n" % addr)
261 symbol = child.stdout.readline().strip()
264 location = child.stdout.readline().strip()
265 if location == "??:0":
267 if symbol is None and location is None:
269 records.append((symbol, location))
271 # Write a blank line as a sentinel so we know when to stop
272 # reading inlines from the output.
273 # The blank line will cause addr2line to emit "??\n??:0\n".
274 child.stdin.write("\n")
276 result[addr] = records
283 """Strips the Thumb bit a program counter address when appropriate.
286 addr: the program counter address
289 The stripped program counter address.
298 def CallObjdumpForSet(lib, unique_addrs):
299 """Use objdump to find out the names of the containing functions.
302 lib: library (or executable) pathname containing symbols
303 unique_addrs: set of string hexidecimal addresses to find the functions for.
306 A dictionary of the form {addr: (string symbol, offset)}.
311 symbols = SYMBOLS_DIR + lib
312 if not os.path.exists(symbols):
315 symbols = SYMBOLS_DIR + lib
316 if not os.path.exists(symbols):
321 # Function lines look like:
322 # 000177b0 <android::IBinder::~IBinder()+0x2c>:
323 # We pull out the address and function first. Then we check for an optional
324 # offset. This is tricky due to functions that look like "operator+(..)+0x2c"
325 func_regexp = re.compile("(^[a-f0-9]*) \<(.*)\>:$")
326 offset_regexp = re.compile("(.*)\+0x([a-f0-9]*)")
328 # A disassembly line looks like:
329 # 177b2: b510 push {r4, lr}
330 asm_regexp = re.compile("(^[ a-f0-9]*):[ a-f0-0]*.*$")
332 for target_addr in unique_addrs:
333 start_addr_dec = str(StripPC(int(target_addr, 16)))
334 stop_addr_dec = str(StripPC(int(target_addr, 16)) + 8)
335 cmd = [ToolPath("objdump"),
339 "--start-address=" + start_addr_dec,
340 "--stop-address=" + stop_addr_dec,
343 current_symbol = None # The current function symbol in the disassembly.
344 current_symbol_addr = 0 # The address of the current function.
346 stream = subprocess.Popen(cmd, stdout=subprocess.PIPE).stdout
348 # Is it a function line like:
349 # 000177b0 <android::IBinder::~IBinder()>:
350 components = func_regexp.match(line)
352 # This is a new function, so record the current function and its address.
353 current_symbol_addr = int(components.group(1), 16)
354 current_symbol = components.group(2)
356 # Does it have an optional offset like: "foo(..)+0x2c"?
357 components = offset_regexp.match(current_symbol)
359 current_symbol = components.group(1)
360 offset = components.group(2)
362 current_symbol_addr -= int(offset, 16)
364 # Is it an disassembly line like:
365 # 177b2: b510 push {r4, lr}
366 components = asm_regexp.match(line)
368 addr = components.group(1)
369 i_addr = int(addr, 16)
370 i_target = StripPC(int(target_addr, 16))
371 if i_addr == i_target:
372 result[target_addr] = (current_symbol, i_target - current_symbol_addr)
378 def CallCppFilt(mangled_symbol):
379 cmd = [ToolPath("c++filt")]
380 process = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
381 process.stdin.write(mangled_symbol)
382 process.stdin.write("\n")
383 process.stdin.close()
384 demangled_symbol = process.stdout.readline().strip()
385 process.stdout.close()
386 return demangled_symbol
388 def FormatSymbolWithOffset(symbol, offset):
391 return "%s+%d" % (symbol, offset)