flutter_flutter/tools/android_library_cacher.py
Eric Seidel 97ae8b44bd Add very basic support for symboled debugging on android
This only barely works.  We pull down system libraries
once before we attach.  The libraries we pull from
the device do not have debug symbols, but have enough
that we have decent callstacks.  We launch a background
process to repeatedly update a cache directory with
symlinks into our build directory corresponding to the cache
names used on the device, however gdb doesn't watch
the solib-search-path directories to notice the links
as we add them.

Better solutions would be to add support for pulling
down full android symboled system images and using those
instead of pulling libraries off the device as well as
figure out how to get android binaries to support
build-id so that we can present a directory of build-id
associated libraries to gdb on boot and have it to build-id
based lookups of libraries instead of our current broken
watch-logs-and-add-symlinks approach.

If you know what you're doing with this you can actually
make debugging work on the device.  It's not particularly
user friendly yet, but we'll work on it.

I added a build_dir member to skypy.paths.Paths
as a temporary measure before we move off of skypy's
paths and onto mopy's paths.  This helped eliminate
problems with using a relative path in args.build_dir
as is common.

R=abarth@chromium.org, ojan@chromium.org, qsr@chromium.org
BUG=

Review URL: https://codereview.chromium.org/855663003
2015-01-16 13:39:20 -08:00

62 lines
2.2 KiB
Python
Executable File

#!/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 re
import sys
import os
import logging
import subprocess
# TODO(eseidel): This should be shared with adb_gdb
def main():
logging.basicConfig(level=logging.INFO)
parser = argparse.ArgumentParser(
description='Pull all libraries used by a pid on android into a cache.')
parser.add_argument('cache_root', type=str)
parser.add_argument('pid', type=int)
args = parser.parse_args()
if not os.path.exists(args.cache_root):
os.makedirs(args.cache_root)
subprocess.check_call(['adb', 'root'])
# TODO(eseidel): Check the build.props, or find some way to avoid
# re-pulling every library every time. adb_gdb has code to do this
# but doesn't seem to notice when the set of needed libraries changed.
library_regexp = re.compile(r'(?P<library_path>/system/.*\.so)')
cat_maps_cmd = ['adb', 'shell', 'cat', '/proc/%s/maps' % args.pid]
maps_lines = subprocess.check_output(cat_maps_cmd).strip().split('\n')
# adb shell doesn't return the return code from the shell?
if not maps_lines or 'No such file or directory' in maps_lines[0]:
print 'Failed to get maps for pid %s on device.' % args.pid
sys.exit(1)
def library_from_line(line):
result = library_regexp.search(line)
if not result:
return None
return result.group('library_path')
dev_null = open(os.devnull, 'w') # Leaking.
to_pull = set(filter(None, map(library_from_line, maps_lines)))
to_pull.add('/system/bin/linker') # Unclear why but adb_gdb pulls this too.
for library_path in sorted(to_pull):
# Not using os.path.join since library_path is absolute.
dest_file = os.path.normpath("%s/%s" % (args.cache_root, library_path))
dest_dir = os.path.dirname(dest_file)
if not os.path.exists(dest_dir):
os.makedirs(dest_dir)
print '%s -> %s' % (library_path, dest_file)
pull_cmd = ['adb', 'pull', library_path, dest_file]
subprocess.check_call(pull_cmd, stderr=dev_null)
if __name__ == '__main__':
main()