mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
* Find the arm64 local engine build if the attached device is arm64 * Overwrite any previously copied gdbserver binary even if the binary is read only * Stop any currently running gdbserver process for the package being debugged
228 lines
8.3 KiB
Python
Executable File
228 lines
8.3 KiB
Python
Executable File
#!/usr/bin/env python
|
|
# Copyright 2013 The Flutter 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 os
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
|
|
"""Tool for starting a GDB client and server to debug a Flutter engine process on an Android device.
|
|
|
|
Usage:
|
|
flutter_gdb server com.example.package_name
|
|
flutter_gdb client com.example.package_name
|
|
|
|
The Android package must be marked as debuggable in its manifest.
|
|
|
|
The "client" command will copy system libraries from the device to the host
|
|
in order to provide debug symbols. If this has already been done on a
|
|
previous run for a given device, then you can skip this step by passing
|
|
--no-pull-libs.
|
|
"""
|
|
|
|
ADB_LOCAL_PATH = 'third_party/android_tools/sdk/platform-tools/adb'
|
|
|
|
|
|
def _get_flutter_root():
|
|
path = os.path.dirname(os.path.abspath(__file__))
|
|
while os.path.basename(path) != 'src':
|
|
path = os.path.dirname(path)
|
|
return path
|
|
|
|
|
|
def _find_package_pid(adb_path, package):
|
|
"""Find the pid of the Flutter application process."""
|
|
ps_output = subprocess.check_output([adb_path, 'shell', 'ps'])
|
|
ps_match = re.search('^\S+\s+(\d+).*\s%s' % package, ps_output, re.MULTILINE)
|
|
if not ps_match:
|
|
print 'Unable to find pid for package %s on device' % package
|
|
return None
|
|
return int(ps_match.group(1))
|
|
|
|
|
|
def _get_device_abi(adb_path):
|
|
abi_output = subprocess.check_output(
|
|
[adb_path, 'shell', 'getprop', 'ro.product.cpu.abi']).strip()
|
|
if abi_output.startswith('arm64'):
|
|
return 'arm64'
|
|
if abi_output.startswith('arm'):
|
|
return 'arm'
|
|
return abi_output
|
|
|
|
|
|
def _default_local_engine(abi):
|
|
"""Return the default Flutter build output directory for a given target ABI."""
|
|
if abi == 'x86':
|
|
return 'android_debug_unopt_x86'
|
|
elif abi == 'x86_64':
|
|
return 'android_debug_unopt_x64'
|
|
elif abi == 'arm64':
|
|
return 'android_debug_unopt_arm64'
|
|
else:
|
|
return 'android_debug_unopt'
|
|
|
|
|
|
class GdbClient(object):
|
|
SYSTEM_LIBS_PATH = '/tmp/flutter_gdb_device_libs'
|
|
|
|
def _gdb_local_path(self):
|
|
GDB_LOCAL_PATH = ('third_party/android_tools/ndk/prebuilt/%s-x86_64/bin/gdb-orig')
|
|
if sys.platform.startswith('darwin'):
|
|
return GDB_LOCAL_PATH % 'darwin'
|
|
else:
|
|
return GDB_LOCAL_PATH % 'linux'
|
|
|
|
def add_subparser(self, subparsers):
|
|
parser = subparsers.add_parser('client',
|
|
help='run a GDB client')
|
|
parser.add_argument('package', type=str)
|
|
parser.add_argument('--local-engine', type=str)
|
|
parser.add_argument('--gdb-port', type=int, default=8888)
|
|
parser.add_argument('--no-pull-libs', action="store_false",
|
|
default=True, dest="pull_libs",
|
|
help="Do not copy system libraries from the device to the host")
|
|
parser.add_argument('--old-sysroot', action="store_true", default=False,
|
|
help='Create a sysroot tree suitable for debugging on older (pre-N) versions of Android')
|
|
parser.set_defaults(func=self.run)
|
|
|
|
def _copy_system_libs(self, adb_path, package, old_sysroot):
|
|
"""Copy libraries used by the Flutter process from the device to the host."""
|
|
package_pid = _find_package_pid(adb_path, package)
|
|
if package_pid is None:
|
|
return False
|
|
|
|
# Find library files that are mapped into the process.
|
|
proc_maps = subprocess.check_output(
|
|
[adb_path, 'shell', 'run-as', package, 'cat', '/proc/%d/maps' % package_pid])
|
|
proc_libs = re.findall('(/system/.*\.(?:so|oat))\s*$', proc_maps, re.MULTILINE)
|
|
|
|
if old_sysroot:
|
|
device_libs = set((lib, os.path.basename(lib)) for lib in proc_libs)
|
|
device_libs.add(('/system/bin/linker', 'linker'))
|
|
else:
|
|
device_libs = set((lib, lib[1:]) for lib in proc_libs)
|
|
device_libs.add(('/system/bin/linker', 'system/bin/linker'))
|
|
device_libs.add(('/system/bin/app_process32', 'system/bin/app_process32'))
|
|
device_libs.add(('/system/bin/app_process64', 'system/bin/app_process64'))
|
|
|
|
if os.path.isdir(GdbClient.SYSTEM_LIBS_PATH):
|
|
shutil.rmtree(GdbClient.SYSTEM_LIBS_PATH)
|
|
|
|
dev_null = open(os.devnull, 'w')
|
|
for lib, local_path in sorted(device_libs):
|
|
print 'Copying %s' % lib
|
|
local_path = os.path.join(GdbClient.SYSTEM_LIBS_PATH, local_path)
|
|
if not os.path.exists(os.path.dirname(local_path)):
|
|
os.makedirs(os.path.dirname(local_path))
|
|
subprocess.call([adb_path, 'pull', lib, local_path], stderr=dev_null)
|
|
|
|
return True
|
|
|
|
def run(self, args):
|
|
flutter_root = _get_flutter_root()
|
|
if args.adb is None:
|
|
adb_path = os.path.join(flutter_root, ADB_LOCAL_PATH)
|
|
else:
|
|
adb_path = args.adb
|
|
|
|
if args.pull_libs:
|
|
if not self._copy_system_libs(adb_path, args.package, args.old_sysroot):
|
|
return 1
|
|
|
|
subprocess.check_call(
|
|
[adb_path, 'forward', 'tcp:%d' % args.gdb_port, 'tcp:%d' % args.gdb_port])
|
|
|
|
if args.local_engine is None:
|
|
abi = _get_device_abi(adb_path)
|
|
local_engine = _default_local_engine(abi)
|
|
else:
|
|
local_engine = args.local_engine
|
|
|
|
debug_out_path = os.path.join(flutter_root, 'out/%s' % local_engine)
|
|
if not os.path.exists(os.path.join(debug_out_path, 'libflutter.so')):
|
|
print 'Unable to find libflutter.so. Make sure you have completed a %s build' % local_engine
|
|
return 1
|
|
|
|
eval_commands = []
|
|
if not args.old_sysroot:
|
|
eval_commands.append('set sysroot %s' % GdbClient.SYSTEM_LIBS_PATH)
|
|
eval_commands.append('set solib-search-path %s:%s' %
|
|
(debug_out_path, GdbClient.SYSTEM_LIBS_PATH))
|
|
eval_commands.append('target remote localhost:%d' % args.gdb_port)
|
|
|
|
exec_command = [os.path.join(flutter_root, self._gdb_local_path())]
|
|
for command in eval_commands:
|
|
exec_command += ['--eval-command', command]
|
|
|
|
os.execv(exec_command[0], exec_command)
|
|
|
|
|
|
class GdbServer(object):
|
|
GDB_SERVER_DEVICE_TMP_PATH = '/data/local/tmp/gdbserver'
|
|
|
|
def add_subparser(self, subparsers):
|
|
parser = subparsers.add_parser('server',
|
|
help='run a GDB server on the device')
|
|
parser.add_argument('package', type=str)
|
|
parser.add_argument('--gdb-port', type=int, default=8888)
|
|
parser.set_defaults(func=self.run)
|
|
|
|
def run(self, args):
|
|
flutter_root = _get_flutter_root()
|
|
if args.adb is None:
|
|
adb_path = os.path.join(flutter_root, ADB_LOCAL_PATH)
|
|
else:
|
|
adb_path = args.adb
|
|
|
|
package_pid = _find_package_pid(adb_path, args.package)
|
|
if package_pid is None:
|
|
return 1
|
|
|
|
abi = _get_device_abi(adb_path)
|
|
gdb_server_local_path = 'third_party/android_tools/ndk/prebuilt/android-%s/gdbserver/gdbserver' % abi
|
|
|
|
# Copy gdbserver to the package's data directory.
|
|
subprocess.check_call([adb_path, 'push',
|
|
os.path.join(flutter_root, gdb_server_local_path),
|
|
GdbServer.GDB_SERVER_DEVICE_TMP_PATH])
|
|
gdb_server_device_path = '/data/data/%s/gdbserver' % args.package
|
|
subprocess.check_call([adb_path, 'shell', 'run-as', args.package, 'cp', '-F',
|
|
GdbServer.GDB_SERVER_DEVICE_TMP_PATH,
|
|
gdb_server_device_path])
|
|
|
|
|
|
subprocess.call([adb_path, 'shell', 'run-as', args.package,
|
|
'killall', 'gdbserver'])
|
|
|
|
# Run gdbserver.
|
|
try:
|
|
subprocess.call([adb_path, 'shell', 'run-as', args.package,
|
|
gdb_server_device_path,
|
|
'--attach', ':%d' % args.gdb_port, str(package_pid)])
|
|
except KeyboardInterrupt:
|
|
pass
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description='Flutter debugger tool')
|
|
subparsers = parser.add_subparsers(help='sub-command help')
|
|
parser.add_argument('--adb', type=str, help='path to ADB tool')
|
|
|
|
commands = [
|
|
GdbClient(),
|
|
GdbServer(),
|
|
]
|
|
for command in commands:
|
|
command.add_subparser(subparsers)
|
|
|
|
args = parser.parse_args()
|
|
return args.func(args)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main())
|