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

Side by Side Diff: sky/tools/skydb

Issue 848013004: Make --gdb work for android (Closed) Base URL: git@github.com:domokit/mojo.git@master
Patch Set: Updated per qsr review Created 5 years, 11 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
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # Copyright 2014 The Chromium Authors. All rights reserved. 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 3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file. 4 # found in the LICENSE file.
5 5
6 from skypy.skyserver import SkyServer 6 from skypy.skyserver import SkyServer
7 import argparse 7 import argparse
8 import json 8 import json
9 import logging 9 import logging
10 import os 10 import os
11 import pipes 11 import pipes
12 import requests 12 import requests
13 import signal 13 import signal
14 import skypy.paths 14 import skypy.paths
15 import StringIO 15 import StringIO
16 import subprocess 16 import subprocess
17 import sys 17 import sys
18 import time 18 import time
19 import urlparse 19 import urlparse
20 import re
20 21
21 SRC_ROOT = skypy.paths.Paths('ignored').src_root 22 SRC_ROOT = skypy.paths.Paths('ignored').src_root
22 sys.path.insert(0, os.path.join(SRC_ROOT, 'build', 'android')) 23 sys.path.insert(0, os.path.join(SRC_ROOT, 'build', 'android'))
23 from pylib import android_commands 24 from pylib import android_commands
24 from pylib import constants 25 from pylib import constants
25 from pylib import forwarder 26 from pylib import forwarder
26 27
27 28
28 SUPPORTED_MIME_TYPES = [ 29 SUPPORTED_MIME_TYPES = [
29 'text/html', 30 'text/html',
30 'text/sky', 31 'text/sky',
31 'text/plain', 32 'text/plain',
32 ] 33 ]
33 34
34 DEFAULT_SKY_COMMAND_PORT = 7777 35 DEFAULT_SKY_COMMAND_PORT = 7777
35 GDB_PORT = 8888 36 GDB_PORT = 8888
36 SKY_SERVER_PORT = 9999 37 SKY_SERVER_PORT = 9999
37 PID_FILE_PATH = "/tmp/skydb.pids" 38 PID_FILE_PATH = "/tmp/skydb.pids"
38 DEFAULT_URL = "https://raw.githubusercontent.com/domokit/mojo/master/sky/example s/home.sky" 39 DEFAULT_URL = "https://raw.githubusercontent.com/domokit/mojo/master/sky/example s/home.sky"
39 40
40 ANDROID_PACKAGE = "org.chromium.mojo.shell" 41 ANDROID_PACKAGE = "org.chromium.mojo.shell"
41 ANDROID_ACTIVITY = "%s/.MojoShellActivity" % ANDROID_PACKAGE 42 ANDROID_ACTIVITY = "%s/.MojoShellActivity" % ANDROID_PACKAGE
42 43
44
43 # FIXME: Move this into mopy.config 45 # FIXME: Move this into mopy.config
44 def gn_args_from_build_dir(build_dir): 46 def gn_args_from_build_dir(build_dir):
45 gn_cmd = [ 47 gn_cmd = [
46 'gn', 'args', 48 'gn', 'args',
47 build_dir, 49 build_dir,
48 '--list', '--short' 50 '--list', '--short'
49 ] 51 ]
50 config = {} 52 config = {}
51 for line in subprocess.check_output(gn_cmd).strip().split('\n'): 53 for line in subprocess.check_output(gn_cmd).strip().split('\n'):
52 # FIXME: This doesn't handle = in values. 54 # FIXME: This doesn't handle = in values.
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after
90 'am', 'start', 92 'am', 'start',
91 '-W', 93 '-W',
92 '-S', 94 '-S',
93 '-a', 'android.intent.action.VIEW', 95 '-a', 'android.intent.action.VIEW',
94 '-n', ANDROID_ACTIVITY, 96 '-n', ANDROID_ACTIVITY,
95 # FIXME: This quoting is very error-prone. Perhaps we should read 97 # FIXME: This quoting is very error-prone. Perhaps we should read
96 # our args from a file instead? 98 # our args from a file instead?
97 '--esa', 'parameters', ','.join(escaped_args), 99 '--esa', 'parameters', ','.join(escaped_args),
98 ] 100 ]
99 101
100 def _build_mojo_shell_command(self, args): 102 def _build_mojo_shell_command(self, args, is_android):
101 content_handlers = ['%s,%s' % (mime_type, 'mojo:sky_viewer') 103 content_handlers = ['%s,%s' % (mime_type, 'mojo:sky_viewer')
102 for mime_type in SUPPORTED_MIME_TYPES] 104 for mime_type in SUPPORTED_MIME_TYPES]
103 105
104 remote_command_port = self.pids.get('remote_sky_command_port', self.pids ['sky_command_port']) 106 remote_command_port = self.pids.get('remote_sky_command_port', self.pids ['sky_command_port'])
105 107
106 shell_args = [ 108 shell_args = [
107 '--v=1', 109 '--v=1',
108 '--content-handlers=%s' % ','.join(content_handlers), 110 '--content-handlers=%s' % ','.join(content_handlers),
109 '--url-mappings=mojo:window_manager=mojo:sky_debugger', 111 '--url-mappings=mojo:window_manager=mojo:sky_debugger',
110 '--args-for=mojo:sky_debugger_prompt %d' % remote_command_port, 112 '--args-for=mojo:sky_debugger_prompt %d' % remote_command_port,
111 'mojo:window_manager', 113 'mojo:window_manager',
112 ] 114 ]
113 115
116 # Desktop-only work-around for mojo crashing under chromoting.
117 if not is_android and args.use_osmesa:
118 shell_args.append(
119 '--args-for=mojo:native_viewport_service --use-osmesa')
120
121 if is_android and args.gdb:
122 shell_args.append('--wait_for_debugger')
123
114 if 'remote_sky_server_port' in self.pids: 124 if 'remote_sky_server_port' in self.pids:
115 shell_command = self._wrap_for_android(shell_args) 125 shell_command = self._wrap_for_android(shell_args)
116 else: 126 else:
117 shell_command = [self.paths.mojo_shell_path] + shell_args 127 shell_command = [self.paths.mojo_shell_path] + shell_args
118 128
119 return shell_command 129 return shell_command
120 130
121 def _connect_to_device(self): 131 def _connect_to_device(self):
122 device = android_commands.AndroidCommands( 132 device = android_commands.AndroidCommands(
123 android_commands.GetAttachedDevices()[0]) 133 android_commands.GetAttachedDevices()[0])
124 device.EnableAdbRoot() 134 device.EnableAdbRoot()
125 return device 135 return device
126 136
127 def sky_server_for_args(self, args): 137 def sky_server_for_args(self, args):
128 # FIXME: This is a hack. sky_server should just take a build_dir 138 # FIXME: This is a hack. sky_server should just take a build_dir
129 # not a magical "configuration" name. 139 # not a magical "configuration" name.
130 configuration = os.path.basename(os.path.normpath(args.build_dir)) 140 configuration = os.path.basename(os.path.normpath(args.build_dir))
131 server_root = self._server_root_for_url(args.url_or_path) 141 server_root = self._server_root_for_url(args.url_or_path)
132 sky_server = SkyServer(self.paths, SKY_SERVER_PORT, 142 sky_server = SkyServer(self.paths, SKY_SERVER_PORT,
133 configuration, server_root) 143 configuration, server_root)
134 return sky_server 144 return sky_server
135 145
136 def _create_paths_for_build_dir(self, build_dir): 146 def _create_paths_for_build_dir(self, build_dir):
137 # skypy.paths.Paths takes a root-relative build_dir argument. :( 147 # skypy.paths.Paths takes a root-relative build_dir argument. :(
138 abs_build_dir = os.path.abspath(build_dir) 148 abs_build_dir = os.path.abspath(build_dir)
139 root_relative_build_dir = os.path.relpath(abs_build_dir, SRC_ROOT) 149 root_relative_build_dir = os.path.relpath(abs_build_dir, SRC_ROOT)
140 return skypy.paths.Paths(root_relative_build_dir) 150 return skypy.paths.Paths(root_relative_build_dir)
141 151
152 def _find_remote_pid_for_package(self, package):
153 ps_output = subprocess.check_output(['adb', 'shell', 'ps'])
154 for line in ps_output.split('\n'):
155 fields = line.split()
156 if fields and fields[-1] == package:
157 return fields[1]
158 return None
159
160 def _find_install_location_for_package(self, package):
161 pm_command = ['adb', 'shell', 'pm', 'path', package]
162 pm_output = subprocess.check_output(pm_command)
163 # e.g. package:/data/app/org.chromium.mojo.shell-1/base.apk
164 return pm_output.split(':')[-1]
165
142 def start_command(self, args): 166 def start_command(self, args):
143 # FIXME: Lame that we use self for a command-specific variable. 167 # FIXME: Lame that we use self for a command-specific variable.
144 self.paths = self._create_paths_for_build_dir(args.build_dir) 168 self.paths = self._create_paths_for_build_dir(args.build_dir)
145 self.stop_command(None) # Quit any existing process. 169 self.stop_command(None) # Quit any existing process.
146 self.pids = {} # Clear out our pid file. 170 self.pids = {} # Clear out our pid file.
147 171
148 if not os.path.exists(self.paths.mojo_shell_path): 172 if not os.path.exists(self.paths.mojo_shell_path):
149 print "mojo_shell not found in build_dir '%s'" % args.build_dir 173 print "mojo_shell not found in build_dir '%s'" % args.build_dir
150 print "Are you sure you sure that's a valid build_dir location?" 174 print "Are you sure you sure that's a valid build_dir location?"
151 print "See skydb start --help for more info" 175 print "See skydb start --help for more info"
(...skipping 23 matching lines...) Expand all
175 device_http_port = forwarder.Forwarder.DevicePortForHostPort( 199 device_http_port = forwarder.Forwarder.DevicePortForHostPort(
176 sky_server.port) 200 sky_server.port)
177 self.pids['remote_sky_server_port'] = device_http_port 201 self.pids['remote_sky_server_port'] = device_http_port
178 202
179 port_string = 'tcp:%s' % args.command_port 203 port_string = 'tcp:%s' % args.command_port
180 subprocess.check_call([ 204 subprocess.check_call([
181 'adb', 'forward', port_string, port_string 205 'adb', 'forward', port_string, port_string
182 ]) 206 ])
183 self.pids['remote_sky_command_port'] = args.command_port 207 self.pids['remote_sky_command_port'] = args.command_port
184 208
185 shell_command = self._build_mojo_shell_command(args) 209 shell_command = self._build_mojo_shell_command(args, is_android)
186 210
187 if not is_android: 211 # On android we can't launch inside gdb, but rather have to attach.
188 # Desktop-only work-around for mojo crashing under chromoting. 212 if not is_android and args.gdb:
189 if args.use_osmesa: 213 shell_command = ['gdbserver', ':%d' % GDB_PORT] + shell_command
190 shell_args.append(
191 '--args-for=mojo:native_viewport_service --use-osmesa')
192
193 # On android we can't launch inside gdb, but rather have to attach.
194 if args.gdb:
195 shell_command = ['gdbserver', ':%s' % GDB_PORT] + shell_command
196 214
197 print ' '.join(map(pipes.quote, shell_command)) 215 print ' '.join(map(pipes.quote, shell_command))
198 self.pids['mojo_shell_pid'] = subprocess.Popen(shell_command).pid 216 # This pid is meaningless on android (it's the adb shell pid)
217 start_command_pid = subprocess.Popen(shell_command).pid
218
219 if is_android:
220 # TODO(eseidel): am start -W does not seem to work?
221 pid_tries = 0
222 while True:
223 pid = self._find_remote_pid_for_package(ANDROID_PACKAGE)
224 if pid or pid_tries > 3:
225 break
226 logging.warn('No pid for %s yet, waiting' % ANDROID_PACKAGE)
227 time.sleep(5)
228 pid_tries += 1
229
230 if not pid:
231 logging.error('Failed to find mojo_shell pid on device!')
232 return
233 self.pids['mojo_shell_pid'] = pid
234 else:
235 self.pids['mojo_shell_pid'] = start_command_pid
199 236
200 if args.gdb and is_android: 237 if args.gdb and is_android:
201 gdbserver_cmd = ['gdbserver', '--attach', ':%s' % GDB_PORT] 238 # We push our own copy of gdbserver with the package since
202 self.pids['remote_gdbserver_pid'] = subprocess.Popen(shell_command). pid 239 # the default gdbserver is a different version from our gdb.
240 package_path = \
241 self._find_install_location_for_package(ANDROID_PACKAGE)
242 gdb_server_path = os.path.join(
243 os.path.dirname(package_path), 'lib/arm/gdbserver')
244 gdbserver_cmd = [
245 'adb', 'shell',
246 gdb_server_path, '--attach',
247 ':%s' % GDB_PORT,
qsr 2015/01/16 00:46:40 %d ?
eseidel 2015/01/16 19:17:18 I think this makes the code needlessly fragile for
248 str(self.pids['mojo_shell_pid'])
249 ]
250 print ' '.join(map(pipes.quote, gdbserver_cmd))
251 self.pids['adb_shell_gdbserver_pid'] = \
252 subprocess.Popen(gdbserver_cmd).pid
203 253
204 port_string = 'tcp:%s' % GDB_PORT 254 port_string = 'tcp:%s' % GDB_PORT
205 subprocess.check_call([ 255 subprocess.check_call([
206 'adb', 'forward', port_string, port_string 256 'adb', 'forward', port_string, port_string
207 ]) 257 ])
208 self.pids['remote_gdbserver_port'] = GDB_PORT 258 self.pids['remote_gdbserver_port'] = GDB_PORT
209 259
210 if not args.gdb: 260 if not args.gdb:
211 if not self._wait_for_sky_command_port(): 261 if not self._wait_for_sky_command_port():
212 logging.error('Failed to start sky') 262 logging.error('Failed to start sky')
(...skipping 10 matching lines...) Expand all
223 return 273 return
224 logging.info('Killing %s (%s).' % (name, pid)) 274 logging.info('Killing %s (%s).' % (name, pid))
225 try: 275 try:
226 os.kill(pid, signal.SIGTERM) 276 os.kill(pid, signal.SIGTERM)
227 except OSError: 277 except OSError:
228 logging.info('%s (%s) already gone.' % (name, pid)) 278 logging.info('%s (%s) already gone.' % (name, pid))
229 279
230 def stop_command(self, args): 280 def stop_command(self, args):
231 # TODO(eseidel): mojo_shell crashes when attempting graceful shutdown. 281 # TODO(eseidel): mojo_shell crashes when attempting graceful shutdown.
232 # self._send_command_to_sky('/quit') 282 # self._send_command_to_sky('/quit')
233 self._kill_if_exists('mojo_shell_pid', 'mojo_shell')
234 283
235 self._kill_if_exists('sky_server_pid', 'sky_server') 284 self._kill_if_exists('sky_server_pid', 'sky_server')
285
236 # We could be much more surgical here: 286 # We could be much more surgical here:
237 if 'remote_sky_server_port' in self.pids: 287 if 'remote_sky_server_port' in self.pids:
238 device = android_commands.AndroidCommands( 288 device = android_commands.AndroidCommands(
239 self.pids['device_serial']) 289 self.pids['device_serial'])
240 forwarder.Forwarder.UnmapAllDevicePorts(device) 290 forwarder.Forwarder.UnmapAllDevicePorts(device)
241 291
242 if 'remote_sky_command_port' in self.pids: 292 if 'remote_sky_command_port' in self.pids:
243 # adb forward --remove takes the *host* port, not the remote port. 293 # adb forward --remove takes the *host* port, not the remote port.
244 port_string = 'tcp:%s' % self.pids['sky_command_port'] 294 port_string = 'tcp:%s' % self.pids['sky_command_port']
245 subprocess.call(['adb', 'forward', '--remove', port_string]) 295 subprocess.call(['adb', 'forward', '--remove', port_string])
246 296
247 subprocess.call([ 297 subprocess.call([
248 'adb', 'shell', 'am', 'force-stop', ANDROID_PACKAGE]) 298 'adb', 'shell', 'am', 'force-stop', ANDROID_PACKAGE])
299 else:
300 # Only try to kill mojo_shell if it's running locally.
301 self._kill_if_exists('mojo_shell_pid', 'mojo_shell')
249 302
250 if 'remote_gdbserver_port' in self.pids: 303 if 'remote_gdbserver_port' in self.pids:
304 self._kill_if_exists('adb_shell_gdbserver_pid',
305 'adb shell gdbserver')
306
251 port_string = 'tcp:%s' % self.pids['remote_gdbserver_port'] 307 port_string = 'tcp:%s' % self.pids['remote_gdbserver_port']
252 subprocess.call(['adb', 'forward', '--remove', port_string]) 308 subprocess.call(['adb', 'forward', '--remove', port_string])
253 309
310 self._kill_if_exists('mojo_cache_linker_pid', 'mojo cache linker')
311
254 def load_command(self, args): 312 def load_command(self, args):
255 if not urlparse.urlparse(args.url_or_path).scheme: 313 if not urlparse.urlparse(args.url_or_path).scheme:
256 # The load happens on the remote device, use the remote port. 314 # The load happens on the remote device, use the remote port.
257 remote_sky_server_port = self.pids.get('remote_sky_server_port', 315 remote_sky_server_port = self.pids.get('remote_sky_server_port',
258 self.pids['sky_server_port']) 316 self.pids['sky_server_port'])
259 url = SkyServer.url_for_path(remote_sky_server_port, 317 url = SkyServer.url_for_path(remote_sky_server_port,
260 self.pids['sky_server_root'], args.url_or_path) 318 self.pids['sky_server_root'], args.url_or_path)
261 else: 319 else:
262 url = args.url_or_path 320 url = args.url_or_path
263 self._send_command_to_sky('/load', url) 321 self._send_command_to_sky('/load', url)
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after
316 'AndroidHandler', 374 'AndroidHandler',
317 'MojoMain', 375 'MojoMain',
318 'MojoShellActivity', 376 'MojoShellActivity',
319 'MojoShellApplication', 377 'MojoShellApplication',
320 'chromium', 378 'chromium',
321 ] 379 ]
322 subprocess.call(['adb', 'logcat', '-d', '-s'] + TAGS) 380 subprocess.call(['adb', 'logcat', '-d', '-s'] + TAGS)
323 381
324 def gdb_attach_command(self, args): 382 def gdb_attach_command(self, args):
325 self.paths = self._create_paths_for_build_dir(self.pids['build_dir']) 383 self.paths = self._create_paths_for_build_dir(self.pids['build_dir'])
384
385 self._kill_if_exists('mojo_cache_linker_pid', 'mojo cache linker')
386
387 links_path = '/tmp/mojo_cache_links'
388 if not os.path.exists(links_path):
389 os.makedirs(links_path)
390 shell_link_path = os.path.join(links_path, 'libmojo_shell.so')
391 if os.path.lexists(shell_link_path):
392 os.unlink(shell_link_path)
393 os.symlink(self.paths.mojo_shell_path, shell_link_path)
394
395 logcat_cmd = ['adb', 'logcat']
396 logcat = subprocess.Popen(logcat_cmd, stdout=subprocess.PIPE)
397
398 mojo_cache_linker_path = os.path.join(
399 self.paths.sky_tools_directory, 'mojo_cache_linker.py')
400 cache_linker_cmd = [
401 mojo_cache_linker_path,
402 links_path,
403 self.pids['build_dir'],
404 'http://localhost:%s' % self.pids['remote_sky_server_port']
405 ]
406 self.pids['mojo_cache_linker_pid'] = \
407 subprocess.Popen(cache_linker_cmd, stdin=logcat.stdout).pid
408
409 # Write out our pid file before we exec ourselves.
410 self._write_pid_file(PID_FILE_PATH, self.pids)
411
412 # TODO(eseidel): Need to sync down system libraries into a directory.
413 symbol_search_paths = [
414 links_path,
415 self.pids['build_dir'],
416 ]
417 # TODO(eseidel): We need to look up the toolchain somehow?
418 gdb_path = os.path.join(SRC_ROOT, 'third_party/android_tools/ndk/'
419 'toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/'
420 'bin/arm-linux-androideabi-gdb')
326 gdb_command = [ 421 gdb_command = [
327 '/usr/bin/gdb', self.paths.mojo_shell_path, 422 gdb_path,
328 '--eval-command', 'target remote localhost:%s' % GDB_PORT 423 '--eval-command', 'file %s' % self.paths.mojo_shell_path,
424 '--eval-command', 'directory %s' % self.paths.src_root,
425 '--eval-command', 'target remote localhost:%s' % GDB_PORT,
426 '--eval-command', 'set solib-search-path %s' %
427 ':'.join(symbol_search_paths),
329 ] 428 ]
330 print " ".join(gdb_command) 429 print " ".join(gdb_command)
331 # We don't want python listenting for signals or anything, so exec 430 # We don't want python listenting for signals or anything, so exec
332 # gdb and let it take the entire process. 431 # gdb and let it take the entire process.
333 os.execv(gdb_command[0], gdb_command) 432 os.execv(gdb_command[0], gdb_command)
334 433
335 def print_crash_command(self, args): 434 def print_crash_command(self, args):
336 logcat_cmd = ['adb', 'logcat', '-d'] 435 logcat_cmd = ['adb', 'logcat', '-d']
337 logcat = subprocess.Popen(logcat_cmd, stdout=subprocess.PIPE) 436 logcat = subprocess.Popen(logcat_cmd, stdout=subprocess.PIPE)
338 437
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after
394 load_parser.set_defaults(func=self.load_command) 493 load_parser.set_defaults(func=self.load_command)
395 494
396 args = parser.parse_args() 495 args = parser.parse_args()
397 args.func(args) 496 args.func(args)
398 497
399 self._write_pid_file(PID_FILE_PATH, self.pids) 498 self._write_pid_file(PID_FILE_PATH, self.pids)
400 499
401 500
402 if __name__ == '__main__': 501 if __name__ == '__main__':
403 SkyDebugger().main() 502 SkyDebugger().main()
OLDNEW
« sky/tools/mojo_cache_linker.py ('K') | « sky/tools/mojo_cache_linker.py ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698