Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 #!/usr/bin/python | 1 #!/usr/bin/python |
| 2 # | 2 # |
| 3 # Copyright (C) 2013 The Android Open Source Project | 3 # Copyright (C) 2013 The Android Open Source Project |
| 4 # | 4 # |
| 5 # Licensed under the Apache License, Version 2.0 (the "License"); | 5 # Licensed under the Apache License, Version 2.0 (the "License"); |
| 6 # you may not use this file except in compliance with 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 | 7 # You may obtain a copy of the License at |
| 8 # | 8 # |
| 9 # http://www.apache.org/licenses/LICENSE-2.0 | 9 # http://www.apache.org/licenses/LICENSE-2.0 |
| 10 # | 10 # |
| 11 # Unless required by applicable law or agreed to in writing, software | 11 # Unless required by applicable law or agreed to in writing, software |
| 12 # distributed under the License is distributed on an "AS IS" BASIS, | 12 # distributed under the License is distributed on an "AS IS" BASIS, |
| 13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 # See the License for the specific language governing permissions and | 14 # See the License for the specific language governing permissions and |
| 15 # limitations under the License. | 15 # limitations under the License. |
| 16 | 16 |
| 17 """Module for looking up symbolic debugging information. | 17 """Module for looking up symbolic debugging information. |
| 18 | 18 |
| 19 The information can include symbol names, offsets, and source locations. | 19 The information can include symbol names, offsets, and source locations. |
| 20 """ | 20 """ |
| 21 | 21 |
| 22 import glob | 22 import glob |
| 23 import itertools | 23 import itertools |
| 24 import logging | |
| 24 import os | 25 import os |
| 25 import re | 26 import re |
| 27 import struct | |
| 26 import subprocess | 28 import subprocess |
| 27 import zipfile | 29 import zipfile |
| 28 | 30 |
| 29 CHROME_SRC = os.path.join(os.path.realpath(os.path.dirname(__file__)), | 31 CHROME_SRC = os.path.join(os.path.realpath(os.path.dirname(__file__)), |
| 30 os.pardir, os.pardir, os.pardir, os.pardir) | 32 os.pardir, os.pardir, os.pardir, os.pardir) |
| 31 ANDROID_BUILD_TOP = CHROME_SRC | 33 ANDROID_BUILD_TOP = CHROME_SRC |
| 32 SYMBOLS_DIR = CHROME_SRC | 34 SYMBOLS_DIR = CHROME_SRC |
| 33 CHROME_SYMBOLS_DIR = CHROME_SRC | 35 CHROME_SYMBOLS_DIR = CHROME_SRC |
| 34 | 36 |
| 35 ARCH = "arm" | 37 ARCH = "arm" |
| 36 | 38 |
| 37 TOOLCHAIN_INFO = None | 39 TOOLCHAIN_INFO = None |
| 38 | 40 |
| 41 # See: | |
| 42 # http://bugs.python.org/issue14315 | |
| 43 # https://hg.python.org/cpython/rev/6dd5e9556a60#l2.8 | |
| 44 def PatchZipFile(): | |
| 45 oldDecodeExtra = zipfile.ZipInfo._decodeExtra | |
| 46 def decodeExtra(self): | |
| 47 try: | |
| 48 oldDecodeExtra(self) | |
| 49 except struct.error: | |
| 50 pass | |
| 51 zipfile.ZipInfo._decodeExtra = decodeExtra | |
| 52 PatchZipFile() | |
| 53 | |
| 39 def Uname(): | 54 def Uname(): |
| 40 """'uname' for constructing prebuilt/<...> and out/host/<...> paths.""" | 55 """'uname' for constructing prebuilt/<...> and out/host/<...> paths.""" |
| 41 uname = os.uname()[0] | 56 uname = os.uname()[0] |
| 42 if uname == "Darwin": | 57 if uname == "Darwin": |
| 43 proc = os.uname()[-1] | 58 proc = os.uname()[-1] |
| 44 if proc == "i386" or proc == "x86_64": | 59 if proc == "i386" or proc == "x86_64": |
| 45 return "darwin-x86" | 60 return "darwin-x86" |
| 46 return "darwin-ppc" | 61 return "darwin-ppc" |
| 47 if uname == "Linux": | 62 if uname == "Linux": |
| 48 return "linux-x86" | 63 return "linux-x86" |
| (...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 114 known_toolchains = [ | 129 known_toolchains = [ |
| 115 ("x86_64-" + gcc_version, "x86_64", "x86_64-linux-android") | 130 ("x86_64-" + gcc_version, "x86_64", "x86_64-linux-android") |
| 116 ] | 131 ] |
| 117 elif ARCH == "mips": | 132 elif ARCH == "mips": |
| 118 known_toolchains = [ | 133 known_toolchains = [ |
| 119 ("mipsel-linux-android-" + gcc_version, "mips", "mipsel-linux-android") | 134 ("mipsel-linux-android-" + gcc_version, "mips", "mipsel-linux-android") |
| 120 ] | 135 ] |
| 121 else: | 136 else: |
| 122 known_toolchains = [] | 137 known_toolchains = [] |
| 123 | 138 |
| 139 logging.debug('FindToolcahin: known_toolchains=%s' % known_toolchains) | |
| 124 # Look for addr2line to check for valid toolchain path. | 140 # Look for addr2line to check for valid toolchain path. |
| 125 for (label, platform, target) in known_toolchains: | 141 for (label, platform, target) in known_toolchains: |
| 126 toolchain_info = (label, platform, target); | 142 toolchain_info = (label, platform, target); |
| 127 if os.path.exists(ToolPath("addr2line", toolchain_info)): | 143 if os.path.exists(ToolPath("addr2line", toolchain_info)): |
| 128 TOOLCHAIN_INFO = toolchain_info | 144 TOOLCHAIN_INFO = toolchain_info |
| 129 print "Using toolchain from :" + ToolPath("", TOOLCHAIN_INFO) | 145 print "Using toolchain from :" + ToolPath("", TOOLCHAIN_INFO) |
| 130 return toolchain_info | 146 return toolchain_info |
| 131 | 147 |
| 132 raise Exception("Could not find tool chain") | 148 raise Exception("Could not find tool chain") |
| 133 | 149 |
| (...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 199 out_dir = os.path.join(CHROME_SYMBOLS_DIR, out_dir) | 215 out_dir = os.path.join(CHROME_SYMBOLS_DIR, out_dir) |
| 200 buildtype = os.environ.get('BUILDTYPE') | 216 buildtype = os.environ.get('BUILDTYPE') |
| 201 if buildtype: | 217 if buildtype: |
| 202 buildtype_list = [ buildtype ] | 218 buildtype_list = [ buildtype ] |
| 203 else: | 219 else: |
| 204 buildtype_list = [ 'Debug', 'Release' ] | 220 buildtype_list = [ 'Debug', 'Release' ] |
| 205 | 221 |
| 206 candidates = PathListJoin([out_dir], buildtype_list) + [CHROME_SYMBOLS_DIR] | 222 candidates = PathListJoin([out_dir], buildtype_list) + [CHROME_SYMBOLS_DIR] |
| 207 candidates = PathListJoin(candidates, dirs) | 223 candidates = PathListJoin(candidates, dirs) |
| 208 candidates = PathListJoin(candidates, [filepart]) | 224 candidates = PathListJoin(candidates, [filepart]) |
| 225 logging.debug('GetCandidates: prefiltered candidates = %s' % candidates) | |
| 209 candidates = list( | 226 candidates = list( |
| 210 itertools.chain.from_iterable(map(candidate_fun, candidates))) | 227 itertools.chain.from_iterable(map(candidate_fun, candidates))) |
| 211 candidates = sorted(candidates, key=os.path.getmtime, reverse=True) | 228 candidates = sorted(candidates, key=os.path.getmtime, reverse=True) |
| 212 return candidates | 229 return candidates |
| 213 | 230 |
| 214 def GetCandidateApks(): | 231 def GetCandidateApks(): |
| 215 """Returns a list of APKs which could contain the library. | 232 """Returns a list of APKs which could contain the library. |
| 216 | 233 |
| 217 Args: | 234 Args: |
| 218 None | 235 None |
| (...skipping 11 matching lines...) Expand all Loading... | |
| 230 | 247 |
| 231 Returns: | 248 Returns: |
| 232 Name of the first library which would be crazy loaded from this APK. | 249 Name of the first library which would be crazy loaded from this APK. |
| 233 """ | 250 """ |
| 234 zip_file = zipfile.ZipFile(apk_filename, 'r') | 251 zip_file = zipfile.ZipFile(apk_filename, 'r') |
| 235 for filename in zip_file.namelist(): | 252 for filename in zip_file.namelist(): |
| 236 match = re.match('lib/[^/]*/crazy.(lib.*[.]so)', filename) | 253 match = re.match('lib/[^/]*/crazy.(lib.*[.]so)', filename) |
| 237 if match: | 254 if match: |
| 238 return match.group(1) | 255 return match.group(1) |
| 239 | 256 |
| 240 def GetMatchingApks(device_apk_name): | 257 def GetApkFromLibrary(device_library_path): |
| 258 match = re.match('.*/([^/]*)-[0-9]+[.]apk$', device_library_path) | |
| 259 if not match: | |
| 260 match = re.match('.*/([^/]*)-[0-9]+\/[^/]*\.apk$', device_library_path) | |
|
rmcilroy
2015/03/12 21:50:14
Could you put some comments with examples for what
| |
| 261 if not match: | |
| 262 return None | |
| 263 return match.group(1) | |
| 264 | |
| 265 def GetMatchingApks(package_name): | |
| 241 """Find any APKs which match the package indicated by the device_apk_name. | 266 """Find any APKs which match the package indicated by the device_apk_name. |
| 242 | 267 |
| 243 Args: | 268 Args: |
| 244 device_apk_name: name of the APK on the device. | 269 device_apk_name: name of the APK on the device. |
| 245 | 270 |
| 246 Returns: | 271 Returns: |
| 247 A list of APK filenames which could contain the desired library. | 272 A list of APK filenames which could contain the desired library. |
| 248 """ | 273 """ |
| 249 match = re.match('(.*)-[0-9]+[.]apk$', device_apk_name) | |
| 250 if not match: | |
| 251 return None | |
| 252 package_name = match.group(1) | |
| 253 return filter( | 274 return filter( |
| 254 lambda candidate_apk: | 275 lambda candidate_apk: |
| 255 ApkMatchPackageName(GetAapt(), candidate_apk, package_name), | 276 ApkMatchPackageName(GetAapt(), candidate_apk, package_name), |
| 256 GetCandidateApks()) | 277 GetCandidateApks()) |
| 257 | 278 |
| 258 def MapDeviceApkToLibrary(device_apk_name): | 279 def MapDeviceApkToLibrary(device_apk_name): |
| 259 """Provide a library name which corresponds with device_apk_name. | 280 """Provide a library name which corresponds with device_apk_name. |
| 260 | 281 |
| 261 Args: | 282 Args: |
| 262 device_apk_name: name of the APK on the device. | 283 device_apk_name: name of the APK on the device. |
| 263 | 284 |
| 264 Returns: | 285 Returns: |
| 265 Name of the library which corresponds to that APK. | 286 Name of the library which corresponds to that APK. |
| 266 """ | 287 """ |
| 267 matching_apks = GetMatchingApks(device_apk_name) | 288 matching_apks = GetMatchingApks(device_apk_name) |
| 289 logging.debug('MapDeviceApkToLibrary: matching_apks=%s' % matching_apks) | |
| 268 for matching_apk in matching_apks: | 290 for matching_apk in matching_apks: |
| 269 crazy_lib = GetCrazyLib(matching_apk) | 291 crazy_lib = GetCrazyLib(matching_apk) |
| 270 if crazy_lib: | 292 if crazy_lib: |
| 271 return crazy_lib | 293 return crazy_lib |
| 272 | 294 |
| 273 def GetCandidateLibraries(library_name): | 295 def GetCandidateLibraries(library_name): |
| 274 """Returns a list of candidate library filenames. | 296 """Returns a list of candidate library filenames. |
| 275 | 297 |
| 276 Args: | 298 Args: |
| 277 library_name: basename of the library to match. | 299 library_name: basename of the library to match. |
| 278 | 300 |
| 279 Returns: | 301 Returns: |
| 280 A list of matching library filenames for library_name. | 302 A list of matching library filenames for library_name. |
| 281 """ | 303 """ |
| 282 return GetCandidates( | 304 return GetCandidates( |
| 283 ['lib', 'lib.target'], library_name, | 305 ['lib', 'lib.target', '.'], library_name, |
| 284 lambda filename: filter(os.path.exists, [filename])) | 306 lambda filename: filter(os.path.exists, [filename])) |
| 285 | 307 |
| 286 def TranslateLibPath(lib): | 308 def TranslateLibPath(lib): |
| 287 # SymbolInformation(lib, addr) receives lib as the path from symbols | |
| 288 # root to the symbols file. This needs to be translated to point to the | |
| 289 # correct .so path. If the user doesn't explicitly specify which directory to | |
| 290 # use, then use the most recently updated one in one of the known directories. | |
| 291 # If the .so is not found somewhere in CHROME_SYMBOLS_DIR, leave it | |
| 292 # untranslated in case it is an Android symbol in SYMBOLS_DIR. | |
| 293 library_name = os.path.basename(lib) | |
| 294 | |
| 295 # The filename in the stack trace maybe an APK name rather than a library | 309 # The filename in the stack trace maybe an APK name rather than a library |
| 296 # name. This happens when the library was loaded directly from inside the | 310 # name. This happens when the library was loaded directly from inside the |
| 297 # APK. If this is the case we try to figure out the library name by looking | 311 # APK. If this is the case we try to figure out the library name by looking |
| 298 # for a matching APK file and finding the name of the library in contains. | 312 # for a matching APK file and finding the name of the library in contains. |
| 299 # The name of the APK file on the device is of the form | 313 # The name of the APK file on the device is of the form |
| 300 # <package_name>-<number>.apk. The APK file on the host may have any name | 314 # <package_name>-<number>.apk. The APK file on the host may have any name |
| 301 # so we look at the APK badging to see if the package name matches. | 315 # so we look at the APK badging to see if the package name matches. |
| 302 if re.search('-[0-9]+[.]apk$', library_name): | 316 apk = GetApkFromLibrary(lib) |
| 303 mapping = MapDeviceApkToLibrary(library_name) | 317 if apk is not None: |
| 318 logging.debug('TranslateLibPath: apk=%s' % apk) | |
| 319 mapping = MapDeviceApkToLibrary(apk) | |
| 304 if mapping: | 320 if mapping: |
| 305 library_name = mapping | 321 lib = mapping |
| 322 | |
| 323 # SymbolInformation(lib, addr) receives lib as the path from symbols | |
| 324 # root to the symbols file. This needs to be translated to point to the | |
| 325 # correct .so path. If the user doesn't explicitly specify which directory to | |
| 326 # use, then use the most recently updated one in one of the known directories. | |
| 327 # If the .so is not found somewhere in CHROME_SYMBOLS_DIR, leave it | |
| 328 # untranslated in case it is an Android symbol in SYMBOLS_DIR. | |
| 329 library_name = os.path.basename(lib) | |
| 330 | |
| 331 logging.debug('TranslateLibPath: lib=%s library_name=%s' % (lib, library_name) ) | |
| 306 | 332 |
| 307 candidate_libraries = GetCandidateLibraries(library_name) | 333 candidate_libraries = GetCandidateLibraries(library_name) |
| 334 logging.debug('TranslateLibPath: candidate_libraries=%s' % candidate_libraries ) | |
| 308 if not candidate_libraries: | 335 if not candidate_libraries: |
| 309 return lib | 336 return lib |
| 310 | 337 |
| 311 library_path = os.path.relpath(candidate_libraries[0], SYMBOLS_DIR) | 338 library_path = os.path.relpath(candidate_libraries[0], SYMBOLS_DIR) |
| 339 logging.debug('TranslateLibPath: library_path=%s' % library_path) | |
| 312 return '/' + library_path | 340 return '/' + library_path |
| 313 | 341 |
| 314 def SymbolInformation(lib, addr, get_detailed_info): | 342 def SymbolInformation(lib, addr, get_detailed_info): |
| 315 """Look up symbol information about an address. | 343 """Look up symbol information about an address. |
| 316 | 344 |
| 317 Args: | 345 Args: |
| 318 lib: library (or executable) pathname containing symbols | 346 lib: library (or executable) pathname containing symbols |
| 319 addr: string hexidecimal address | 347 addr: string hexidecimal address |
| 320 | 348 |
| 321 Returns: | 349 Returns: |
| (...skipping 244 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 566 process.stdin.write("\n") | 594 process.stdin.write("\n") |
| 567 process.stdin.close() | 595 process.stdin.close() |
| 568 demangled_symbol = process.stdout.readline().strip() | 596 demangled_symbol = process.stdout.readline().strip() |
| 569 process.stdout.close() | 597 process.stdout.close() |
| 570 return demangled_symbol | 598 return demangled_symbol |
| 571 | 599 |
| 572 def FormatSymbolWithOffset(symbol, offset): | 600 def FormatSymbolWithOffset(symbol, offset): |
| 573 if offset == 0: | 601 if offset == 0: |
| 574 return symbol | 602 return symbol |
| 575 return "%s+%d" % (symbol, offset) | 603 return "%s+%d" % (symbol, offset) |
| OLD | NEW |