diff options
Diffstat (limited to 'ndkgdb.py')
-rwxr-xr-x | ndkgdb.py | 946 |
1 files changed, 946 insertions, 0 deletions
diff --git a/ndkgdb.py b/ndkgdb.py new file mode 100755 index 000000000..cd10b8cae --- /dev/null +++ b/ndkgdb.py @@ -0,0 +1,946 @@ +#!/usr/bin/env python3 +# +# 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. +# + +from __future__ import print_function + +import argparse +import contextlib +import logging +import operator +import os +import posixpath +import signal +import subprocess +import sys +import time +import xml.etree.cElementTree as ElementTree + +import adb +import gdbrunner + + +def log(msg): + logger = logging.getLogger(__name__) + logger.info(msg) + + +def enable_verbose_logging(): + logger = logging.getLogger(__name__) + handler = logging.StreamHandler(sys.stdout) + formatter = logging.Formatter() + + handler.setFormatter(formatter) + logger.addHandler(handler) + logger.propagate = False + + logger.setLevel(logging.INFO) + + +def error(msg): + sys.exit("ERROR: {}".format(msg)) + + +class ArgumentParser(gdbrunner.ArgumentParser): + def __init__(self): + super(ArgumentParser, self).__init__() + self.add_argument( + "--verbose", "-v", action="store_true", help="enable verbose mode" + ) + + self.add_argument( + "--force", + "-f", + action="store_true", + help="kill existing debug session if it exists", + ) + + self.add_argument( + "--port", + type=int, + nargs="?", + default="5039", + help="override the port used on the host.", + ) + + self.add_argument( + "--delay", + type=float, + default=0.25, + help="delay in seconds to wait after starting activity.\n" + "defaults to 0.25, higher values may be needed on slower devices.", + ) + + self.add_argument( + "-p", "--project", dest="project", help="specify application project path" + ) + + lldb_group = self.add_mutually_exclusive_group() + lldb_group.add_argument("--lldb", action="store_true", help="Use lldb.") + lldb_group.add_argument( + "--no-lldb", action="store_true", help="Do not use lldb." + ) + + app_group = self.add_argument_group("target selection") + start_group = app_group.add_mutually_exclusive_group() + + start_group.add_argument( + "--attach", + nargs="?", + dest="package_name", + metavar="PKG_NAME", + help="attach to application (default)\n" + "autodetects PKG_NAME if not specified", + ) + + # NB: args.launch can be False (--attach), None (--launch), or a string + start_group.add_argument( + "--launch", + nargs="?", + dest="launch", + default=False, + metavar="ACTIVITY", + help="launch application activity\n" + "launches main activity if ACTIVITY not specified", + ) + + start_group.add_argument( + "--launch-list", + action="store_true", + help="list all launchable activity names from manifest", + ) + + debug_group = self.add_argument_group("debugging options") + debug_group.add_argument( + "-x", + "--exec", + dest="exec_file", + help="execute gdb commands in EXEC_FILE after connection", + ) + + debug_group.add_argument( + "--nowait", + action="store_true", + help="do not wait for debugger to attach (may miss early JNI " + "breakpoints)", + ) + + if sys.platform.startswith("win"): + tui_help = argparse.SUPPRESS + else: + tui_help = "use GDB's tui mode" + + debug_group.add_argument( + "-t", "--tui", action="store_true", dest="tui", help=tui_help + ) + + +def extract_package_name(xmlroot): + if "package" in xmlroot.attrib: + return xmlroot.attrib["package"] + error("Failed to find package name in AndroidManifest.xml") + + +ANDROID_XMLNS = "{http://schemas.android.com/apk/res/android}" + + +def extract_launchable(xmlroot): + """ + A given application can have several activities, and each activity + can have several intent filters. We want to only list, in the final + output, the activities which have a intent-filter that contains the + following elements: + + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + """ + launchable_activities = [] + application = xmlroot.findall("application")[0] + + main_action = "android.intent.action.MAIN" + launcher_category = "android.intent.category.LAUNCHER" + name_attrib = "{}name".format(ANDROID_XMLNS) + + for activity in application.iter("activity"): + if name_attrib not in activity.attrib: + continue + + for intent_filter in activity.iter("intent-filter"): + found_action = False + found_category = False + for child in intent_filter: + if child.tag == "action": + if not found_action and name_attrib in child.attrib: + if child.attrib[name_attrib] == main_action: + found_action = True + if child.tag == "category": + if not found_category and name_attrib in child.attrib: + if child.attrib[name_attrib] == launcher_category: + found_category = True + if found_action and found_category: + launchable_activities.append(activity.attrib[name_attrib]) + return launchable_activities + + +def ndk_bin_path(): + return os.path.dirname(os.path.realpath(__file__)) + + +def handle_args(): + def find_program(program, paths): + """Find a binary in paths""" + exts = [""] + if sys.platform.startswith("win"): + exts += [".exe", ".bat", ".cmd"] + for path in paths: + if os.path.isdir(path): + for ext in exts: + full = path + os.sep + program + ext + if os.path.isfile(full): + return full + return None + + # FIXME: This is broken for PATH that contains quoted colons. + paths = os.environ["PATH"].replace('"', "").split(os.pathsep) + + args = ArgumentParser().parse_args() + + if args.tui and sys.platform.startswith("win"): + error("TUI is unsupported on Windows.") + + ndk_bin = ndk_bin_path() + args.make_cmd = find_program("make", [ndk_bin]) + args.jdb_cmd = find_program("jdb", paths) + if args.make_cmd is None: + error("Failed to find make in '{}'".format(ndk_bin)) + if args.jdb_cmd is None: + print("WARNING: Failed to find jdb on your path, defaulting to " "--nowait") + args.nowait = True + + if args.verbose: + enable_verbose_logging() + + return args + + +def find_project(args): + manifest_name = "AndroidManifest.xml" + if args.project is not None: + log("Using project directory: {}".format(args.project)) + args.project = os.path.realpath(os.path.expanduser(args.project)) + if not os.path.exists(os.path.join(args.project, manifest_name)): + msg = "could not find AndroidManifest.xml in '{}'" + error(msg.format(args.project)) + else: + # Walk upwards until we find AndroidManifest.xml, or run out of path. + current_dir = os.getcwd() + while not os.path.exists(os.path.join(current_dir, manifest_name)): + parent_dir = os.path.dirname(current_dir) + if parent_dir == current_dir: + error( + "Could not find AndroidManifest.xml in current" + " directory or a parent directory.\n" + " Launch this script from inside a project, or" + " use --project=<path>." + ) + current_dir = parent_dir + args.project = current_dir + log("Using project directory: {} ".format(args.project)) + args.manifest_path = os.path.join(args.project, manifest_name) + return args.project + + +def canonicalize_activity(package_name, activity_name): + if activity_name.startswith("."): + return "{}{}".format(package_name, activity_name) + return activity_name + + +def parse_manifest(args): + manifest = ElementTree.parse(args.manifest_path) + manifest_root = manifest.getroot() + package_name = extract_package_name(manifest_root) + log("Found package name: {}".format(package_name)) + + activities = extract_launchable(manifest_root) + activities = [canonicalize_activity(package_name, a) for a in activities] + + if args.launch_list: + print("Launchable activities: {}".format(", ".join(activities))) + sys.exit(0) + + args.activities = activities + args.package_name = package_name + + +def select_target(args): + assert args.launch != False + + if len(args.activities) == 0: + error("No launchable activities found.") + + if args.launch is None: + target = args.activities[0] + + if len(args.activities) > 1: + print( + "WARNING: Multiple launchable activities found, choosing" + " '{}'.".format(args.activities[0]) + ) + else: + activity_name = canonicalize_activity(args.package_name, args.launch) + + if activity_name not in args.activities: + msg = "Could not find launchable activity: '{}'." + error(msg.format(activity_name)) + target = activity_name + return target + + +@contextlib.contextmanager +def cd(path): + curdir = os.getcwd() + os.chdir(path) + os.environ["PWD"] = path + try: + yield + finally: + os.environ["PWD"] = curdir + os.chdir(curdir) + + +def dump_var(args, variable, abi=None): + make_args = [ + args.make_cmd, + "--no-print-dir", + "-f", + os.path.join(NDK_PATH, "build/core/build-local.mk"), + "-C", + args.project, + "DUMP_{}".format(variable), + ] + + if abi is not None: + make_args.append("APP_ABI={}".format(abi)) + + with cd(args.project): + try: + make_output = subprocess.check_output(make_args, cwd=args.project) + except subprocess.CalledProcessError: + error("Failed to retrieve application ABI from Android.mk.") + return make_output.splitlines()[-1].decode() + + +def get_api_level(device): + # Check the device API level + try: + api_level = int(device.get_prop("ro.build.version.sdk")) + except (TypeError, ValueError): + error( + "Failed to find target device's supported API level.\n" + "ndk-gdb only supports devices running Android 2.2 or higher." + ) + if api_level < 8: + error( + "ndk-gdb only supports devices running Android 2.2 or higher.\n" + "(expected API level 8, actual: {})".format(api_level) + ) + + return api_level + + +def fetch_abi(args): + """ + Figure out the intersection of which ABIs the application is built for and + which ones the device supports, then pick the one preferred by the device, + so that we know which gdbserver to push and run on the device. + """ + + app_abis = dump_var(args, "APP_ABI").split(" ") + if "all" in app_abis: + app_abis = dump_var(args, "NDK_ALL_ABIS").split(" ") + app_abis_msg = "Application ABIs: {}".format(", ".join(app_abis)) + log(app_abis_msg) + + new_abi_props = ["ro.product.cpu.abilist"] + old_abi_props = ["ro.product.cpu.abi", "ro.product.cpu.abi2"] + abi_props = new_abi_props + if args.device.get_prop("ro.product.cpu.abilist") is None: + abi_props = old_abi_props + + device_abis = [] + for key in abi_props: + value = args.device.get_prop(key) + if value is not None: + device_abis.extend(value.split(",")) + + device_abis_msg = "Device ABIs: {}".format(", ".join(device_abis)) + log(device_abis_msg) + + for abi in device_abis: + if abi in app_abis: + # TODO(jmgao): Do we expect gdb to work with ARM-x86 translation? + log("Selecting ABI: {}".format(abi)) + return abi + + msg = "Application cannot run on the selected device." + + # Don't repeat ourselves. + if not args.verbose: + msg += "\n{}\n{}".format(app_abis_msg, device_abis_msg) + + error(msg) + + +def get_run_as_cmd(user, cmd): + return ["run-as", user] + cmd + + +def get_app_data_dir(args, package_name): + cmd = ["/system/bin/sh", "-c", "pwd", "2>/dev/null"] + cmd = get_run_as_cmd(package_name, cmd) + (rc, stdout, _) = args.device.shell_nocheck(cmd) + if rc != 0: + error( + "Could not find application's data directory. Are you sure that " + "the application is installed and debuggable?" + ) + data_dir = stdout.strip() + + # Applications with minSdkVersion >= 24 will have their data directories + # created with rwx------ permissions, preventing adbd from forwarding to + # the gdbserver socket. To be safe, if we're on a device >= 24, always + # chmod the directory. + if get_api_level(args.device) >= 24: + chmod_cmd = ["/system/bin/chmod", "a+x", data_dir] + chmod_cmd = get_run_as_cmd(package_name, chmod_cmd) + (rc, _, _) = args.device.shell_nocheck(chmod_cmd) + if rc != 0: + error("Failed to make application data directory world executable") + + log("Found application data directory: {}".format(data_dir)) + return data_dir + + +def abi_to_arch(abi): + if abi.startswith("armeabi"): + return "arm" + elif abi == "arm64-v8a": + return "arm64" + else: + return abi + + +def abi_to_llvm_arch(abi): + if abi.startswith("armeabi"): + return "arm" + elif abi == "arm64-v8a": + return "aarch64" + elif abi == "x86": + return "i386" + else: + return "x86_64" + + +def get_llvm_host_name(): + platform = sys.platform + if platform.startswith("win"): + return "windows-x86_64" + elif platform.startswith("darwin"): + return "darwin-x86_64" + else: + return "linux-x86_64" + + +def get_python_executable(toolchain_path): + if sys.platform.startswith("win"): + return os.path.join(toolchain_path, "python3", "python.exe") + else: + return os.path.join(toolchain_path, "python3", "bin", "python3") + + +def get_lldb_path(toolchain_path): + for lldb_name in ["lldb.sh", "lldb.cmd", "lldb", "lldb.exe"]: + debugger_path = os.path.join(toolchain_path, "bin", lldb_name) + if os.path.isfile(debugger_path): + return debugger_path + return None + + +def get_llvm_package_version(llvm_toolchain_dir): + version_file_path = os.path.join(llvm_toolchain_dir, "AndroidVersion.txt") + try: + version_file = open(version_file_path, "r") + except IOError: + error( + "Failed to open llvm package version file: '{}'.".format(version_file_path) + ) + + with version_file: + return version_file.readline().strip() + + +def get_debugger_server_path( + args, package_name, app_data_dir, arch, server_name, local_path +): + app_debugger_server_path = "{}/lib/{}".format(app_data_dir, server_name) + cmd = ["ls", app_debugger_server_path, "2>/dev/null"] + cmd = get_run_as_cmd(package_name, cmd) + (rc, _, _) = args.device.shell_nocheck(cmd) + if rc == 0: + log("Found app {}: {}".format(server_name, app_debugger_server_path)) + return app_debugger_server_path + + # We need to upload our debugger server + log( + "App {} not found at {}, uploading.".format( + server_name, app_debugger_server_path + ) + ) + remote_path = "/data/local/tmp/{}-{}".format(arch, server_name) + args.device.push(local_path, remote_path) + + # Copy debugger server into the data directory on M+, because selinux prevents + # execution of binaries directly from /data/local/tmp. + if get_api_level(args.device) >= 23: + destination = "{}/{}-{}".format(app_data_dir, arch, server_name) + log("Copying {} to {}.".format(server_name, destination)) + cmd = [ + "cat", + remote_path, + "|", + "run-as", + package_name, + "sh", + "-c", + "'cat > {}'".format(destination), + ] + (rc, _, _) = args.device.shell_nocheck(cmd) + if rc != 0: + error("Failed to copy {} to {}.".format(server_name, destination)) + (rc, _, _) = args.device.shell_nocheck( + ["run-as", package_name, "chmod", "700", destination] + ) + if rc != 0: + error("Failed to chmod {} at {}.".format(server_name, destination)) + + remote_path = destination + + log("Uploaded {} to {}".format(server_name, remote_path)) + return remote_path + + +def pull_binaries(device, out_dir, app_64bit): + required_files = [] + libraries = ["libc.so", "libm.so", "libdl.so"] + + if app_64bit: + required_files = ["/system/bin/app_process64", "/system/bin/linker64"] + library_path = "/system/lib64" + else: + required_files = ["/system/bin/linker"] + library_path = "/system/lib" + + for library in libraries: + required_files.append(posixpath.join(library_path, library)) + + for required_file in required_files: + # os.path.join not used because joining absolute paths will pick the last one + local_path = os.path.realpath(out_dir + required_file) + local_dirname = os.path.dirname(local_path) + if not os.path.isdir(local_dirname): + os.makedirs(local_dirname) + log("Pulling '{}' to '{}'".format(required_file, local_path)) + device.pull(required_file, local_path) + + # /system/bin/app_process is 32-bit on 32-bit devices, but a symlink to + # app_process64 on 64-bit. If we need the 32-bit version, try to pull + # app_process32, and if that fails, pull app_process. + if not app_64bit: + destination = os.path.realpath(out_dir + "/system/bin/app_process") + try: + device.pull("/system/bin/app_process32", destination) + except: + device.pull("/system/bin/app_process", destination) + + +def generate_lldb_script( + args, sysroot, binary_path, app_64bit, jdb_pid, llvm_toolchain_dir +): + lldb_commands = [] + solib_search_paths = [ + "{}/system/bin".format(sysroot), + "{}/system/lib{}".format(sysroot, "64" if app_64bit else ""), + ] + lldb_commands.append( + "settings append target.exec-search-paths {}".format( + " ".join(solib_search_paths) + ) + ) + + lldb_commands.append("target create '{}'".format(binary_path)) + lldb_commands.append("target modules search-paths add / {}/".format(sysroot)) + + lldb_commands.append("gdb-remote {}".format(args.port)) + if jdb_pid is not None: + # After we've interrupted the app, reinvoke ndk-gdb.py to start jdb and + # wake up the app. + lldb_commands.append( + """ +script +def start_jdb_to_unblock_app(): + import subprocess + subprocess.Popen({}) + +start_jdb_to_unblock_app() +exit() + """.format( + repr( + [ + # We can't use sys.executable because it is the python2. + # lldb wrapper will set PYTHONHOME to point to python3. + get_python_executable(llvm_toolchain_dir), + os.path.realpath(__file__), + "--internal-wakeup-pid-with-jdb", + args.device.adb_path, + args.device.serial, + args.jdb_cmd, + str(jdb_pid), + str(bool(args.verbose)), + ] + ) + ) + ) + + if args.tui: + lldb_commands.append("gui") + + if args.exec_file is not None: + try: + exec_file = open(args.exec_file, "r") + except IOError: + error("Failed to open lldb exec file: '{}'.".format(args.exec_file)) + + with exec_file: + lldb_commands.append(exec_file.read()) + + return "\n".join(lldb_commands) + + +def generate_gdb_script( + args, sysroot, binary_path, app_64bit, jdb_pid, connect_timeout=5 +): + if sys.platform.startswith("win"): + # GDB expects paths to use forward slashes. + sysroot = sysroot.replace("\\", "/") + binary_path = binary_path.replace("\\", "/") + + gdb_commands = "set osabi GNU/Linux\n" + gdb_commands += "file '{}'\n".format(binary_path) + + solib_search_path = [sysroot, "{}/system/bin".format(sysroot)] + if app_64bit: + solib_search_path.append("{}/system/lib64".format(sysroot)) + else: + solib_search_path.append("{}/system/lib".format(sysroot)) + solib_search_path = os.pathsep.join(solib_search_path) + gdb_commands += "set solib-absolute-prefix {}\n".format(sysroot) + gdb_commands += "set solib-search-path {}\n".format(solib_search_path) + + # Try to connect for a few seconds, sometimes the device gdbserver takes + # a little bit to come up, especially on emulators. + gdb_commands += """ +python + +def target_remote_with_retry(target, timeout_seconds): + import time + end_time = time.time() + timeout_seconds + while True: + try: + gdb.execute('target remote ' + target) + return True + except gdb.error as e: + time_left = end_time - time.time() + if time_left < 0 or time_left > timeout_seconds: + print("Error: unable to connect to device.") + print(e) + return False + time.sleep(min(0.25, time_left)) + +target_remote_with_retry(':{}', {}) + +end +""".format( + args.port, connect_timeout + ) + + if jdb_pid is not None: + # After we've interrupted the app, reinvoke ndk-gdb.py to start jdb and + # wake up the app. + gdb_commands += """ +python +def start_jdb_to_unblock_app(): + import subprocess + subprocess.Popen({}) +start_jdb_to_unblock_app() +end + """.format( + repr( + [ + sys.executable, + os.path.realpath(__file__), + "--internal-wakeup-pid-with-jdb", + args.device.adb_path, + args.device.serial, + args.jdb_cmd, + str(jdb_pid), + str(bool(args.verbose)), + ] + ) + ) + + if args.exec_file is not None: + try: + exec_file = open(args.exec_file, "r") + except IOError: + error("Failed to open GDB exec file: '{}'.".format(args.exec_file)) + + with exec_file: + gdb_commands += exec_file.read() + + return gdb_commands + + +def start_jdb(adb_path, serial, jdb_cmd, pid, verbose): + pid = int(pid) + device = adb.get_device(serial, adb_path=adb_path) + if verbose == "True": + enable_verbose_logging() + + log("Starting jdb to unblock application.") + + # Do setup stuff to keep ^C in the parent from killing us. + signal.signal(signal.SIGINT, signal.SIG_IGN) + windows = sys.platform.startswith("win") + if not windows: + os.setpgrp() + + jdb_port = 65534 + device.forward("tcp:{}".format(jdb_port), "jdwp:{}".format(pid)) + jdb_cmd = [ + jdb_cmd, + "-connect", + "com.sun.jdi.SocketAttach:hostname=localhost,port={}".format(jdb_port), + ] + + flags = subprocess.CREATE_NEW_PROCESS_GROUP if windows else 0 + jdb = subprocess.Popen( + jdb_cmd, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + creationflags=flags, + ) + + # Wait until jdb can communicate with the app. Once it can, the app will + # start polling for a Java debugger (e.g. every 200ms). We need to wait + # a while longer then so that the app notices jdb. + jdb_magic = "__verify_jdb_has_started__" + jdb.stdin.write('print "{}"\n'.format(jdb_magic).encode("utf-8")) + saw_magic_str = False + while True: + line = jdb.stdout.readline() + if line == "": + break + log("jdb output: " + line.rstrip()) + if jdb_magic in line and not saw_magic_str: + saw_magic_str = True + time.sleep(0.3) + jdb.stdin.write("exit\n") + jdb.wait() + if saw_magic_str: + log("JDB finished unblocking application.") + else: + log("error: did not find magic string in JDB output.") + + +def main(): + if sys.argv[1:2] == ["--internal-wakeup-pid-with-jdb"]: + return start_jdb(*sys.argv[2:]) + + args = handle_args() + device = args.device + use_lldb = not args.no_lldb + + if not use_lldb: + print("WARNING: --no-lldb was used but GDB is no longer supported.") + print("GDB will be used, but will be removed in the next release.") + + if device is None: + error("Could not find a unique connected device/emulator.") + + # Warn on old Pixel C firmware (b/29381985). Newer devices may have Yama + # enabled but still work with ndk-gdb (b/19277529). + yama_check = device.shell_nocheck( + ["cat", "/proc/sys/kernel/yama/ptrace_scope", "2>/dev/null"] + ) + if ( + yama_check[0] == 0 + and yama_check[1].rstrip() not in ["", "0"] + and (device.get_prop("ro.build.product"), device.get_prop("ro.product.name")) + == ("dragon", "ryu") + ): + print( + "WARNING: The device uses Yama ptrace_scope to restrict debugging. ndk-gdb will" + ) + print( + " likely be unable to attach to a process. With root access, the restriction" + ) + print( + " can be lifted by writing 0 to /proc/sys/kernel/yama/ptrace_scope. Consider" + ) + print(" upgrading your Pixel C to MXC89L or newer, where Yama is disabled.") + + adb_version = subprocess.check_output(device.adb_cmd + ["version"]).decode() + log("ADB command used: '{}'".format(" ".join(device.adb_cmd))) + log("ADB version: {}".format(" ".join(adb_version.splitlines()))) + + project = find_project(args) + if args.package_name: + log("Attaching to specified package: {}".format(args.package_name)) + else: + parse_manifest(args) + + pkg_name = args.package_name + + if args.launch is False: + log("Attaching to existing application process.") + else: + args.launch = select_target(args) + log("Selected target activity: '{}'".format(args.launch)) + + abi = fetch_abi(args) + arch = abi_to_arch(abi) + + out_dir = os.path.join(project, (dump_var(args, "TARGET_OUT", abi))) + out_dir = os.path.realpath(out_dir) + + app_data_dir = get_app_data_dir(args, pkg_name) + + llvm_toolchain_dir = os.path.join( + NDK_PATH, "toolchains", "llvm", "prebuilt", get_llvm_host_name() + ) + if use_lldb: + server_local_path = os.path.join( + llvm_toolchain_dir, + "lib64", + "clang", + get_llvm_package_version(llvm_toolchain_dir), + "lib", + "linux", + abi_to_llvm_arch(abi), + "lldb-server", + ) + server_name = "lldb-server" + else: + server_local_path = "{}/prebuilt/android-{}/gdbserver/gdbserver" + server_local_path = server_local_path.format(NDK_PATH, arch) + server_name = "gdbserver" + if not os.path.exists(server_local_path): + error("Can not find {}: {}".format(server_name, server_local_path)) + log("Using {}: {}".format(server_name, server_local_path)) + debugger_server_path = get_debugger_server_path( + args, pkg_name, app_data_dir, arch, server_name, server_local_path + ) + + # Kill the process and gdbserver if requested. + if args.force: + kill_pids = gdbrunner.get_pids(device, debugger_server_path) + if args.launch: + kill_pids += gdbrunner.get_pids(device, pkg_name) + kill_pids = [str(pid) for pid in kill_pids] + if kill_pids: + log("Killing processes: {}".format(", ".join(kill_pids))) + device.shell_nocheck(["run-as", pkg_name, "kill", "-9"] + kill_pids) + + # Launch the application if needed, and get its pid + if args.launch: + am_cmd = ["am", "start"] + if not args.nowait: + am_cmd.append("-D") + component_name = "{}/{}".format(pkg_name, args.launch) + am_cmd.append(component_name) + log("Launching activity {}...".format(component_name)) + (rc, _, _) = device.shell_nocheck(am_cmd) + if rc != 0: + error("Failed to start {}".format(component_name)) + + if args.delay > 0.0: + log("Sleeping for {} seconds.".format(args.delay)) + time.sleep(args.delay) + + pids = gdbrunner.get_pids(device, pkg_name) + if len(pids) == 0: + error("Failed to find running process '{}'".format(pkg_name)) + if len(pids) > 1: + error("Multiple running processes named '{}'".format(pkg_name)) + pid = pids[0] + + # Pull the linker, zygote, and notable system libraries + app_64bit = "64" in abi + pull_binaries(device, out_dir, app_64bit) + if app_64bit: + zygote_path = os.path.join(out_dir, "system", "bin", "app_process64") + else: + zygote_path = os.path.join(out_dir, "system", "bin", "app_process") + + # Start gdbserver. + debug_socket = posixpath.join(app_data_dir, "debug_socket") + log("Starting {}...".format(server_name)) + gdbrunner.start_gdbserver( + device, + None, + debugger_server_path, + target_pid=pid, + run_cmd=None, + debug_socket=debug_socket, + port=args.port, + run_as_cmd=["run-as", pkg_name], + lldb=use_lldb, + ) + + # Start jdb to unblock the application if necessary. + jdb_pid = pid if (args.launch and not args.nowait) else None + + # Start gdb. + if use_lldb: + script_commands = generate_lldb_script( + args, out_dir, zygote_path, app_64bit, jdb_pid, llvm_toolchain_dir + ) + debugger_path = get_lldb_path(llvm_toolchain_dir) + flags = [] + else: + script_commands = generate_gdb_script( + args, out_dir, zygote_path, app_64bit, jdb_pid + ) + debugger_path = os.path.join(ndk_bin_path(), "gdb") + flags = ["--tui"] if args.tui else [] + print(debugger_path) + gdbrunner.start_gdb(debugger_path, script_commands, flags, lldb=use_lldb) + + +if __name__ == "__main__": + main() |