diff options
-rwxr-xr-x | mandoline/tools/android_run_mandoline.py | 13 | ||||
-rw-r--r-- | mojo/tools/mopy/android.py | 149 |
2 files changed, 150 insertions, 12 deletions
diff --git a/mandoline/tools/android_run_mandoline.py b/mandoline/tools/android_run_mandoline.py index a68f3e6..f0481a3 100755 --- a/mandoline/tools/android_run_mandoline.py +++ b/mandoline/tools/android_run_mandoline.py @@ -30,6 +30,11 @@ def main(): parser.add_argument('--target-cpu', help='CPU architecture to run for.', choices=['x64', 'x86', 'arm'], default='arm') parser.add_argument('--target-device', help='Device to run on.') + parser.add_argument('--dont-install', + help='Disables installing the apk', + default=False, action='store_true') + parser.add_argument('--gdb', help='Run gdb', + default=False, action='store_true') launcher_args, args = parser.parse_known_args() config = Config(target_os=Config.OS_ANDROID, @@ -39,14 +44,16 @@ def main(): paths = Paths(config) shell = AndroidShell(paths.apk_path, paths.build_dir, paths.adb_path, launcher_args.target_device, - target_package='org.chromium.mandoline') + target_package='org.chromium.mandoline', + src_root=paths.src_root) - extra_shell_args = shell.PrepareShellRun() + extra_shell_args = shell.PrepareShellRun( + install=not launcher_args.dont_install, gdb=launcher_args.gdb) args.extend(extra_shell_args) shell.CleanLogs() p = shell.ShowLogs() - shell.StartShell(args, sys.stdout, p.terminate) + shell.StartShell(args, sys.stdout, p.terminate, launcher_args.gdb) return 0 diff --git a/mojo/tools/mopy/android.py b/mojo/tools/mopy/android.py index 74499b2..1bf66df 100644 --- a/mojo/tools/mopy/android.py +++ b/mojo/tools/mopy/android.py @@ -13,8 +13,11 @@ import math import os import os.path import random +import shutil +import signal import subprocess import sys +import tempfile import threading import time import urlparse @@ -185,11 +188,13 @@ class AndroidShell(object): local_dir: directory where locally build Mojo apps will be served, optional adb_path: path to adb, optional if adb is in PATH target_device: device to run on, if multiple devices are connected + src_root: root of the source tree """ def __init__( self, shell_apk_path, local_dir=None, adb_path="adb", target_device=None, - target_package=MOJO_SHELL_PACKAGE_NAME): + target_package=MOJO_SHELL_PACKAGE_NAME, src_root=None): self.shell_apk_path = shell_apk_path + self.src_root = src_root self.adb_path = adb_path self.local_dir = local_dir self.target_device = target_device @@ -310,7 +315,7 @@ class AndroidShell(object): result.append(self._StartHttpServerForOriginMapping(value, 0)) return [MAPPING_PREFIX + ','.join(result)] - def PrepareShellRun(self, origin=None): + def PrepareShellRun(self, origin=None, install=True, gdb=False): """ Prepares for StartShell: runs adb as root and installs the apk. If the origin specified is 'localhost', a local http server will be set up to serve files from the build directory along with port forwarding. @@ -322,9 +327,11 @@ class AndroidShell(object): subprocess.check_output(self._CreateADBCommand(['devices']))) subprocess.check_call(self._CreateADBCommand(['root'])) - subprocess.check_call( - self._CreateADBCommand(['install', '-r', self.shell_apk_path, '-i', - self.target_package])) + if install: + subprocess.check_call( + self._CreateADBCommand(['install', '-r', self.shell_apk_path, '-i', + self.target_package])) + atexit.register(self.StopShell) extra_args = [] @@ -332,18 +339,81 @@ class AndroidShell(object): origin = self._StartHttpServerForDirectory(self.local_dir, 0) if origin: extra_args.append("--origin=" + origin) + + if gdb: + # Remote debugging needs a port forwarded. + subprocess.check_call(self._CreateADBCommand(['forward', 'tcp:5039', + 'tcp:5039'])) + return extra_args + def _GetProcessId(self, process): + """Returns the process id of the process on the remote device.""" + while True: + line = process.stdout.readline() + pid_command = 'launcher waiting for GDB. pid: ' + index = line.find(pid_command) + if index != -1: + return line[index + len(pid_command):].strip() + return 0 + + def _GetLocalGdbPath(self): + """Returns the path to the android gdb.""" + return os.path.join(self.src_root, "third_party", "android_tools", "ndk", + "toolchains", "arm-linux-androideabi-4.9", "prebuilt", + "linux-x86_64", "bin", "arm-linux-androideabi-gdb") + + def _WaitForProcessIdAndStartGdb(self, process): + """Waits until we see the process id from the remote device, starts up + gdbserver on the remote device, and gdb on the local device.""" + # Wait until we see "PID" + pid = self._GetProcessId(process) + assert pid != 0 + # No longer need the logcat process. + process.kill() + # Disable python's processing of SIGINT while running gdb. Otherwise + # control-c doesn't work well in gdb. + signal.signal(signal.SIGINT, signal.SIG_IGN) + gdbserver_process = subprocess.Popen(self._CreateADBCommand(['shell', + 'gdbserver', + '--attach', + ':5039', + pid])) + atexit.register(_ExitIfNeeded, gdbserver_process) + + temp_dir = tempfile.mkdtemp() + atexit.register(shutil.rmtree, temp_dir, True) + + gdbinit_path = os.path.join(temp_dir, 'gdbinit') + _CreateGdbInit(temp_dir, gdbinit_path, self.local_dir) + + _CreateSOLinks(temp_dir, self.local_dir) + + # Wait a second for gdb to start up on the device. Without this the local + # gdb starts before the remote side has registered the port. + # TODO(sky): maybe we should try a couple of times and then give up? + time.sleep(1) + + local_gdb_process = subprocess.Popen([self._GetLocalGdbPath(), + "-x", + gdbinit_path], + cwd=temp_dir) + atexit.register(_ExitIfNeeded, local_gdb_process) + local_gdb_process.wait() + signal.signal(signal.SIGINT, signal.SIG_DFL) + def StartShell(self, arguments, stdout=None, - on_application_stop=None): + on_application_stop=None, + gdb=False): """ Starts the mojo shell, passing it the given arguments. The |arguments| list must contain the "--origin=" arg from PrepareShellRun. If |stdout| is not None, it should be a valid argument for subprocess.Popen. """ + STDOUT_PIPE = "/data/data/%s/stdout.fifo" % self.target_package cmd = self._CreateADBCommand([ @@ -355,6 +425,12 @@ class AndroidShell(object): '-n', '%s/%s.MojoShellActivity' % (self.target_package, MOJO_SHELL_PACKAGE_NAME)]) + logcat_process = None + + if gdb: + arguments += ['--wait-for-debugger'] + logcat_process = self.ShowLogs(stdout=subprocess.PIPE) + parameters = [] if stdout or on_application_stop: subprocess.check_call(self._CreateADBCommand( @@ -376,7 +452,10 @@ class AndroidShell(object): cmd += ['--es', 'encodedParameters', encodedParameters] with open(os.devnull, 'w') as devnull: - subprocess.Popen(cmd, stdout=devnull).wait() + cmd_process = subprocess.Popen(cmd, stdout=devnull) + if logcat_process: + self._WaitForProcessIdAndStartGdb(logcat_process) + cmd_process.wait() def StopShell(self): """ @@ -393,7 +472,7 @@ class AndroidShell(object): """ subprocess.check_call(self._CreateADBCommand(['logcat', '-c'])) - def ShowLogs(self): + def ShowLogs(self, stdout=sys.stdout): """ Displays the log for the mojo shell. @@ -403,6 +482,58 @@ class AndroidShell(object): 'logcat', '-s', ' '.join(LOGCAT_TAGS)]), - stdout=sys.stdout) + stdout=stdout) atexit.register(_ExitIfNeeded, logcat) return logcat + + +def _CreateGdbInit(tmp_dir, gdb_init_path, build_dir): + """ + Creates the gdbinit file. + Args: + tmp_dir: the directory where the gdbinit and other files lives. + gdb_init_path: path to gdbinit + build_dir: path where build files are located. + """ + gdbinit = ('target remote localhost:5039\n' + 'def reload-symbols\n' + ' set solib-search-path %s:%s\n' + 'end\n' + 'def info-symbols\n' + ' info sharedlibrary\n' + 'end\n' + 'reload-symbols\n' + 'echo \\n\\n' + 'You are now in gdb and need to type continue (or c) to continue ' + 'execution.\\n' + 'gdb is in the directory %s\\n' + 'The following functions have been defined:\\n' + 'reload-symbols: forces reloading symbols. If after a crash you\\n' + 'still do not see symbols you likely need to create a link in\\n' + 'the directory you are in.\\n' + 'info-symbols: shows status of current shared libraries.\\n' + 'NOTE: you may need to type reload-symbols again after a ' + 'crash.\\n\\n' % (tmp_dir, build_dir, tmp_dir)) + with open(gdb_init_path, 'w') as f: + f.write(gdbinit) + + +def _CreateSOLinks(dest_dir, build_dir): + """ + Creates links from files (such as mojo files) to the real .so so that gdb can + find them. + """ + # The files to create links for. The key is the name as seen on the device, + # and the target an array of path elements as to where the .so lives (relative + # to the output directory). + # TODO(sky): come up with some way to automate this. + files_to_link = { + 'html_viewer.mojo': ['libhtml_viewer', 'html_viewer_library.so'], + 'libmandoline_runner.so': ['mandoline_runner'], + } + for android_name, so_path in files_to_link.iteritems(): + src = os.path.join(build_dir, *so_path) + if not os.path.isfile(src): + print 'Expected file not found', src + sys.exit(-1) + os.symlink(src, os.path.join(dest_dir, android_name)) |