| OLD | NEW |
| (Empty) |
| 1 #!/usr/bin/env python | |
| 2 # Copyright 2014 The Chromium Authors. All rights reserved. | |
| 3 # Use of this source code is governed by a BSD-style license that can be | |
| 4 # found in the LICENSE file. | |
| 5 | |
| 6 from skypy.skyserver import SkyServer | |
| 7 import argparse | |
| 8 import json | |
| 9 import logging | |
| 10 import os | |
| 11 import pipes | |
| 12 import re | |
| 13 import requests | |
| 14 import signal | |
| 15 import skypy.paths | |
| 16 import StringIO | |
| 17 import subprocess | |
| 18 import sys | |
| 19 import time | |
| 20 import urlparse | |
| 21 import platform | |
| 22 | |
| 23 SUPPORTED_MIME_TYPES = [ | |
| 24 'text/html', | |
| 25 'text/sky', | |
| 26 'text/plain', | |
| 27 ] | |
| 28 | |
| 29 DEFAULT_SKY_COMMAND_PORT = 7777 | |
| 30 GDB_PORT = 8888 | |
| 31 SKY_SERVER_PORT = 9999 | |
| 32 DEFAULT_URL = "https://raw.githubusercontent.com/domokit/mojo/master/sky/example
s/home.sky" | |
| 33 | |
| 34 ANDROID_PACKAGE = "org.chromium.mojo.shell" | |
| 35 ANDROID_ACTIVITY = "%s/.MojoShellActivity" % ANDROID_PACKAGE | |
| 36 ANDROID_APK_NAME = 'MojoShell.apk' | |
| 37 | |
| 38 PID_FILE_PATH = "/tmp/skydb.pids" | |
| 39 CACHE_LINKS_PATH = '/tmp/mojo_cache_links' | |
| 40 | |
| 41 SRC_ROOT = skypy.paths.Paths('ignored').src_root | |
| 42 ADB_PATH = os.path.join(SRC_ROOT, | |
| 43 'third_party/android_tools/sdk/platform-tools/adb') | |
| 44 | |
| 45 # TODO(iansf): Fix undefined behavior when you have more than one device attache
d. | |
| 46 SYSTEM_LIBS_ROOT_PATH = '/tmp/device_libs/%s' % (subprocess.check_output([ADB_PA
TH, 'get-serialno']).strip()) | |
| 47 | |
| 48 | |
| 49 # FIXME: Move this into mopy.config | |
| 50 def gn_args_from_build_dir(build_dir): | |
| 51 gn_cmd = [ | |
| 52 'gn', 'args', | |
| 53 build_dir, | |
| 54 '--list', '--short' | |
| 55 ] | |
| 56 config = {} | |
| 57 for line in subprocess.check_output(gn_cmd).strip().split('\n'): | |
| 58 # FIXME: This doesn't handle = in values. | |
| 59 key, value = line.split(' = ') | |
| 60 config[key] = value | |
| 61 return config | |
| 62 | |
| 63 | |
| 64 def ensure_assets_are_downloaded(build_dir): | |
| 65 sky_pkg_dir = os.path.join(build_dir, 'gen', 'dart-pkg', 'sky') | |
| 66 sky_pkg_lib_dir = os.path.join(sky_pkg_dir, 'lib') | |
| 67 sky_icons_dir = \ | |
| 68 os.path.join(sky_pkg_lib_dir, 'assets', 'material-design-icons') | |
| 69 if not os.path.isdir(sky_icons_dir): | |
| 70 logging.info('NOTE: sky/assets/material-design-icons missing, ' | |
| 71 'Running `download_material_design_icons` for you.') | |
| 72 subprocess.check_call( | |
| 73 [os.path.join(sky_pkg_lib_dir, 'download_material_design_icons')]) | |
| 74 | |
| 75 class SkyDebugger(object): | |
| 76 def __init__(self): | |
| 77 self.pids = {} | |
| 78 self.paths = None | |
| 79 | |
| 80 def _server_root_for_url(self, url_or_path): | |
| 81 path = os.path.abspath(url_or_path) | |
| 82 if os.path.commonprefix([path, SRC_ROOT]) == SRC_ROOT: | |
| 83 server_root = SRC_ROOT | |
| 84 else: | |
| 85 server_root = os.path.dirname(path) | |
| 86 logging.warn( | |
| 87 '%s is outside of mojo root, using %s as server root' % | |
| 88 (path, server_root)) | |
| 89 return server_root | |
| 90 | |
| 91 def _in_chromoting(self): | |
| 92 return os.environ.get('CHROME_REMOTE_DESKTOP_SESSION', False) | |
| 93 | |
| 94 def _wrap_for_android(self, shell_args): | |
| 95 # am shell --esa: (someone shoot me now) | |
| 96 # [--esa <EXTRA_KEY> <EXTRA_STRING_VALUE>[,<EXTRA_STRING_VALUE...]] | |
| 97 # (to embed a comma into a string escape it using "\,") | |
| 98 escaped_args = map(lambda arg: arg.replace(',', '\\,'), shell_args) | |
| 99 return [ | |
| 100 ADB_PATH, 'shell', | |
| 101 'am', 'start', | |
| 102 '-W', | |
| 103 '-S', | |
| 104 '-a', 'android.intent.action.VIEW', | |
| 105 '-n', ANDROID_ACTIVITY, | |
| 106 # FIXME: This quoting is very error-prone. Perhaps we should read | |
| 107 # our args from a file instead? | |
| 108 '--esa', 'parameters', ','.join(escaped_args), | |
| 109 ] | |
| 110 | |
| 111 def _build_mojo_shell_command(self, args, is_android): | |
| 112 content_handlers = ['%s,%s' % (mime_type, 'mojo:sky_viewer') | |
| 113 for mime_type in SUPPORTED_MIME_TYPES] | |
| 114 | |
| 115 remote_command_port = self.pids.get('remote_sky_command_port', self.pids
['sky_command_port']) | |
| 116 remote_server_port = self.pids.get('remote_sky_server_port', self.pids['
sky_server_port']) | |
| 117 | |
| 118 shell_args = [ | |
| 119 '--v=1', | |
| 120 '--content-handlers=%s' % ','.join(content_handlers), | |
| 121 '--url-mappings=mojo:window_manager=mojo:kiosk_wm', | |
| 122 '--args-for=mojo:debugger %d --wm' % remote_command_port, | |
| 123 'mojo:debugger', | |
| 124 ] | |
| 125 | |
| 126 if args.url_or_path: | |
| 127 shell_args.append( | |
| 128 '--args-for=mojo:window_manager %s' % self._url_from_args(args)) | |
| 129 | |
| 130 if args.trace_startup: | |
| 131 shell_args.append('--trace-startup') | |
| 132 | |
| 133 # Map all mojo: urls to http: urls using the --origin command. | |
| 134 build_dir_url = SkyServer.url_for_path( | |
| 135 remote_server_port, | |
| 136 self.pids['sky_server_root'], | |
| 137 self.pids['build_dir']) | |
| 138 | |
| 139 # TODO(eseidel): We should do this on linux, but we need to fix | |
| 140 # mojo http loading to be faster first. | |
| 141 if is_android: | |
| 142 shell_args += ['--origin=%s' % build_dir_url] | |
| 143 | |
| 144 # Desktop-only work-around for mojo crashing under chromoting. | |
| 145 if not is_android and args.use_osmesa: | |
| 146 shell_args.append( | |
| 147 '--args-for=mojo:native_viewport_service --use-osmesa') | |
| 148 | |
| 149 if is_android and args.gdb: | |
| 150 shell_args.append('--wait-for-debugger') | |
| 151 shell_args.append('--predictable-app-filenames') | |
| 152 | |
| 153 if 'remote_sky_server_port' in self.pids: | |
| 154 shell_command = self._wrap_for_android(shell_args) | |
| 155 else: | |
| 156 shell_command = [self.paths.mojo_shell_path] + shell_args | |
| 157 | |
| 158 return shell_command | |
| 159 | |
| 160 def sky_server_for_args(self, args, packages_root): | |
| 161 # FIXME: This is a hack. sky_server should just take a build_dir | |
| 162 # not a magical "configuration" name. | |
| 163 configuration = os.path.basename(os.path.normpath(self.paths.build_dir)) | |
| 164 server_root = self._server_root_for_url(args.url_or_path) | |
| 165 return SkyServer(SKY_SERVER_PORT, configuration, server_root, packages_r
oot) | |
| 166 | |
| 167 def _create_paths_for_build_dir(self, build_dir): | |
| 168 # skypy.paths.Paths takes a root-relative build_dir argument. :( | |
| 169 abs_build_dir = os.path.abspath(build_dir) | |
| 170 root_relative_build_dir = os.path.relpath(abs_build_dir, SRC_ROOT) | |
| 171 return skypy.paths.Paths(root_relative_build_dir) | |
| 172 | |
| 173 def _find_remote_pid_for_package(self, package): | |
| 174 ps_output = subprocess.check_output([ADB_PATH, 'shell', 'ps']) | |
| 175 for line in ps_output.split('\n'): | |
| 176 fields = line.split() | |
| 177 if fields and fields[-1] == package: | |
| 178 return fields[1] | |
| 179 return None | |
| 180 | |
| 181 def _find_install_location_for_package(self, package): | |
| 182 pm_command = [ADB_PATH, 'shell', 'pm', 'path', package] | |
| 183 pm_output = subprocess.check_output(pm_command) | |
| 184 # e.g. package:/data/app/org.chromium.mojo.shell-1/base.apk | |
| 185 return pm_output.split(':')[-1] | |
| 186 | |
| 187 def start_command(self, args): | |
| 188 # FIXME: Lame that we use self for a command-specific variable. | |
| 189 self.paths = self._create_paths_for_build_dir(args.build_dir) | |
| 190 self.stop_command(None) # Quit any existing process. | |
| 191 | |
| 192 # FIXME: This is probably not the right way to compute is_android | |
| 193 # from the build directory? | |
| 194 gn_args = gn_args_from_build_dir(self.paths.build_dir) | |
| 195 is_android = 'android_sdk_version' in gn_args | |
| 196 | |
| 197 ensure_assets_are_downloaded(args.build_dir) | |
| 198 | |
| 199 shell_found = True | |
| 200 if is_android: | |
| 201 apk_path = os.path.join(self.paths.build_dir, 'apks', ANDROID_APK_NA
ME) | |
| 202 if not os.path.exists(apk_path): | |
| 203 print "%s not found in build_dir '%s'" % \ | |
| 204 (ANDROID_APK_NAME, os.path.join(args.build_dir, 'apks')) | |
| 205 shell_found = False | |
| 206 elif not os.path.exists(self.paths.mojo_shell_path): | |
| 207 print "mojo_shell not found in build_dir '%s'" % args.build_dir | |
| 208 shell_found = False | |
| 209 | |
| 210 if not shell_found: | |
| 211 print "Are you sure you sure that's a valid build_dir location?" | |
| 212 print "See skydb start --help for more info" | |
| 213 sys.exit(2) | |
| 214 | |
| 215 if is_android and args.gdb and not 'is_debug' in gn_args: | |
| 216 # FIXME: We don't include gdbserver in the release APK... | |
| 217 print "Cannot debug Release builds on Android" | |
| 218 sys.exit(2) | |
| 219 | |
| 220 dart_pkg_dir = os.path.join(self.paths.build_dir, 'gen', 'dart-pkg') | |
| 221 packages_root = os.path.join(dart_pkg_dir, 'packages') | |
| 222 | |
| 223 sky_server = self.sky_server_for_args(args, packages_root) | |
| 224 self.pids['sky_server_pid'] = sky_server.start() | |
| 225 self.pids['sky_server_port'] = sky_server.port | |
| 226 self.pids['sky_server_root'] = sky_server.root | |
| 227 | |
| 228 self.pids['build_dir'] = self.paths.build_dir | |
| 229 self.pids['sky_command_port'] = args.command_port | |
| 230 | |
| 231 if is_android: | |
| 232 # TODO(eseidel): This should move into a helper method and handle | |
| 233 # failures with nice messages explaining how to get root. | |
| 234 subprocess.check_call([ADB_PATH, 'root']) | |
| 235 | |
| 236 # We could make installing conditional on an argument. | |
| 237 # -r to replace an existing apk, -d to allow version downgrade. | |
| 238 subprocess.check_call([ADB_PATH, 'install', '-r', '-d', apk_path]) | |
| 239 | |
| 240 port_string = 'tcp:%s' % sky_server.port | |
| 241 subprocess.check_call([ | |
| 242 ADB_PATH, 'reverse', port_string, port_string | |
| 243 ]) | |
| 244 self.pids['remote_sky_server_port'] = sky_server.port | |
| 245 | |
| 246 port_string = 'tcp:%s' % args.command_port | |
| 247 subprocess.check_call([ | |
| 248 ADB_PATH, 'forward', port_string, port_string | |
| 249 ]) | |
| 250 self.pids['remote_sky_command_port'] = args.command_port | |
| 251 | |
| 252 shell_command = self._build_mojo_shell_command(args, is_android) | |
| 253 | |
| 254 # On android we can't launch inside gdb, but rather have to attach. | |
| 255 if not is_android and args.gdb: | |
| 256 shell_command = ['gdbserver', ':%d' % GDB_PORT] + shell_command | |
| 257 | |
| 258 print ' '.join(map(pipes.quote, shell_command)) | |
| 259 # This pid is meaningless on android (it's the adb shell pid) | |
| 260 start_command_pid = subprocess.Popen(shell_command).pid | |
| 261 | |
| 262 if is_android: | |
| 263 # TODO(eseidel): am start -W does not seem to work? | |
| 264 pid_tries = 0 | |
| 265 while True: | |
| 266 pid = self._find_remote_pid_for_package(ANDROID_PACKAGE) | |
| 267 if pid or pid_tries > 3: | |
| 268 break | |
| 269 logging.debug('No pid for %s yet, waiting' % ANDROID_PACKAGE) | |
| 270 time.sleep(5) | |
| 271 pid_tries += 1 | |
| 272 | |
| 273 if not pid: | |
| 274 logging.error('Failed to find mojo_shell pid on device!') | |
| 275 return | |
| 276 self.pids['mojo_shell_pid'] = pid | |
| 277 else: | |
| 278 self.pids['mojo_shell_pid'] = start_command_pid | |
| 279 | |
| 280 if args.gdb and is_android: | |
| 281 # We push our own copy of gdbserver with the package since | |
| 282 # the default gdbserver is a different version from our gdb. | |
| 283 package_path = \ | |
| 284 self._find_install_location_for_package(ANDROID_PACKAGE) | |
| 285 gdb_server_path = os.path.join( | |
| 286 os.path.dirname(package_path), 'lib/arm/gdbserver') | |
| 287 gdbserver_cmd = [ | |
| 288 ADB_PATH, 'shell', | |
| 289 gdb_server_path, '--attach', | |
| 290 ':%d' % GDB_PORT, | |
| 291 str(self.pids['mojo_shell_pid']) | |
| 292 ] | |
| 293 print ' '.join(map(pipes.quote, gdbserver_cmd)) | |
| 294 self.pids['adb_shell_gdbserver_pid'] = \ | |
| 295 subprocess.Popen(gdbserver_cmd).pid | |
| 296 | |
| 297 port_string = 'tcp:%d' % GDB_PORT | |
| 298 subprocess.check_call([ | |
| 299 ADB_PATH, 'forward', port_string, port_string | |
| 300 ]) | |
| 301 self.pids['remote_gdbserver_port'] = GDB_PORT | |
| 302 | |
| 303 if not args.gdb: | |
| 304 if not self._wait_for_sky_command_port(): | |
| 305 logging.error('Failed to start sky') | |
| 306 self.stop_command(None) | |
| 307 else: | |
| 308 # We could just run gdb_attach_command here, but when I do that | |
| 309 # it auto-suspends in my zsh. Unclear why. | |
| 310 # self.gdb_attach_command(args) | |
| 311 print "Run 'skydb gdb_attach' to attach." | |
| 312 | |
| 313 def _kill_if_exists(self, key, name): | |
| 314 pid = self.pids.pop(key, None) | |
| 315 if not pid: | |
| 316 logging.info('No pid for %s, nothing to do.' % name) | |
| 317 return | |
| 318 logging.info('Killing %s (%d).' % (name, pid)) | |
| 319 try: | |
| 320 os.kill(pid, signal.SIGTERM) | |
| 321 except OSError: | |
| 322 logging.info('%s (%d) already gone.' % (name, pid)) | |
| 323 | |
| 324 def stop_command(self, args): | |
| 325 # TODO(eseidel): mojo_shell crashes when attempting graceful shutdown. | |
| 326 # self._run_basic_command('/quit') | |
| 327 | |
| 328 self._kill_if_exists('sky_server_pid', 'sky_server') | |
| 329 | |
| 330 if 'remote_sky_server_port' in self.pids: | |
| 331 port_string = 'tcp:%s' % self.pids['remote_sky_server_port'] | |
| 332 subprocess.call([ADB_PATH, 'reverse', '--remove', port_string]) | |
| 333 | |
| 334 if 'remote_sky_command_port' in self.pids: | |
| 335 # adb forward --remove takes the *host* port, not the remote port. | |
| 336 port_string = 'tcp:%s' % self.pids['sky_command_port'] | |
| 337 subprocess.call([ADB_PATH, 'forward', '--remove', port_string]) | |
| 338 | |
| 339 subprocess.call([ | |
| 340 ADB_PATH, 'shell', 'am', 'force-stop', ANDROID_PACKAGE]) | |
| 341 else: | |
| 342 # Only try to kill mojo_shell if it's running locally. | |
| 343 self._kill_if_exists('mojo_shell_pid', 'mojo_shell') | |
| 344 | |
| 345 if 'remote_gdbserver_port' in self.pids: | |
| 346 self._kill_if_exists('adb_shell_gdbserver_pid', | |
| 347 'adb shell gdbserver') | |
| 348 | |
| 349 port_string = 'tcp:%s' % self.pids['remote_gdbserver_port'] | |
| 350 subprocess.call([ADB_PATH, 'forward', '--remove', port_string]) | |
| 351 self.pids = {} # Clear out our pid file. | |
| 352 | |
| 353 def _url_from_args(self, args): | |
| 354 if urlparse.urlparse(args.url_or_path).scheme: | |
| 355 return args.url_or_path | |
| 356 # The load happens on the remote device, use the remote port. | |
| 357 remote_sky_server_port = self.pids.get('remote_sky_server_port', | |
| 358 self.pids['sky_server_port']) | |
| 359 return SkyServer.url_for_path(remote_sky_server_port, | |
| 360 self.pids['sky_server_root'], args.url_or_path) | |
| 361 | |
| 362 def load_command(self, args): | |
| 363 self._run_basic_command('/load', self._url_from_args(args)) | |
| 364 | |
| 365 def _read_mojo_map(self): | |
| 366 # TODO(eseidel): Does not work for android. | |
| 367 mojo_map_path = "/tmp/mojo_shell.%d.maps" % self.pids['mojo_shell_pid'] | |
| 368 with open(mojo_map_path, 'r') as maps_file: | |
| 369 lines = maps_file.read().strip().split('\n') | |
| 370 return dict(map(lambda line: line.split(' '), lines)) | |
| 371 | |
| 372 def stop_tracing_command(self, args): | |
| 373 file_name = args.file_name | |
| 374 trace = self._send_command_to_sky('/stop_tracing').content | |
| 375 with open(file_name, "wb") as trace_file: | |
| 376 trace_file.write('{"traceEvents":[') | |
| 377 trace_file.write(trace) | |
| 378 trace_file.write(']}') | |
| 379 print "Trace saved in %s" % file_name | |
| 380 | |
| 381 def stop_profiling_command(self, args): | |
| 382 self._run_basic_command('/stop_profiling') | |
| 383 mojo_map = self._read_mojo_map() | |
| 384 | |
| 385 # TODO(eseidel): We should have a helper for resolving urls, etc. | |
| 386 remote_server_port = self.pids.get('remote_sky_server_port', self.pids['
sky_server_port']) | |
| 387 build_dir_url = SkyServer.url_for_path( | |
| 388 remote_server_port, | |
| 389 self.pids['sky_server_root'], | |
| 390 self.pids['build_dir']) | |
| 391 | |
| 392 # Map /tmp cache paths to urls and then to local build_dir paths. | |
| 393 def map_to_local_paths(match): | |
| 394 path = match.group('mojo_path') | |
| 395 url = mojo_map.get(path) | |
| 396 if url and url.startswith(build_dir_url): | |
| 397 return url.replace(build_dir_url, self.pids['build_dir']) | |
| 398 return match.group(0) | |
| 399 | |
| 400 MOJO_PATH_RE = re.compile(r'(?P<mojo_path>\S+\.mojo)') | |
| 401 MOJO_NAME_RE = re.compile(r'(?P<mojo_name>\w+)\.mojo') | |
| 402 | |
| 403 with open("sky_viewer.pprof", "rb+") as profile_file: | |
| 404 # ISO-8859-1 can represent arbitrary binary while still keeping | |
| 405 # ASCII characters in the ASCII range (allowing us to regexp). | |
| 406 # http://en.wikipedia.org/wiki/ISO/IEC_8859-1 | |
| 407 as_string = profile_file.read().decode('iso-8859-1') | |
| 408 # Using the mojo_shell.PID.maps file tmp paths to build_dir paths. | |
| 409 as_string = MOJO_PATH_RE.sub(map_to_local_paths, as_string) | |
| 410 # In release foo.mojo is stripped but libfoo_library.so isn't. | |
| 411 as_string = MOJO_NAME_RE.sub(r'lib\1_library.so', as_string) | |
| 412 profile_file.seek(0) | |
| 413 profile_file.write(as_string.encode('iso-8859-1')) | |
| 414 profile_file.truncate() | |
| 415 | |
| 416 def _command_base_url(self): | |
| 417 return 'http://localhost:%s' % self.pids['sky_command_port'] | |
| 418 | |
| 419 def _send_command_to_sky(self, command_path, payload=None): | |
| 420 url = 'http://localhost:%s%s' % ( | |
| 421 self.pids['sky_command_port'], command_path) | |
| 422 if payload: | |
| 423 response = requests.post(url, payload) | |
| 424 else: | |
| 425 response = requests.get(url) | |
| 426 return response | |
| 427 | |
| 428 def _run_basic_command(self, command_path, payload=None): | |
| 429 print self._send_command_to_sky(command_path, payload=payload).text | |
| 430 | |
| 431 # FIXME: These could be made into a context object with __enter__/__exit__. | |
| 432 def _load_pid_file(self, path): | |
| 433 try: | |
| 434 with open(path, 'r') as pid_file: | |
| 435 return json.load(pid_file) | |
| 436 except: | |
| 437 if os.path.exists(path): | |
| 438 logging.warn('Failed to read pid file: %s' % path) | |
| 439 return {} | |
| 440 | |
| 441 def _write_pid_file(self, path, pids): | |
| 442 try: | |
| 443 with open(path, 'w') as pid_file: | |
| 444 json.dump(pids, pid_file, indent=2, sort_keys=True) | |
| 445 except: | |
| 446 logging.warn('Failed to write pid file: %s' % path) | |
| 447 | |
| 448 def _add_basic_command(self, subparsers, name, url_path, help_text): | |
| 449 parser = subparsers.add_parser(name, help=help_text) | |
| 450 command = lambda args: self._run_basic_command(url_path) | |
| 451 parser.set_defaults(func=command) | |
| 452 | |
| 453 def _wait_for_sky_command_port(self): | |
| 454 tries = 0 | |
| 455 while True: | |
| 456 try: | |
| 457 self._run_basic_command('/') | |
| 458 return True | |
| 459 except: | |
| 460 tries += 1 | |
| 461 if tries == 3: | |
| 462 logging.warn('Still waiting for sky on port %s' % | |
| 463 self.pids['sky_command_port']) | |
| 464 if tries > 10: | |
| 465 return False | |
| 466 time.sleep(1) | |
| 467 | |
| 468 def logcat_command(self, args): | |
| 469 TAGS = [ | |
| 470 'AndroidHandler', | |
| 471 'MojoMain', | |
| 472 'MojoShellActivity', | |
| 473 'MojoShellApplication', | |
| 474 'chromium', | |
| 475 ] | |
| 476 subprocess.call([ADB_PATH, 'logcat', '-d', '-s'] + TAGS) | |
| 477 | |
| 478 def _pull_system_libraries(self, system_libs_root): | |
| 479 # Pull down the system libraries this pid has already mapped in. | |
| 480 # TODO(eseidel): This does not handle dynamic loads. | |
| 481 library_cacher_path = os.path.join( | |
| 482 self.paths.sky_tools_directory, 'android_library_cacher.py') | |
| 483 subprocess.call([ | |
| 484 library_cacher_path, system_libs_root, self.pids['mojo_shell_pid'] | |
| 485 ]) | |
| 486 | |
| 487 # TODO(eseidel): adb_gdb does, this, unclear why solib-absolute-prefix | |
| 488 # doesn't make this explicit listing not necessary? | |
| 489 return subprocess.check_output([ | |
| 490 'find', system_libs_root, | |
| 491 '-mindepth', '1', | |
| 492 '-maxdepth', '4', | |
| 493 '-type', 'd', | |
| 494 ]).strip().split('\n') | |
| 495 | |
| 496 def _add_android_library_links(self, links_path): | |
| 497 # TODO(eseidel): This might not match mojo_shell on the device? | |
| 498 # TODO(eseidel): Should we pass libmojo_shell.so as 'file' to gdb? | |
| 499 shell_link_path = os.path.join(links_path, 'libmojo_shell.so') | |
| 500 if os.path.lexists(shell_link_path): | |
| 501 os.unlink(shell_link_path) | |
| 502 os.symlink(self.paths.mojo_shell_path, shell_link_path) | |
| 503 | |
| 504 def gdb_attach_command(self, args): | |
| 505 self.paths = self._create_paths_for_build_dir(self.pids['build_dir']) | |
| 506 | |
| 507 if not os.path.exists(CACHE_LINKS_PATH): | |
| 508 os.makedirs(CACHE_LINKS_PATH) | |
| 509 cache_linker_path = os.path.join( | |
| 510 self.paths.sky_tools_directory, 'mojo_cache_linker.py') | |
| 511 subprocess.check_call([ | |
| 512 cache_linker_path, CACHE_LINKS_PATH, self.paths.build_dir]) | |
| 513 | |
| 514 symbol_search_paths = [ | |
| 515 self.pids['build_dir'], | |
| 516 CACHE_LINKS_PATH, | |
| 517 ] | |
| 518 gdb_path = '/usr/bin/gdb' | |
| 519 | |
| 520 eval_commands = [ | |
| 521 'directory %s' % self.paths.src_root, | |
| 522 'file %s' % self.paths.mojo_shell_path, | |
| 523 'target remote localhost:%s' % GDB_PORT, | |
| 524 ] | |
| 525 | |
| 526 # A bunch of extra work is needed for android: | |
| 527 if 'remote_sky_server_port' in self.pids: | |
| 528 self._add_android_library_links(CACHE_LINKS_PATH) | |
| 529 | |
| 530 system_lib_dirs = self._pull_system_libraries(SYSTEM_LIBS_ROOT_PATH) | |
| 531 eval_commands.append( | |
| 532 'set solib-absolute-prefix %s' % SYSTEM_LIBS_ROOT_PATH) | |
| 533 | |
| 534 symbol_search_paths = system_lib_dirs + symbol_search_paths | |
| 535 | |
| 536 # TODO(eseidel): We need to look up the toolchain somehow? | |
| 537 if platform.system() == 'Darwin': | |
| 538 gdb_path = os.path.join(SRC_ROOT, 'third_party/android_tools/ndk
/' | |
| 539 'toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64
/' | |
| 540 'bin/arm-linux-androideabi-gdb') | |
| 541 else: | |
| 542 gdb_path = os.path.join(SRC_ROOT, 'third_party/android_tools/ndk
/' | |
| 543 'toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/
' | |
| 544 'bin/arm-linux-androideabi-gdb') | |
| 545 | |
| 546 # Set solib-search-path after letting android modify symbol_search_paths | |
| 547 eval_commands.append( | |
| 548 'set solib-search-path %s' % ':'.join(symbol_search_paths)) | |
| 549 | |
| 550 exec_command = [gdb_path] | |
| 551 for command in eval_commands: | |
| 552 exec_command += ['--eval-command', command] | |
| 553 | |
| 554 print " ".join(exec_command) | |
| 555 | |
| 556 # Write out our pid file before we exec ourselves. | |
| 557 self._write_pid_file(PID_FILE_PATH, self.pids) | |
| 558 | |
| 559 # Exec gdb directly to avoid python intercepting symbols, etc. | |
| 560 os.execv(exec_command[0], exec_command) | |
| 561 | |
| 562 def print_crash_command(self, args): | |
| 563 logcat_cmd = [ADB_PATH, 'logcat', '-d'] | |
| 564 logcat = subprocess.Popen(logcat_cmd, stdout=subprocess.PIPE) | |
| 565 | |
| 566 stack_path = os.path.join(SRC_ROOT, | |
| 567 'tools', 'android_stack_parser', 'stack') | |
| 568 stack = subprocess.Popen([stack_path, '-'], stdin=logcat.stdout) | |
| 569 logcat.wait() | |
| 570 stack.wait() | |
| 571 | |
| 572 def pids_command(self, args): | |
| 573 print json.dumps(self.pids, indent=1) | |
| 574 | |
| 575 def main(self): | |
| 576 logging.basicConfig(level=logging.WARNING) | |
| 577 logging.getLogger("requests").setLevel(logging.WARNING) | |
| 578 | |
| 579 self.pids = self._load_pid_file(PID_FILE_PATH) | |
| 580 | |
| 581 parser = argparse.ArgumentParser(description='Sky launcher/debugger') | |
| 582 subparsers = parser.add_subparsers(help='sub-command help') | |
| 583 | |
| 584 start_parser = subparsers.add_parser('start', | |
| 585 help='launch a new mojo_shell with sky') | |
| 586 start_parser.add_argument('--gdb', action='store_true') | |
| 587 start_parser.add_argument('--command-port', type=int, | |
| 588 default=DEFAULT_SKY_COMMAND_PORT) | |
| 589 start_parser.add_argument('--use-osmesa', action='store_true', | |
| 590 default=self._in_chromoting()) | |
| 591 start_parser.add_argument('build_dir', type=str) | |
| 592 start_parser.add_argument('url_or_path', nargs='?', type=str, | |
| 593 default=DEFAULT_URL) | |
| 594 start_parser.add_argument('--show-command', action='store_true', | |
| 595 help='Display the shell command and exit') | |
| 596 start_parser.add_argument('--trace-startup', action='store_true') | |
| 597 start_parser.set_defaults(func=self.start_command) | |
| 598 | |
| 599 stop_parser = subparsers.add_parser('stop', | |
| 600 help=('stop sky (as listed in %s)' % PID_FILE_PATH)) | |
| 601 stop_parser.set_defaults(func=self.stop_command) | |
| 602 | |
| 603 pids_parser = subparsers.add_parser('pids', | |
| 604 help='dump the current skydb pids file') | |
| 605 pids_parser.set_defaults(func=self.pids_command) | |
| 606 | |
| 607 logcat_parser = subparsers.add_parser('logcat', | |
| 608 help=('dump sky-related logs from device')) | |
| 609 logcat_parser.set_defaults(func=self.logcat_command) | |
| 610 | |
| 611 print_crash_parser = subparsers.add_parser('print_crash', | |
| 612 help=('dump (and symbolicate) recent crash-stacks')) | |
| 613 print_crash_parser.set_defaults(func=self.print_crash_command) | |
| 614 | |
| 615 gdb_attach_parser = subparsers.add_parser('gdb_attach', | |
| 616 help='launch gdb and attach to gdbserver launched from start --gdb') | |
| 617 gdb_attach_parser.set_defaults(func=self.gdb_attach_command) | |
| 618 | |
| 619 self._add_basic_command(subparsers, 'start_tracing', '/start_tracing', | |
| 620 'starts tracing the running sky instance') | |
| 621 self._add_basic_command(subparsers, 'reload', '/reload', | |
| 622 'reload the current page') | |
| 623 self._add_basic_command(subparsers, 'start_profiling', '/start_profiling
', | |
| 624 'starts profiling the running sky instance (Linux only)') | |
| 625 | |
| 626 stop_tracing_parser = subparsers.add_parser('stop_tracing', | |
| 627 help='stops tracing the running sky instance') | |
| 628 stop_tracing_parser.add_argument('file_name', type=str, default='sky_vie
wer.trace') | |
| 629 stop_tracing_parser.set_defaults(func=self.stop_tracing_command) | |
| 630 | |
| 631 stop_profiling_parser = subparsers.add_parser('stop_profiling', | |
| 632 help='stops profiling the running sky instance (Linux only)') | |
| 633 stop_profiling_parser.set_defaults(func=self.stop_profiling_command) | |
| 634 | |
| 635 load_parser = subparsers.add_parser('load', | |
| 636 help='load a new page in the currently running sky') | |
| 637 load_parser.add_argument('url_or_path', type=str) | |
| 638 load_parser.set_defaults(func=self.load_command) | |
| 639 | |
| 640 args = parser.parse_args() | |
| 641 args.func(args) | |
| 642 | |
| 643 self._write_pid_file(PID_FILE_PATH, self.pids) | |
| 644 | |
| 645 | |
| 646 if __name__ == '__main__': | |
| 647 SkyDebugger().main() | |
| OLD | NEW |