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 |
| 23 import itertools |
22 import os | 24 import os |
23 import re | 25 import re |
24 import subprocess | 26 import subprocess |
| 27 import zipfile |
25 | 28 |
26 CHROME_SRC = os.path.join(os.path.realpath(os.path.dirname(__file__)), | 29 CHROME_SRC = os.path.join(os.path.realpath(os.path.dirname(__file__)), |
27 os.pardir, os.pardir, os.pardir, os.pardir) | 30 os.pardir, os.pardir, os.pardir, os.pardir) |
28 ANDROID_BUILD_TOP = CHROME_SRC | 31 ANDROID_BUILD_TOP = CHROME_SRC |
29 SYMBOLS_DIR = CHROME_SRC | 32 SYMBOLS_DIR = CHROME_SRC |
30 CHROME_SYMBOLS_DIR = CHROME_SRC | 33 CHROME_SYMBOLS_DIR = CHROME_SRC |
31 | 34 |
32 ARCH = "arm" | 35 ARCH = "arm" |
33 | 36 |
34 TOOLCHAIN_INFO = None | 37 TOOLCHAIN_INFO = None |
(...skipping 88 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
123 # Look for addr2line to check for valid toolchain path. | 126 # Look for addr2line to check for valid toolchain path. |
124 for (label, platform, target) in known_toolchains: | 127 for (label, platform, target) in known_toolchains: |
125 toolchain_info = (label, platform, target); | 128 toolchain_info = (label, platform, target); |
126 if os.path.exists(ToolPath("addr2line", toolchain_info)): | 129 if os.path.exists(ToolPath("addr2line", toolchain_info)): |
127 TOOLCHAIN_INFO = toolchain_info | 130 TOOLCHAIN_INFO = toolchain_info |
128 print "Using toolchain from :" + ToolPath("", TOOLCHAIN_INFO) | 131 print "Using toolchain from :" + ToolPath("", TOOLCHAIN_INFO) |
129 return toolchain_info | 132 return toolchain_info |
130 | 133 |
131 raise Exception("Could not find tool chain") | 134 raise Exception("Could not find tool chain") |
132 | 135 |
| 136 def GetAapt(): |
| 137 """Returns the path to aapt. |
| 138 |
| 139 Args: |
| 140 None |
| 141 |
| 142 Returns: |
| 143 the pathname of the 'aapt' executable. |
| 144 """ |
| 145 sdk_home = os.path.join('third_party', 'android_tools', 'sdk') |
| 146 sdk_home = os.environ.get('SDK_HOME', sdk_home) |
| 147 aapt_exe = glob.glob(os.path.join(sdk_home, 'build-tools', '*', 'aapt')) |
| 148 if not aapt_exe: |
| 149 return None |
| 150 return sorted(aapt_exe, key=os.path.getmtime, reverse=True)[0] |
| 151 |
| 152 def ApkMatchPackageName(aapt, apk_path, package_name): |
| 153 """Returns true the APK's package name matches package_name. |
| 154 |
| 155 Args: |
| 156 aapt: pathname for the 'aapt' executable. |
| 157 apk_path: pathname of the APK file. |
| 158 package_name: package name to match. |
| 159 |
| 160 Returns: |
| 161 True if the package name matches or aapt is None, False otherwise. |
| 162 """ |
| 163 if not aapt: |
| 164 # Allow false positives |
| 165 return True |
| 166 aapt_output = subprocess.check_output( |
| 167 [aapt, 'dump', 'badging', apk_path]).split('\n') |
| 168 package_name_re = re.compile(r'package: .*name=\'(\S*)\'') |
| 169 for line in aapt_output: |
| 170 match = package_name_re.match(line) |
| 171 if match: |
| 172 return package_name == match.group(1) |
| 173 return False |
| 174 |
| 175 def PathListJoin(prefix_list, suffix_list): |
| 176 """Returns each prefix in prefix_list joined with each suffix in suffix list. |
| 177 |
| 178 Args: |
| 179 prefix_list: list of path prefixes. |
| 180 suffix_list: list of path suffixes. |
| 181 |
| 182 Returns: |
| 183 List of paths each of which joins a prefix with a suffix. |
| 184 """ |
| 185 return [ |
| 186 os.path.join(prefix, suffix) |
| 187 for prefix in prefix_list for suffix in suffix_list ] |
| 188 |
| 189 def GetCandidates(dirs, filepart, candidate_fun): |
| 190 """Returns a list of candidate filenames. |
| 191 |
| 192 Args: |
| 193 dirs: a list of the directory part of the pathname. |
| 194 filepart: the file part of the pathname. |
| 195 candidate_fun: a function to apply to each candidate, returns a list. |
| 196 |
| 197 Returns: |
| 198 A list of candidate files ordered by modification time, newest first. |
| 199 """ |
| 200 out_dir = os.environ.get('CHROMIUM_OUT_DIR', 'out') |
| 201 out_dir = os.path.join(CHROME_SYMBOLS_DIR, out_dir) |
| 202 buildtype = os.environ.get('BUILDTYPE') |
| 203 if buildtype: |
| 204 buildtype_list = [ buildtype ] |
| 205 else: |
| 206 buildtype_list = [ 'Debug', 'Release' ] |
| 207 |
| 208 candidates = PathListJoin([out_dir], buildtype_list) + [CHROME_SYMBOLS_DIR] |
| 209 candidates = PathListJoin(candidates, dirs) |
| 210 candidates = PathListJoin(candidates, [filepart]) |
| 211 candidates = list( |
| 212 itertools.chain.from_iterable(map(candidate_fun, candidates))) |
| 213 candidates = sorted(candidates, key=os.path.getmtime, reverse=True) |
| 214 return candidates |
| 215 |
| 216 def GetCandidateApks(): |
| 217 """Returns a list of APKs which could contain the library. |
| 218 |
| 219 Args: |
| 220 None |
| 221 |
| 222 Returns: |
| 223 list of APK filename which could contain the library. |
| 224 """ |
| 225 return GetCandidates(['apks'], '*.apk', glob.glob) |
| 226 |
| 227 def GetCrazyLib(apk_filename): |
| 228 """Returns the name of the first crazy library from this APK. |
| 229 |
| 230 Args: |
| 231 apk_filename: name of an APK file. |
| 232 |
| 233 Returns: |
| 234 Name of the first library which would be crazy loaded from this APK. |
| 235 """ |
| 236 zip_file = zipfile.ZipFile(apk_filename, 'r') |
| 237 for filename in zip_file.namelist(): |
| 238 match = re.match('lib/[^/]*/crazy.(lib.*[.]so)', filename) |
| 239 if match: |
| 240 return match.group(1) |
| 241 |
| 242 def GetMatchingApks(device_apk_name): |
| 243 """Find any APKs which match the package indicated by the device_apk_name. |
| 244 |
| 245 Args: |
| 246 device_apk_name: name of the APK on the device. |
| 247 |
| 248 Returns: |
| 249 A list of APK filenames which could contain the desired library. |
| 250 """ |
| 251 match = re.match('(.*)-[0-9]+[.]apk$', device_apk_name) |
| 252 if not match: |
| 253 return None |
| 254 package_name = match.group(1) |
| 255 return filter( |
| 256 lambda candidate_apk: |
| 257 ApkMatchPackageName(GetAapt(), candidate_apk, package_name), |
| 258 GetCandidateApks()) |
| 259 |
| 260 def MapDeviceApkToLibrary(device_apk_name): |
| 261 """Provide a library name which corresponds with device_apk_name. |
| 262 |
| 263 Args: |
| 264 device_apk_name: name of the APK on the device. |
| 265 |
| 266 Returns: |
| 267 Name of the library which corresponds to that APK. |
| 268 """ |
| 269 matching_apks = GetMatchingApks(device_apk_name) |
| 270 for matching_apk in matching_apks: |
| 271 crazy_lib = GetCrazyLib(matching_apk) |
| 272 if crazy_lib: |
| 273 return crazy_lib |
| 274 |
| 275 def GetCandidateLibraries(library_name): |
| 276 """Returns a list of candidate library filenames. |
| 277 |
| 278 Args: |
| 279 library_name: basename of the library to match. |
| 280 |
| 281 Returns: |
| 282 A list of matching library filenames for library_name. |
| 283 """ |
| 284 return GetCandidates( |
| 285 ['lib', 'lib.target'], library_name, |
| 286 lambda filename: filter(os.path.exists, [filename])) |
| 287 |
133 def TranslateLibPath(lib): | 288 def TranslateLibPath(lib): |
134 # SymbolInformation(lib, addr) receives lib as the path from symbols | 289 # 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 | 290 # 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 | 291 # 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. | 292 # 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 | 293 # 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. | 294 # untranslated in case it is an Android symbol in SYMBOLS_DIR. |
140 library_name = os.path.basename(lib) | 295 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'), | |
147 ] | |
148 | 296 |
149 candidate_libraries = map( | 297 # The filename in the stack trace maybe an APK name rather than a library |
150 lambda d: ('%s/%s/%s' % (CHROME_SYMBOLS_DIR, d, library_name)), | 298 # name. This happens when the library was loaded directly from inside the |
151 candidate_dirs) | 299 # APK. If this is the case we try to figure out the library name by looking |
152 candidate_libraries = filter(os.path.exists, candidate_libraries) | 300 # for a matching APK file and finding the name of the library in contains. |
153 candidate_libraries = sorted(candidate_libraries, | 301 # The name of the APK file on the device is of the form |
154 key=os.path.getmtime, reverse=True) | 302 # <package_name>-<number>.apk. The APK file on the host may have any name |
| 303 # so we look at the APK badging to see if the package name matches. |
| 304 if re.search('-[0-9]+[.]apk$', library_name): |
| 305 mapping = MapDeviceApkToLibrary(library_name) |
| 306 if mapping: |
| 307 library_name = mapping |
155 | 308 |
| 309 candidate_libraries = GetCandidateLibraries(library_name) |
156 if not candidate_libraries: | 310 if not candidate_libraries: |
157 return lib | 311 return lib |
158 | 312 |
159 library_path = os.path.relpath(candidate_libraries[0], SYMBOLS_DIR) | 313 library_path = os.path.relpath(candidate_libraries[0], SYMBOLS_DIR) |
160 return '/' + library_path | 314 return '/' + library_path |
161 | 315 |
162 def SymbolInformation(lib, addr, get_detailed_info): | 316 def SymbolInformation(lib, addr, get_detailed_info): |
163 """Look up symbol information about an address. | 317 """Look up symbol information about an address. |
164 | 318 |
165 Args: | 319 Args: |
(...skipping 248 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
414 process.stdin.write("\n") | 568 process.stdin.write("\n") |
415 process.stdin.close() | 569 process.stdin.close() |
416 demangled_symbol = process.stdout.readline().strip() | 570 demangled_symbol = process.stdout.readline().strip() |
417 process.stdout.close() | 571 process.stdout.close() |
418 return demangled_symbol | 572 return demangled_symbol |
419 | 573 |
420 def FormatSymbolWithOffset(symbol, offset): | 574 def FormatSymbolWithOffset(symbol, offset): |
421 if offset == 0: | 575 if offset == 0: |
422 return symbol | 576 return symbol |
423 return "%s+%d" % (symbol, offset) | 577 return "%s+%d" % (symbol, offset) |
OLD | NEW |