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

Side by Side Diff: mojo/tools/mopy/android.py

Issue 1152663002: Makes android.py support gdb (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: review feedback and symbols Created 5 years, 7 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
« no previous file with comments | « mandoline/tools/android_run_mandoline.py ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # Copyright 2014 The Chromium Authors. All rights reserved. 1 # Copyright 2014 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 import atexit 5 import atexit
6 import datetime 6 import datetime
7 import email.utils 7 import email.utils
8 import hashlib 8 import hashlib
9 import itertools 9 import itertools
10 import json 10 import json
11 import logging 11 import logging
12 import math 12 import math
13 import os 13 import os
14 import os.path 14 import os.path
15 import random 15 import random
16 import shutil
17 import signal
16 import subprocess 18 import subprocess
17 import sys 19 import sys
20 import tempfile
18 import threading 21 import threading
19 import time 22 import time
20 import urlparse 23 import urlparse
21 24
22 import SimpleHTTPServer 25 import SimpleHTTPServer
23 import SocketServer 26 import SocketServer
24 27
25 28
26 # Tags used by the mojo shell application logs. 29 # Tags used by the mojo shell application logs.
27 LOGCAT_TAGS = [ 30 LOGCAT_TAGS = [
(...skipping 150 matching lines...) Expand 10 before | Expand all | Expand 10 after
178 181
179 182
180 class AndroidShell(object): 183 class AndroidShell(object):
181 """ Allows to set up and run a given mojo shell binary on an Android device. 184 """ Allows to set up and run a given mojo shell binary on an Android device.
182 185
183 Args: 186 Args:
184 shell_apk_path: path to the shell Android binary 187 shell_apk_path: path to the shell Android binary
185 local_dir: directory where locally build Mojo apps will be served, optional 188 local_dir: directory where locally build Mojo apps will be served, optional
186 adb_path: path to adb, optional if adb is in PATH 189 adb_path: path to adb, optional if adb is in PATH
187 target_device: device to run on, if multiple devices are connected 190 target_device: device to run on, if multiple devices are connected
191 src_root: root of the source tree
188 """ 192 """
189 def __init__( 193 def __init__(
190 self, shell_apk_path, local_dir=None, adb_path="adb", target_device=None, 194 self, shell_apk_path, local_dir=None, adb_path="adb", target_device=None,
191 target_package=MOJO_SHELL_PACKAGE_NAME): 195 target_package=MOJO_SHELL_PACKAGE_NAME, src_root=None):
192 self.shell_apk_path = shell_apk_path 196 self.shell_apk_path = shell_apk_path
197 self.src_root = src_root
193 self.adb_path = adb_path 198 self.adb_path = adb_path
194 self.local_dir = local_dir 199 self.local_dir = local_dir
195 self.target_device = target_device 200 self.target_device = target_device
196 self.target_package = target_package 201 self.target_package = target_package
197 202
198 def _CreateADBCommand(self, args): 203 def _CreateADBCommand(self, args):
199 adb_command = [self.adb_path] 204 adb_command = [self.adb_path]
200 if self.target_device: 205 if self.target_device:
201 adb_command.extend(['-s', self.target_device]) 206 adb_command.extend(['-s', self.target_device])
202 adb_command.extend(args) 207 adb_command.extend(args)
(...skipping 100 matching lines...) Expand 10 before | Expand all | Expand 10 after
303 return [] 308 return []
304 309
305 original_values = list(itertools.chain( 310 original_values = list(itertools.chain(
306 *map(lambda x: x[len(MAPPING_PREFIX):].split(','), map_parameters))) 311 *map(lambda x: x[len(MAPPING_PREFIX):].split(','), map_parameters)))
307 sorted(original_values) 312 sorted(original_values)
308 result = [] 313 result = []
309 for value in original_values: 314 for value in original_values:
310 result.append(self._StartHttpServerForOriginMapping(value, 0)) 315 result.append(self._StartHttpServerForOriginMapping(value, 0))
311 return [MAPPING_PREFIX + ','.join(result)] 316 return [MAPPING_PREFIX + ','.join(result)]
312 317
313 def PrepareShellRun(self, origin=None): 318 def PrepareShellRun(self, origin=None, install=True, gdb=False):
314 """ Prepares for StartShell: runs adb as root and installs the apk. If the 319 """ Prepares for StartShell: runs adb as root and installs the apk. If the
315 origin specified is 'localhost', a local http server will be set up to serve 320 origin specified is 'localhost', a local http server will be set up to serve
316 files from the build directory along with port forwarding. 321 files from the build directory along with port forwarding.
317 322
318 Returns arguments that should be appended to shell argument list.""" 323 Returns arguments that should be appended to shell argument list."""
319 # TODO(msw): Remove logging after devices are found; http://crbug.com/486220 324 # TODO(msw): Remove logging after devices are found; http://crbug.com/486220
320 logging.getLogger().debug("Path to adb: %s", self.adb_path) 325 logging.getLogger().debug("Path to adb: %s", self.adb_path)
321 logging.getLogger().debug("adb devices: %s", 326 logging.getLogger().debug("adb devices: %s",
322 subprocess.check_output(self._CreateADBCommand(['devices']))) 327 subprocess.check_output(self._CreateADBCommand(['devices'])))
323 328
324 subprocess.check_call(self._CreateADBCommand(['root'])) 329 subprocess.check_call(self._CreateADBCommand(['root']))
325 subprocess.check_call( 330 if install:
326 self._CreateADBCommand(['install', '-r', self.shell_apk_path, '-i', 331 subprocess.check_call(
327 self.target_package])) 332 self._CreateADBCommand(['install', '-r', self.shell_apk_path, '-i',
333 self.target_package]))
334
328 atexit.register(self.StopShell) 335 atexit.register(self.StopShell)
329 336
330 extra_args = [] 337 extra_args = []
331 if origin is 'localhost': 338 if origin is 'localhost':
332 origin = self._StartHttpServerForDirectory(self.local_dir, 0) 339 origin = self._StartHttpServerForDirectory(self.local_dir, 0)
333 if origin: 340 if origin:
334 extra_args.append("--origin=" + origin) 341 extra_args.append("--origin=" + origin)
342
343 if gdb:
344 # Remote debugging needs a port forwarded.
345 subprocess.check_call(self._CreateADBCommand(['forward', 'tcp:5039',
346 'tcp:5039']))
347
335 return extra_args 348 return extra_args
336 349
350 def _GetProcessId(self, process):
351 """Returns the process id of the process on the remote device."""
352 while True:
353 line = process.stdout.readline()
354 pid_command = 'launcher waiting for GDB. pid: '
355 index = line.find(pid_command)
356 if index != -1:
357 return line[index + len(pid_command):].strip()
358 return 0
359
360 def _GetLocalGdbPath(self):
361 """Returns the path to the android gdb."""
362 return os.path.join(self.src_root, "third_party", "android_tools", "ndk",
363 "toolchains", "arm-linux-androideabi-4.9", "prebuilt",
364 "linux-x86_64", "bin", "arm-linux-androideabi-gdb")
365
366 def _WaitForProcessIdAndStartGdb(self, process):
367 """Waits until we see the process id from the remote device, starts up
368 gdbserver on the remote device, and gdb on the local device."""
369 # Wait until we see "PID"
370 pid = self._GetProcessId(process)
371 assert pid != 0
372 # No longer need the logcat process.
373 process.kill()
374 # Disable python's processing of SIGINT while running gdb. Otherwise
375 # control-c doesn't work well in gdb.
376 signal.signal(signal.SIGINT, signal.SIG_IGN)
377 gdbserver_process = subprocess.Popen(self._CreateADBCommand(['shell',
378 'gdbserver',
379 '--attach',
380 ':5039',
381 pid]))
382 atexit.register(_ExitIfNeeded, gdbserver_process)
383
384 temp_dir = tempfile.mkdtemp()
385 atexit.register(shutil.rmtree, temp_dir, True)
386
387 gdbinit_path = os.path.join(temp_dir, 'gdbinit')
388 _CreateGdbInit(temp_dir, gdbinit_path, self.local_dir)
389
390 _CreateSOLinks(temp_dir, self.local_dir)
391
392 # Wait a second for gdb to start up on the device. Without this the local
393 # gdb starts before the remote side has registered the port.
394 # TODO(sky): maybe we should try a couple of times and then give up?
395 time.sleep(1)
396
397 local_gdb_process = subprocess.Popen([self._GetLocalGdbPath(),
398 "-x",
399 gdbinit_path],
400 cwd=temp_dir)
401 atexit.register(_ExitIfNeeded, local_gdb_process)
402 local_gdb_process.wait()
403 signal.signal(signal.SIGINT, signal.SIG_DFL)
404
337 def StartShell(self, 405 def StartShell(self,
338 arguments, 406 arguments,
339 stdout=None, 407 stdout=None,
340 on_application_stop=None): 408 on_application_stop=None,
409 gdb=False):
341 """ 410 """
342 Starts the mojo shell, passing it the given arguments. 411 Starts the mojo shell, passing it the given arguments.
343 412
344 The |arguments| list must contain the "--origin=" arg from PrepareShellRun. 413 The |arguments| list must contain the "--origin=" arg from PrepareShellRun.
345 If |stdout| is not None, it should be a valid argument for subprocess.Popen. 414 If |stdout| is not None, it should be a valid argument for subprocess.Popen.
346 """ 415 """
416
347 STDOUT_PIPE = "/data/data/%s/stdout.fifo" % self.target_package 417 STDOUT_PIPE = "/data/data/%s/stdout.fifo" % self.target_package
348 418
349 cmd = self._CreateADBCommand([ 419 cmd = self._CreateADBCommand([
350 'shell', 420 'shell',
351 'am', 421 'am',
352 'start', 422 'start',
353 '-S', 423 '-S',
354 '-a', 'android.intent.action.VIEW', 424 '-a', 'android.intent.action.VIEW',
355 '-n', '%s/%s.MojoShellActivity' % (self.target_package, 425 '-n', '%s/%s.MojoShellActivity' % (self.target_package,
356 MOJO_SHELL_PACKAGE_NAME)]) 426 MOJO_SHELL_PACKAGE_NAME)])
357 427
428 logcat_process = None
429
430 if gdb:
431 arguments += ['--wait-for-debugger']
432 logcat_process = self.ShowLogs(stdout=subprocess.PIPE)
433
358 parameters = [] 434 parameters = []
359 if stdout or on_application_stop: 435 if stdout or on_application_stop:
360 subprocess.check_call(self._CreateADBCommand( 436 subprocess.check_call(self._CreateADBCommand(
361 ['shell', 'rm', '-f', STDOUT_PIPE])) 437 ['shell', 'rm', '-f', STDOUT_PIPE]))
362 parameters.append('--fifo-path=%s' % STDOUT_PIPE) 438 parameters.append('--fifo-path=%s' % STDOUT_PIPE)
363 max_attempts = 5 439 max_attempts = 5
364 if '--wait-for-debugger' in arguments: 440 if '--wait-for-debugger' in arguments:
365 max_attempts = 200 441 max_attempts = 200
366 self._ReadFifo(STDOUT_PIPE, stdout, on_application_stop, 442 self._ReadFifo(STDOUT_PIPE, stdout, on_application_stop,
367 max_attempts=max_attempts) 443 max_attempts=max_attempts)
368 444
369 # Extract map-origin arguments. 445 # Extract map-origin arguments.
370 map_parameters, other_parameters = _Split(arguments, _IsMapOrigin) 446 map_parameters, other_parameters = _Split(arguments, _IsMapOrigin)
371 parameters += other_parameters 447 parameters += other_parameters
372 parameters += self._StartHttpServerForOriginMappings(map_parameters) 448 parameters += self._StartHttpServerForOriginMappings(map_parameters)
373 449
374 if parameters: 450 if parameters:
375 encodedParameters = json.dumps(parameters) 451 encodedParameters = json.dumps(parameters)
376 cmd += ['--es', 'encodedParameters', encodedParameters] 452 cmd += ['--es', 'encodedParameters', encodedParameters]
377 453
378 with open(os.devnull, 'w') as devnull: 454 with open(os.devnull, 'w') as devnull:
379 subprocess.Popen(cmd, stdout=devnull).wait() 455 cmd_process = subprocess.Popen(cmd, stdout=devnull)
456 if logcat_process:
457 self._WaitForProcessIdAndStartGdb(logcat_process)
458 cmd_process.wait()
380 459
381 def StopShell(self): 460 def StopShell(self):
382 """ 461 """
383 Stops the mojo shell. 462 Stops the mojo shell.
384 """ 463 """
385 subprocess.check_call(self._CreateADBCommand(['shell', 464 subprocess.check_call(self._CreateADBCommand(['shell',
386 'am', 465 'am',
387 'force-stop', 466 'force-stop',
388 self.target_package])) 467 self.target_package]))
389 468
390 def CleanLogs(self): 469 def CleanLogs(self):
391 """ 470 """
392 Cleans the logs on the device. 471 Cleans the logs on the device.
393 """ 472 """
394 subprocess.check_call(self._CreateADBCommand(['logcat', '-c'])) 473 subprocess.check_call(self._CreateADBCommand(['logcat', '-c']))
395 474
396 def ShowLogs(self): 475 def ShowLogs(self, stdout=sys.stdout):
397 """ 476 """
398 Displays the log for the mojo shell. 477 Displays the log for the mojo shell.
399 478
400 Returns the process responsible for reading the logs. 479 Returns the process responsible for reading the logs.
401 """ 480 """
402 logcat = subprocess.Popen(self._CreateADBCommand([ 481 logcat = subprocess.Popen(self._CreateADBCommand([
403 'logcat', 482 'logcat',
404 '-s', 483 '-s',
405 ' '.join(LOGCAT_TAGS)]), 484 ' '.join(LOGCAT_TAGS)]),
406 stdout=sys.stdout) 485 stdout=stdout)
407 atexit.register(_ExitIfNeeded, logcat) 486 atexit.register(_ExitIfNeeded, logcat)
408 return logcat 487 return logcat
488
489
490 def _CreateGdbInit(tmp_dir, gdb_init_path, build_dir):
491 """
492 Creates the gdbinit file.
493 Args:
494 tmp_dir: the directory where the gdbinit and other files lives.
495 gdb_init_path: path to gdbinit
496 build_dir: path where build files are located.
497 """
498 gdbinit = ('target remote localhost:5039\n'
499 'def reload-symbols\n'
500 ' set solib-search-path %s:%s\n'
501 'end\n'
502 'def info-symbols\n'
503 ' info sharedlibrary\n'
504 'end\n'
505 'reload-symbols\n'
506 'echo \\n\\n'
507 'You are now in gdb and need to type continue (or c) to continue '
508 'execution.\\n'
509 'gdb is in the directory %s\\n'
510 'The following functions have been defined:\\n'
511 'reload-symbols: forces reloading symbols. If after a crash you\\n'
512 'still do not see symbols you likely need to create a link in\\n'
513 'the directory you are in.\\n'
514 'info-symbols: shows status of current shared libraries.\\n'
515 'NOTE: you may need to type reload-symbols again after a '
516 'crash.\\n\\n' % (tmp_dir, build_dir, tmp_dir))
517 with open(gdb_init_path, 'w') as f:
518 f.write(gdbinit)
519
520
521 def _CreateSOLinks(dest_dir, build_dir):
522 """
523 Creates links from files (such as mojo files) to the real .so so that gdb can
524 find them.
525 """
526 # The files to create links for. The key is the name as seen on the device,
527 # and the target an array of path elements as to where the .so lives (relative
528 # to the output directory).
529 # TODO(sky): come up with some way to automate this.
530 files_to_link = {
531 'html_viewer.mojo': ['libhtml_viewer', 'html_viewer_library.so'],
532 'libmandoline_runner.so': ['mandoline_runner'],
533 }
534 for android_name, so_path in files_to_link.iteritems():
535 src = os.path.join(build_dir, *so_path)
536 if not os.path.isfile(src):
537 print 'Expected file not found', src
538 sys.exit(-1)
539 os.symlink(src, os.path.join(dest_dir, android_name))
OLDNEW
« no previous file with comments | « mandoline/tools/android_run_mandoline.py ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698