Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(219)

Side by Side Diff: third_party/android_platform/development/scripts/symbol.py

Issue 1399343002: Roll Mojo to b88737ed62969ce3203085748f0d53ff4f09ba5b. (Closed) Base URL: https://github.com/domokit/monet.git@master
Patch Set: Created 5 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 #!/usr/bin/python
2 #
3 # 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.
16
17 """Module for looking up symbolic debugging information.
18
19 The information can include symbol names, offsets, and source locations.
20 """
21
22 import glob
23 import itertools
24 import os
25 import re
26 import subprocess
27 import zipfile
28
29 CHROME_SRC = os.path.join(os.path.realpath(os.path.dirname(__file__)),
30 os.pardir, os.pardir, os.pardir, os.pardir)
31 ANDROID_BUILD_TOP = CHROME_SRC
32 SYMBOLS_DIR = CHROME_SRC
33 CHROME_SYMBOLS_DIR = CHROME_SRC
34
35 ARCH = "arm"
36
37 TOOLCHAIN_INFO = None
38
39 def Uname():
40 """'uname' for constructing prebuilt/<...> and out/host/<...> paths."""
41 uname = os.uname()[0]
42 if uname == "Darwin":
43 proc = os.uname()[-1]
44 if proc == "i386" or proc == "x86_64":
45 return "darwin-x86"
46 return "darwin-ppc"
47 if uname == "Linux":
48 return "linux-x86"
49 return uname
50
51 def ToolPath(tool, toolchain_info=None):
52 """Return a full qualified path to the specified tool"""
53 # ToolPath looks for the tools in the completely incorrect directory.
54 # This looks in the checked in android_tools.
55 if ARCH == "arm":
56 toolchain_source = "arm-linux-androideabi-4.9"
57 toolchain_prefix = "arm-linux-androideabi"
58 ndk = "ndk"
59 elif ARCH == "arm64":
60 toolchain_source = "aarch64-linux-android-4.9"
61 toolchain_prefix = "aarch64-linux-android"
62 ndk = "ndk"
63 elif ARCH == "x86":
64 toolchain_source = "x86-4.9"
65 toolchain_prefix = "i686-linux-android"
66 ndk = "ndk"
67 elif ARCH == "x86_64":
68 toolchain_source = "x86_64-4.9"
69 toolchain_prefix = "x86_64-linux-android"
70 ndk = "ndk"
71 elif ARCH == "mips":
72 toolchain_source = "mipsel-linux-android-4.9"
73 toolchain_prefix = "mipsel-linux-android"
74 ndk = "ndk"
75 else:
76 raise Exception("Could not find tool chain")
77
78 toolchain_subdir = (
79 "third_party/android_tools/%s/toolchains/%s/prebuilt/linux-x86_64/bin" %
80 (ndk, toolchain_source))
81
82 return os.path.join(CHROME_SRC,
83 toolchain_subdir,
84 toolchain_prefix + "-" + tool)
85
86 def FindToolchain():
87 """Look for the latest available toolchain
88
89 Args:
90 None
91
92 Returns:
93 A pair of strings containing toolchain label and target prefix.
94 """
95 global TOOLCHAIN_INFO
96 if TOOLCHAIN_INFO is not None:
97 return TOOLCHAIN_INFO
98
99 ## Known toolchains, newer ones in the front.
100 gcc_version = "4.9"
101 if ARCH == "arm64":
102 known_toolchains = [
103 ("aarch64-linux-android-" + gcc_version, "aarch64", "aarch64-linux-android ")
104 ]
105 elif ARCH == "arm":
106 known_toolchains = [
107 ("arm-linux-androideabi-" + gcc_version, "arm", "arm-linux-androideabi")
108 ]
109 elif ARCH =="x86":
110 known_toolchains = [
111 ("x86-" + gcc_version, "x86", "i686-linux-android")
112 ]
113 elif ARCH =="x86_64":
114 known_toolchains = [
115 ("x86_64-" + gcc_version, "x86_64", "x86_64-linux-android")
116 ]
117 elif ARCH == "mips":
118 known_toolchains = [
119 ("mipsel-linux-android-" + gcc_version, "mips", "mipsel-linux-android")
120 ]
121 else:
122 known_toolchains = []
123
124 # Look for addr2line to check for valid toolchain path.
125 for (label, platform, target) in known_toolchains:
126 toolchain_info = (label, platform, target);
127 if os.path.exists(ToolPath("addr2line", toolchain_info)):
128 TOOLCHAIN_INFO = toolchain_info
129 print "Using toolchain from :" + ToolPath("", TOOLCHAIN_INFO)
130 return toolchain_info
131
132 raise Exception("Could not find tool chain")
133
134 def GetAapt():
135 """Returns the path to aapt.
136
137 Args:
138 None
139
140 Returns:
141 the pathname of the 'aapt' executable.
142 """
143 sdk_home = os.path.join('third_party', 'android_tools', 'sdk')
144 sdk_home = os.environ.get('SDK_HOME', sdk_home)
145 aapt_exe = glob.glob(os.path.join(sdk_home, 'build-tools', '*', 'aapt'))
146 if not aapt_exe:
147 return None
148 return sorted(aapt_exe, key=os.path.getmtime, reverse=True)[0]
149
150 def ApkMatchPackageName(aapt, apk_path, package_name):
151 """Returns true the APK's package name matches package_name.
152
153 Args:
154 aapt: pathname for the 'aapt' executable.
155 apk_path: pathname of the APK file.
156 package_name: package name to match.
157
158 Returns:
159 True if the package name matches or aapt is None, False otherwise.
160 """
161 if not aapt:
162 # Allow false positives
163 return True
164 aapt_output = subprocess.check_output(
165 [aapt, 'dump', 'badging', apk_path]).split('\n')
166 package_name_re = re.compile(r'package: .*name=\'(\S*)\'')
167 for line in aapt_output:
168 match = package_name_re.match(line)
169 if match:
170 return package_name == match.group(1)
171 return False
172
173 def PathListJoin(prefix_list, suffix_list):
174 """Returns each prefix in prefix_list joined with each suffix in suffix list.
175
176 Args:
177 prefix_list: list of path prefixes.
178 suffix_list: list of path suffixes.
179
180 Returns:
181 List of paths each of which joins a prefix with a suffix.
182 """
183 return [
184 os.path.join(prefix, suffix)
185 for prefix in prefix_list for suffix in suffix_list ]
186
187 def GetCandidates(dirs, filepart, candidate_fun):
188 """Returns a list of candidate filenames.
189
190 Args:
191 dirs: a list of the directory part of the pathname.
192 filepart: the file part of the pathname.
193 candidate_fun: a function to apply to each candidate, returns a list.
194
195 Returns:
196 A list of candidate files ordered by modification time, newest first.
197 """
198 out_dir = os.environ.get('CHROMIUM_OUT_DIR', 'out')
199 out_dir = os.path.join(CHROME_SYMBOLS_DIR, out_dir)
200 buildtype = os.environ.get('BUILDTYPE')
201 if buildtype:
202 buildtype_list = [ buildtype ]
203 else:
204 buildtype_list = [ 'Debug', 'Release' ]
205
206 candidates = PathListJoin([out_dir], buildtype_list) + [CHROME_SYMBOLS_DIR]
207 candidates = PathListJoin(candidates, dirs)
208 candidates = PathListJoin(candidates, [filepart])
209 candidates = list(
210 itertools.chain.from_iterable(map(candidate_fun, candidates)))
211 candidates = sorted(candidates, key=os.path.getmtime, reverse=True)
212 # candidates = ['/usr/local/google/home/qsr/programmes/mojo/src/out/android_De bug/libmojo_shell.so']
213 return candidates
214
215 def GetCandidateApks():
216 """Returns a list of APKs which could contain the library.
217
218 Args:
219 None
220
221 Returns:
222 list of APK filename which could contain the library.
223 """
224 return GetCandidates(['apks'], '*.apk', glob.glob)
225
226 def GetCrazyLib(apk_filename):
227 """Returns the name of the first crazy library from this APK.
228
229 Args:
230 apk_filename: name of an APK file.
231
232 Returns:
233 Name of the first library which would be crazy loaded from this APK.
234 """
235 zip_file = zipfile.ZipFile(apk_filename, 'r')
236 for filename in zip_file.namelist():
237 match = re.match('lib/[^/]*/crazy.(lib.*[.]so)', filename)
238 if match:
239 return match.group(1)
240
241 def GetMatchingApks(device_apk_name):
242 """Find any APKs which match the package indicated by the device_apk_name.
243
244 Args:
245 device_apk_name: name of the APK on the device.
246
247 Returns:
248 A list of APK filenames which could contain the desired library.
249 """
250 match = re.match('(.*)-[0-9]+[.]apk$', device_apk_name)
251 if not match:
252 return None
253 package_name = match.group(1)
254 return filter(
255 lambda candidate_apk:
256 ApkMatchPackageName(GetAapt(), candidate_apk, package_name),
257 GetCandidateApks())
258
259 def MapDeviceApkToLibrary(device_apk_name):
260 """Provide a library name which corresponds with device_apk_name.
261
262 Args:
263 device_apk_name: name of the APK on the device.
264
265 Returns:
266 Name of the library which corresponds to that APK.
267 """
268 matching_apks = GetMatchingApks(device_apk_name)
269 for matching_apk in matching_apks:
270 crazy_lib = GetCrazyLib(matching_apk)
271 if crazy_lib:
272 return crazy_lib
273
274 def GetCandidateLibraries(library_name):
275 """Returns a list of candidate library filenames.
276
277 Args:
278 library_name: basename of the library to match.
279
280 Returns:
281 A list of matching library filenames for library_name.
282 """
283 return GetCandidates(
284 ['', 'lib', 'lib.target'], library_name,
285 lambda filename: filter(os.path.exists, [filename]))
286
287 def TranslateLibPath(lib):
288 # SymbolInformation(lib, addr) receives lib as the path from symbols
289 # root to the symbols file. This needs to be translated to point to the
290 # correct .so path. If the user doesn't explicitly specify which directory to
291 # use, then use the most recently updated one in one of the known directories.
292 # If the .so is not found somewhere in CHROME_SYMBOLS_DIR, leave it
293 # untranslated in case it is an Android symbol in SYMBOLS_DIR.
294 library_name = os.path.basename(lib)
295
296 # The filename in the stack trace maybe an APK name rather than a library
297 # name. This happens when the library was loaded directly from inside the
298 # APK. If this is the case we try to figure out the library name by looking
299 # for a matching APK file and finding the name of the library in contains.
300 # The name of the APK file on the device is of the form
301 # <package_name>-<number>.apk. The APK file on the host may have any name
302 # so we look at the APK badging to see if the package name matches.
303 if re.search('-[0-9]+[.]apk$', library_name):
304 mapping = MapDeviceApkToLibrary(library_name)
305 if mapping:
306 library_name = mapping
307
308 candidate_libraries = GetCandidateLibraries(library_name)
309 if not candidate_libraries:
310 return lib
311
312 library_path = os.path.relpath(candidate_libraries[0], SYMBOLS_DIR)
313 return '/' + library_path
314
315 def SymbolInformation(lib, addr, get_detailed_info):
316 """Look up symbol information about an address.
317
318 Args:
319 lib: library (or executable) pathname containing symbols
320 addr: string hexidecimal address
321
322 Returns:
323 A list of the form [(source_symbol, source_location,
324 object_symbol_with_offset)].
325
326 If the function has been inlined then the list may contain
327 more than one element with the symbols for the most deeply
328 nested inlined location appearing first. The list is
329 always non-empty, even if no information is available.
330
331 Usually you want to display the source_location and
332 object_symbol_with_offset from the last element in the list.
333 """
334 lib = TranslateLibPath(lib)
335 info = SymbolInformationForSet(lib, set([addr]), get_detailed_info)
336 return (info and info.get(addr)) or [(None, None, None)]
337
338
339 def SymbolInformationForSet(lib, unique_addrs, get_detailed_info):
340 """Look up symbol information for a set of addresses from the given library.
341
342 Args:
343 lib: library (or executable) pathname containing symbols
344 unique_addrs: set of hexidecimal addresses
345
346 Returns:
347 A dictionary of the form {addr: [(source_symbol, source_location,
348 object_symbol_with_offset)]} where each address has a list of
349 associated symbols and locations. The list is always non-empty.
350
351 If the function has been inlined then the list may contain
352 more than one element with the symbols for the most deeply
353 nested inlined location appearing first. The list is
354 always non-empty, even if no information is available.
355
356 Usually you want to display the source_location and
357 object_symbol_with_offset from the last element in the list.
358 """
359 if not lib:
360 return None
361
362 addr_to_line = CallAddr2LineForSet(lib, unique_addrs)
363 if not addr_to_line:
364 return None
365
366 if get_detailed_info:
367 addr_to_objdump = CallObjdumpForSet(lib, unique_addrs)
368 if not addr_to_objdump:
369 return None
370 else:
371 addr_to_objdump = dict((addr, ("", 0)) for addr in unique_addrs)
372
373 result = {}
374 for addr in unique_addrs:
375 source_info = addr_to_line.get(addr)
376 if not source_info:
377 source_info = [(None, None)]
378 if addr in addr_to_objdump:
379 (object_symbol, object_offset) = addr_to_objdump.get(addr)
380 object_symbol_with_offset = FormatSymbolWithOffset(object_symbol,
381 object_offset)
382 else:
383 object_symbol_with_offset = None
384 result[addr] = [(source_symbol, source_location, object_symbol_with_offset)
385 for (source_symbol, source_location) in source_info]
386
387 return result
388
389
390 class MemoizedForSet(object):
391 def __init__(self, fn):
392 self.fn = fn
393 self.cache = {}
394
395 def __call__(self, lib, unique_addrs):
396 lib_cache = self.cache.setdefault(lib, {})
397
398 no_cache = filter(lambda x: x not in lib_cache, unique_addrs)
399 if no_cache:
400 lib_cache.update((k, None) for k in no_cache)
401 result = self.fn(lib, no_cache)
402 if result:
403 lib_cache.update(result)
404
405 return dict((k, lib_cache[k]) for k in unique_addrs if lib_cache[k])
406
407
408 @MemoizedForSet
409 def CallAddr2LineForSet(lib, unique_addrs):
410 """Look up line and symbol information for a set of addresses.
411
412 Args:
413 lib: library (or executable) pathname containing symbols
414 unique_addrs: set of string hexidecimal addresses look up.
415
416 Returns:
417 A dictionary of the form {addr: [(symbol, file:line)]} where
418 each address has a list of associated symbols and locations
419 or an empty list if no symbol information was found.
420
421 If the function has been inlined then the list may contain
422 more than one element with the symbols for the most deeply
423 nested inlined location appearing first.
424 """
425 if not lib:
426 return None
427
428
429 symbols = SYMBOLS_DIR + lib
430 if not os.path.isfile(symbols):
431 return None
432
433 (label, platform, target) = FindToolchain()
434 cmd = [ToolPath("addr2line"), "--functions", "--inlines",
435 "--demangle", "--exe=" + symbols]
436 child = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
437
438 result = {}
439 addrs = sorted(unique_addrs)
440 for addr in addrs:
441 child.stdin.write("0x%s\n" % addr)
442 child.stdin.flush()
443 records = []
444 first = True
445 while True:
446 symbol = child.stdout.readline().strip()
447 if symbol == "??":
448 symbol = None
449 location = child.stdout.readline().strip()
450 if location == "??:0":
451 location = None
452 if symbol is None and location is None:
453 break
454 records.append((symbol, location))
455 if first:
456 # Write a blank line as a sentinel so we know when to stop
457 # reading inlines from the output.
458 # The blank line will cause addr2line to emit "??\n??:0\n".
459 child.stdin.write("\n")
460 first = False
461 result[addr] = records
462 child.stdin.close()
463 child.stdout.close()
464 return result
465
466
467 def StripPC(addr):
468 """Strips the Thumb bit a program counter address when appropriate.
469
470 Args:
471 addr: the program counter address
472
473 Returns:
474 The stripped program counter address.
475 """
476 global ARCH
477
478 if ARCH == "arm":
479 return addr & ~1
480 return addr
481
482 @MemoizedForSet
483 def CallObjdumpForSet(lib, unique_addrs):
484 """Use objdump to find out the names of the containing functions.
485
486 Args:
487 lib: library (or executable) pathname containing symbols
488 unique_addrs: set of string hexidecimal addresses to find the functions for.
489
490 Returns:
491 A dictionary of the form {addr: (string symbol, offset)}.
492 """
493 if not lib:
494 return None
495
496 symbols = SYMBOLS_DIR + lib
497 if not os.path.exists(symbols):
498 return None
499
500 symbols = SYMBOLS_DIR + lib
501 if not os.path.exists(symbols):
502 return None
503
504 result = {}
505
506 # Function lines look like:
507 # 000177b0 <android::IBinder::~IBinder()+0x2c>:
508 # We pull out the address and function first. Then we check for an optional
509 # offset. This is tricky due to functions that look like "operator+(..)+0x2c"
510 func_regexp = re.compile("(^[a-f0-9]*) \<(.*)\>:$")
511 offset_regexp = re.compile("(.*)\+0x([a-f0-9]*)")
512
513 # A disassembly line looks like:
514 # 177b2: b510 push {r4, lr}
515 asm_regexp = re.compile("(^[ a-f0-9]*):[ a-f0-0]*.*$")
516
517 for target_addr in unique_addrs:
518 start_addr_dec = str(StripPC(int(target_addr, 16)))
519 stop_addr_dec = str(StripPC(int(target_addr, 16)) + 8)
520 cmd = [ToolPath("objdump"),
521 "--section=.text",
522 "--demangle",
523 "--disassemble",
524 "--start-address=" + start_addr_dec,
525 "--stop-address=" + stop_addr_dec,
526 symbols]
527
528 current_symbol = None # The current function symbol in the disassembly.
529 current_symbol_addr = 0 # The address of the current function.
530
531 stream = subprocess.Popen(cmd, stdout=subprocess.PIPE).stdout
532 for line in stream:
533 # Is it a function line like:
534 # 000177b0 <android::IBinder::~IBinder()>:
535 components = func_regexp.match(line)
536 if components:
537 # This is a new function, so record the current function and its address .
538 current_symbol_addr = int(components.group(1), 16)
539 current_symbol = components.group(2)
540
541 # Does it have an optional offset like: "foo(..)+0x2c"?
542 components = offset_regexp.match(current_symbol)
543 if components:
544 current_symbol = components.group(1)
545 offset = components.group(2)
546 if offset:
547 current_symbol_addr -= int(offset, 16)
548
549 # Is it an disassembly line like:
550 # 177b2: b510 push {r4, lr}
551 components = asm_regexp.match(line)
552 if components:
553 addr = components.group(1)
554 i_addr = int(addr, 16)
555 i_target = StripPC(int(target_addr, 16))
556 if i_addr == i_target:
557 result[target_addr] = (current_symbol, i_target - current_symbol_addr)
558 stream.close()
559
560 return result
561
562
563 def CallCppFilt(mangled_symbol):
564 cmd = [ToolPath("c++filt")]
565 process = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
566 process.stdin.write(mangled_symbol)
567 process.stdin.write("\n")
568 process.stdin.close()
569 demangled_symbol = process.stdout.readline().strip()
570 process.stdout.close()
571 return demangled_symbol
572
573 def FormatSymbolWithOffset(symbol, offset):
574 if offset == 0:
575 return symbol
576 return "%s+%d" % (symbol, offset)
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698