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 |