diff options
author | Josh Gao <jmgao@google.com> | 2015-09-22 11:43:08 -0700 |
---|---|---|
committer | Josh Gao <jmgao@google.com> | 2015-10-01 14:30:29 -0700 |
commit | 043bad746f7401bd16c0e254242714bd4e8cfec6 (patch) | |
tree | 8b785ee35c3b85f1857461bb499dcaedf37ef17a /scripts | |
parent | 07006ce756c6bf8cff745ca85dafb89ebcba37f6 (diff) | |
download | android_development-043bad746f7401bd16c0e254242714bd4e8cfec6.tar.gz android_development-043bad746f7401bd16c0e254242714bd4e8cfec6.tar.bz2 android_development-043bad746f7401bd16c0e254242714bd4e8cfec6.zip |
Add python reimplementation of gdbclient.
Bug: http://b/23715403
Bug: http://b/22946322
Bug: http://b/22946705
Bug: http://b/22120411
Bug: http://b/22715953
Change-Id: I2391ec03be8d2487a4738455b6e934874c7f595f
Diffstat (limited to 'scripts')
-rwxr-xr-x | scripts/gdbclient.py | 227 |
1 files changed, 227 insertions, 0 deletions
diff --git a/scripts/gdbclient.py b/scripts/gdbclient.py new file mode 100755 index 000000000..4b7de2d01 --- /dev/null +++ b/scripts/gdbclient.py @@ -0,0 +1,227 @@ +#!/usr/bin/env python +# +# Copyright (C) 2015 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import adb +import argparse +import logging +import os +import subprocess +import sys + +# Shared functions across gdbclient.py and ndk-gdb.py. +import gdbrunner + +def get_gdbserver_path(root, arch): + path = "{}/prebuilts/misc/android-{}/gdbserver{}/gdbserver{}" + if arch.endswith("64"): + return path.format(root, arch, "64", "64") + else: + return path.format(root, arch, "", "") + + +def parse_args(): + parser = gdbrunner.ArgumentParser() + + group = parser.add_argument_group(title="attach target") + group = group.add_mutually_exclusive_group(required=True) + group.add_argument( + "-p", dest="target_pid", metavar="PID", type=int, + help="attach to a process with specified PID") + group.add_argument( + "-n", dest="target_name", metavar="NAME", + help="attach to a process with specified name") + group.add_argument( + "-r", dest="run_cmd", metavar="CMD", nargs=argparse.REMAINDER, + help="run a binary on the device, with args") + + parser.add_argument( + "--port", nargs="?", default="5039", + help="override the port used on the host") + parser.add_argument( + "--user", nargs="?", default="root", + help="user to run commands as on the device [default: root]") + + return parser.parse_args() + + +def dump_var(root, variable): + make_args = ["make", "CALLED_FROM_SETUP=true", + "BUILD_SYSTEM={}/build/core".format(root), + "--no-print-directory", "-f", + "{}/build/core/config.mk".format(root), + "dumpvar-{}".format(variable)] + + make_output = subprocess.check_output(make_args) + return make_output.splitlines()[0] + + +def verify_device(root, props): + names = set([props["ro.build.product"], props["ro.product.device"]]) + target_device = dump_var(root, "TARGET_DEVICE") + if target_device not in names: + msg = "TARGET_DEVICE ({}) does not match attached device ({})" + sys.exit(msg.format(target_device, ", ".join(names))) + + +def get_remote_pid(device, process_name): + processes = gdbrunner.get_processes(device) + if process_name not in processes: + msg = "failed to find running process {}".format(process_name) + sys.exit(msg) + pids = processes[process_name] + if len(pids) > 1: + msg = "multiple processes match '{}': {}".format(process_name, pids) + sys.exit(msg) + + # Fetch the binary using the PID later. + return pids[0] + + +def ensure_linker(device, sysroot, is64bit): + local_path = os.path.join(sysroot, "system", "bin", "linker") + remote_path = "/system/bin/linker" + if is64bit: + local_path += "64" + remote_path += "64" + if not os.path.exists(local_path): + device.pull(remote_path, local_path) + + +def handle_switches(args): + """Fetch the targeted binary and determine how to attach gdb. + + Args: + args: Parsed arguments. + sysroot: Local sysroot path. + + Returns: + (binary_file, attach_pid, run_cmd). + Precisely one of attach_pid or run_cmd will be None. + """ + + device = args.device + binary_file = None + pid = None + run_cmd = None + + if args.target_pid: + # Fetch the binary using the PID later. + pid = args.target_pid + elif args.target_name: + # Fetch the binary using the PID later. + pid = get_remote_pid(device, args.target_name) + elif args.run_cmd: + if not args.run_cmd[0]: + sys.exit("empty command passed to -r") + if not args.run_cmd[0].startswith("/"): + sys.exit("commands passed to -r must use absolute paths") + run_cmd = args.run_cmd + binary_file = gdbrunner.pull_file(device, run_cmd[0], user=args.user) + if binary_file is None: + assert pid is not None + try: + binary_file = gdbrunner.pull_binary(device, pid=pid, user=args.user) + except adb.ShellError: + sys.exit("failed to pull binary for PID {}".format(pid)) + + return (binary_file, pid, run_cmd) + +def generate_gdb_script(sysroot, binary_file, is64bit, port): + # Generate a gdb script. + # TODO: Detect the zygote and run 'art-on' automatically. + root = os.environ["ANDROID_BUILD_TOP"] + symbols_dir = os.path.join(sysroot, "system", "lib64" if is64bit else "lib") + vendor_dir = os.path.join(sysroot, "vendor", "lib64" if is64bit else "lib") + + solib_search_path = [] + symbols_paths = ["", "hw", "ssl/engines", "drm", "egl", "soundfx"] + vendor_paths = ["", "hw", "egl"] + solib_search_path += [os.path.join(symbols_dir, x) for x in symbols_paths] + solib_search_path += [os.path.join(vendor_dir, x) for x in vendor_paths] + solib_search_path = ":".join(solib_search_path) + + gdb_commands = "" + gdb_commands += "file '{}'\n".format(binary_file.name) + gdb_commands += "set solib-absolute-prefix {}\n".format(sysroot) + gdb_commands += "set solib-search-path {}\n".format(solib_search_path) + + dalvik_gdb_script = os.path.join(root, "development", "scripts", "gdb", + "dalvik.gdb") + if not os.path.exists(dalvik_gdb_script): + logging.warning(("couldn't find {} - ART debugging options will not " + + "be available").format(dalvik_gdb_script)) + else: + gdb_commands += "source {}\n".format(dalvik_gdb_script) + + gdb_commands += "target remote :{}\n".format(port) + return gdb_commands + + +def main(): + args = parse_args() + device = args.device + props = device.get_props() + + root = os.environ["ANDROID_BUILD_TOP"] + sysroot = dump_var(root, "abs-TARGET_OUT_UNSTRIPPED") + + # Make sure the environment matches the attached device. + verify_device(root, props) + + debug_socket = "/data/local/tmp/debug_socket" + pid = None + run_cmd = None + + # Fetch binary for -p, -n. + binary_file, pid, run_cmd = handle_switches(args) + + with binary_file: + arch = gdbrunner.get_binary_arch(binary_file) + is64bit = arch.endswith("64") + + # Make sure we have the linker + ensure_linker(device, sysroot, is64bit) + + # Start gdbserver. + gdbserver_local_path = get_gdbserver_path(root, arch) + gdbserver_remote_path = "/data/local/tmp/{}-gdbserver".format(arch) + gdbrunner.start_gdbserver( + device, gdbserver_local_path, gdbserver_remote_path, + target_pid=pid, run_cmd=run_cmd, debug_socket=debug_socket, + port=args.port, user=args.user) + + # Generate a gdb script. + gdb_commands = generate_gdb_script(sysroot=sysroot, + binary_file=binary_file, + is64bit=is64bit, + port=args.port) + + # Find where gdb is + if sys.platform.startswith("linux"): + platform_name = "linux-x86" + elif sys.platform.startswith("darwin"): + platform_name = "darwin-x86" + else: + sys.exit("Unknown platform: {}".format(sys.platform)) + gdb_path = os.path.join(root, "prebuilts", "gdb", platform_name, "bin", + "gdb") + + # Start gdb. + gdbrunner.start_gdb(gdb_path, gdb_commands) + +if __name__ == "__main__": + main() |