mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Make --gdb work for android
This mostly works. I haven't yet set up pulling down the system binaries from the device to the host so that symbols appear correctly, but I'll do that in the next patch. One of the crazy things this patch adds it a script to watch for loads on adb logcat and set up mappings from the cache library names to the symboled binaries in the out directory. Presumably other scripts may want to share this functionality so I've made it its own script. Better would be to have mojo_shell spit out a file including the cache mapping information and we could watch that file instead of logcat, but this works for now. R=qsr@chromium.org BUG= Review URL: https://codereview.chromium.org/848013004
This commit is contained in:
parent
0545f369f0
commit
74a45026ac
64
tools/mojo_cache_linker.py
Executable file
64
tools/mojo_cache_linker.py
Executable file
@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright 2015 The Chromium Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
# TODO(eseidel): This should be shared with tools/android_stack_parser/stack
|
||||
# TODO(eseidel): This could be replaced by using build-ids on Android
|
||||
# TODO(eseidel): mojo_shell should write out a cache mapping file.
|
||||
def main():
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Watches mojo_shell logcat output and builds a directory '
|
||||
'of symlinks to symboled binaries for seen cache names.')
|
||||
parser.add_argument('links_dir', type=str)
|
||||
parser.add_argument('symbols_dir', type=str)
|
||||
parser.add_argument('base_url', type=str)
|
||||
args = parser.parse_args()
|
||||
|
||||
regex = re.compile('Caching mojo app (?P<url>\S+) at (?P<path>\S+)')
|
||||
|
||||
if not os.path.isdir(args.links_dir):
|
||||
logging.fatal('links_dir: %s is not a directory' % args.links_dir)
|
||||
sys.exit(1)
|
||||
|
||||
for line in sys.stdin:
|
||||
result = regex.search(line)
|
||||
if not result:
|
||||
continue
|
||||
|
||||
url = result.group('url')
|
||||
if not url.startswith(args.base_url):
|
||||
logging.debug('%s does not match base %s' % (url, args.base_url))
|
||||
continue
|
||||
full_name = os.path.basename(url)
|
||||
name, ext = os.path.splitext(full_name)
|
||||
if ext != '.mojo':
|
||||
logging.debug('%s is not a .mojo library' % url)
|
||||
continue
|
||||
|
||||
symboled_name = 'lib%s_library.so' % name
|
||||
cache_link_path = os.path.join(args.links_dir,
|
||||
os.path.basename(result.group('path')))
|
||||
symboled_path = os.path.realpath(
|
||||
os.path.join(args.symbols_dir, symboled_name))
|
||||
if not os.path.isfile(symboled_path):
|
||||
logging.warn('symboled path %s does not exist' % symboled_path)
|
||||
continue
|
||||
|
||||
print "%s -> %s" % (cache_link_path, symboled_path)
|
||||
|
||||
if os.path.lexists(cache_link_path):
|
||||
logging.debug('link already exists %s, replacing' % symboled_path)
|
||||
os.unlink(cache_link_path)
|
||||
|
||||
os.symlink(symboled_path, cache_link_path)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
143
tools/skydb
143
tools/skydb
@ -9,6 +9,7 @@ import json
|
||||
import logging
|
||||
import os
|
||||
import pipes
|
||||
import re
|
||||
import requests
|
||||
import signal
|
||||
import skypy.paths
|
||||
@ -40,6 +41,7 @@ DEFAULT_URL = "https://raw.githubusercontent.com/domokit/mojo/master/sky/example
|
||||
ANDROID_PACKAGE = "org.chromium.mojo.shell"
|
||||
ANDROID_ACTIVITY = "%s/.MojoShellActivity" % ANDROID_PACKAGE
|
||||
|
||||
|
||||
# FIXME: Move this into mopy.config
|
||||
def gn_args_from_build_dir(build_dir):
|
||||
gn_cmd = [
|
||||
@ -97,7 +99,7 @@ class SkyDebugger(object):
|
||||
'--esa', 'parameters', ','.join(escaped_args),
|
||||
]
|
||||
|
||||
def _build_mojo_shell_command(self, args):
|
||||
def _build_mojo_shell_command(self, args, is_android):
|
||||
content_handlers = ['%s,%s' % (mime_type, 'mojo:sky_viewer')
|
||||
for mime_type in SUPPORTED_MIME_TYPES]
|
||||
|
||||
@ -111,6 +113,14 @@ class SkyDebugger(object):
|
||||
'mojo:window_manager',
|
||||
]
|
||||
|
||||
# Desktop-only work-around for mojo crashing under chromoting.
|
||||
if not is_android and args.use_osmesa:
|
||||
shell_args.append(
|
||||
'--args-for=mojo:native_viewport_service --use-osmesa')
|
||||
|
||||
if is_android and args.gdb:
|
||||
shell_args.append('--wait_for_debugger')
|
||||
|
||||
if 'remote_sky_server_port' in self.pids:
|
||||
shell_command = self._wrap_for_android(shell_args)
|
||||
else:
|
||||
@ -139,6 +149,20 @@ class SkyDebugger(object):
|
||||
root_relative_build_dir = os.path.relpath(abs_build_dir, SRC_ROOT)
|
||||
return skypy.paths.Paths(root_relative_build_dir)
|
||||
|
||||
def _find_remote_pid_for_package(self, package):
|
||||
ps_output = subprocess.check_output(['adb', 'shell', 'ps'])
|
||||
for line in ps_output.split('\n'):
|
||||
fields = line.split()
|
||||
if fields and fields[-1] == package:
|
||||
return fields[1]
|
||||
return None
|
||||
|
||||
def _find_install_location_for_package(self, package):
|
||||
pm_command = ['adb', 'shell', 'pm', 'path', package]
|
||||
pm_output = subprocess.check_output(pm_command)
|
||||
# e.g. package:/data/app/org.chromium.mojo.shell-1/base.apk
|
||||
return pm_output.split(':')[-1]
|
||||
|
||||
def start_command(self, args):
|
||||
# FIXME: Lame that we use self for a command-specific variable.
|
||||
self.paths = self._create_paths_for_build_dir(args.build_dir)
|
||||
@ -181,26 +205,52 @@ class SkyDebugger(object):
|
||||
])
|
||||
self.pids['remote_sky_command_port'] = args.command_port
|
||||
|
||||
shell_command = self._build_mojo_shell_command(args)
|
||||
shell_command = self._build_mojo_shell_command(args, is_android)
|
||||
|
||||
if not is_android:
|
||||
# Desktop-only work-around for mojo crashing under chromoting.
|
||||
if args.use_osmesa:
|
||||
shell_command.append(
|
||||
'--args-for=mojo:native_viewport_service --use-osmesa')
|
||||
|
||||
# On android we can't launch inside gdb, but rather have to attach.
|
||||
if args.gdb:
|
||||
shell_command = ['gdbserver', ':%s' % GDB_PORT] + shell_command
|
||||
# On android we can't launch inside gdb, but rather have to attach.
|
||||
if not is_android and args.gdb:
|
||||
shell_command = ['gdbserver', ':%d' % GDB_PORT] + shell_command
|
||||
|
||||
print ' '.join(map(pipes.quote, shell_command))
|
||||
self.pids['mojo_shell_pid'] = subprocess.Popen(shell_command).pid
|
||||
# This pid is meaningless on android (it's the adb shell pid)
|
||||
start_command_pid = subprocess.Popen(shell_command).pid
|
||||
|
||||
if is_android:
|
||||
# TODO(eseidel): am start -W does not seem to work?
|
||||
pid_tries = 0
|
||||
while True:
|
||||
pid = self._find_remote_pid_for_package(ANDROID_PACKAGE)
|
||||
if pid or pid_tries > 3:
|
||||
break
|
||||
logging.debug('No pid for %s yet, waiting' % ANDROID_PACKAGE)
|
||||
time.sleep(5)
|
||||
pid_tries += 1
|
||||
|
||||
if not pid:
|
||||
logging.error('Failed to find mojo_shell pid on device!')
|
||||
return
|
||||
self.pids['mojo_shell_pid'] = pid
|
||||
else:
|
||||
self.pids['mojo_shell_pid'] = start_command_pid
|
||||
|
||||
if args.gdb and is_android:
|
||||
gdbserver_cmd = ['gdbserver', '--attach', ':%s' % GDB_PORT]
|
||||
self.pids['remote_gdbserver_pid'] = subprocess.Popen(shell_command).pid
|
||||
# We push our own copy of gdbserver with the package since
|
||||
# the default gdbserver is a different version from our gdb.
|
||||
package_path = \
|
||||
self._find_install_location_for_package(ANDROID_PACKAGE)
|
||||
gdb_server_path = os.path.join(
|
||||
os.path.dirname(package_path), 'lib/arm/gdbserver')
|
||||
gdbserver_cmd = [
|
||||
'adb', 'shell',
|
||||
gdb_server_path, '--attach',
|
||||
':%d' % GDB_PORT,
|
||||
str(self.pids['mojo_shell_pid'])
|
||||
]
|
||||
print ' '.join(map(pipes.quote, gdbserver_cmd))
|
||||
self.pids['adb_shell_gdbserver_pid'] = \
|
||||
subprocess.Popen(gdbserver_cmd).pid
|
||||
|
||||
port_string = 'tcp:%s' % GDB_PORT
|
||||
port_string = 'tcp:%d' % GDB_PORT
|
||||
subprocess.check_call([
|
||||
'adb', 'forward', port_string, port_string
|
||||
])
|
||||
@ -220,18 +270,18 @@ class SkyDebugger(object):
|
||||
if not pid:
|
||||
logging.info('No pid for %s, nothing to do.' % name)
|
||||
return
|
||||
logging.info('Killing %s (%s).' % (name, pid))
|
||||
logging.info('Killing %s (%d).' % (name, pid))
|
||||
try:
|
||||
os.kill(pid, signal.SIGTERM)
|
||||
except OSError:
|
||||
logging.info('%s (%s) already gone.' % (name, pid))
|
||||
logging.info('%s (%d) already gone.' % (name, pid))
|
||||
|
||||
def stop_command(self, args):
|
||||
# TODO(eseidel): mojo_shell crashes when attempting graceful shutdown.
|
||||
# self._send_command_to_sky('/quit')
|
||||
self._kill_if_exists('mojo_shell_pid', 'mojo_shell')
|
||||
|
||||
self._kill_if_exists('sky_server_pid', 'sky_server')
|
||||
|
||||
# We could be much more surgical here:
|
||||
if 'remote_sky_server_port' in self.pids:
|
||||
device = android_commands.AndroidCommands(
|
||||
@ -245,12 +295,20 @@ class SkyDebugger(object):
|
||||
|
||||
subprocess.call([
|
||||
'adb', 'shell', 'am', 'force-stop', ANDROID_PACKAGE])
|
||||
else:
|
||||
# Only try to kill mojo_shell if it's running locally.
|
||||
self._kill_if_exists('mojo_shell_pid', 'mojo_shell')
|
||||
|
||||
if 'remote_gdbserver_port' in self.pids:
|
||||
self._kill_if_exists('adb_shell_gdbserver_pid',
|
||||
'adb shell gdbserver')
|
||||
|
||||
port_string = 'tcp:%s' % self.pids['remote_gdbserver_port']
|
||||
subprocess.call(['adb', 'forward', '--remove', port_string])
|
||||
self.pids = {} # Clear out our pid file.
|
||||
|
||||
self._kill_if_exists('mojo_cache_linker_pid', 'mojo cache linker')
|
||||
|
||||
def load_command(self, args):
|
||||
if not urlparse.urlparse(args.url_or_path).scheme:
|
||||
# The load happens on the remote device, use the remote port.
|
||||
@ -323,12 +381,53 @@ class SkyDebugger(object):
|
||||
|
||||
def gdb_attach_command(self, args):
|
||||
self.paths = self._create_paths_for_build_dir(self.pids['build_dir'])
|
||||
|
||||
self._kill_if_exists('mojo_cache_linker_pid', 'mojo cache linker')
|
||||
|
||||
links_path = '/tmp/mojo_cache_links'
|
||||
if not os.path.exists(links_path):
|
||||
os.makedirs(links_path)
|
||||
shell_link_path = os.path.join(links_path, 'libmojo_shell.so')
|
||||
if os.path.lexists(shell_link_path):
|
||||
os.unlink(shell_link_path)
|
||||
os.symlink(self.paths.mojo_shell_path, shell_link_path)
|
||||
|
||||
logcat_cmd = ['adb', 'logcat']
|
||||
logcat = subprocess.Popen(logcat_cmd, stdout=subprocess.PIPE)
|
||||
|
||||
mojo_cache_linker_path = os.path.join(
|
||||
self.paths.sky_tools_directory, 'mojo_cache_linker.py')
|
||||
cache_linker_cmd = [
|
||||
mojo_cache_linker_path,
|
||||
links_path,
|
||||
self.pids['build_dir'],
|
||||
'http://localhost:%s' % self.pids['remote_sky_server_port']
|
||||
]
|
||||
self.pids['mojo_cache_linker_pid'] = \
|
||||
subprocess.Popen(cache_linker_cmd, stdin=logcat.stdout).pid
|
||||
|
||||
# Write out our pid file before we exec ourselves.
|
||||
self._write_pid_file(PID_FILE_PATH, self.pids)
|
||||
|
||||
# TODO(eseidel): Need to sync down system libraries into a directory.
|
||||
symbol_search_paths = [
|
||||
links_path,
|
||||
self.pids['build_dir'],
|
||||
]
|
||||
# TODO(eseidel): We need to look up the toolchain somehow?
|
||||
gdb_path = os.path.join(SRC_ROOT, 'third_party/android_tools/ndk/'
|
||||
'toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/'
|
||||
'bin/arm-linux-androideabi-gdb')
|
||||
gdb_command = [
|
||||
'/usr/bin/gdb', self.paths.mojo_shell_path,
|
||||
'--eval-command', 'target remote localhost:%s' % GDB_PORT
|
||||
gdb_path,
|
||||
'--eval-command', 'file %s' % self.paths.mojo_shell_path,
|
||||
'--eval-command', 'directory %s' % self.paths.src_root,
|
||||
'--eval-command', 'target remote localhost:%s' % GDB_PORT,
|
||||
'--eval-command', 'set solib-search-path %s' %
|
||||
':'.join(symbol_search_paths),
|
||||
]
|
||||
print " ".join(gdb_command)
|
||||
# We don't want python listenting for signals or anything, so exec
|
||||
# We don't want python listening for signals or anything, so exec
|
||||
# gdb and let it take the entire process.
|
||||
os.execv(gdb_command[0], gdb_command)
|
||||
|
||||
@ -343,7 +442,7 @@ class SkyDebugger(object):
|
||||
stack.wait()
|
||||
|
||||
def main(self):
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logging.basicConfig(level=logging.WARNING)
|
||||
logging.getLogger("requests").setLevel(logging.WARNING)
|
||||
|
||||
self.pids = self._load_pid_file(PID_FILE_PATH)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user