| #!/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. |
| # |
| |
| from __future__ import print_function |
| |
| import argparse |
| import contextlib |
| import os |
| import operator |
| import posixpath |
| import signal |
| import subprocess |
| import sys |
| import time |
| import xml.etree.cElementTree as ElementTree |
| |
| import logging |
| |
| # Shared functions across gdbclient.py and ndk-gdb.py. |
| # ndk-gdb is installed to $NDK/prebuilt/<platform>/bin |
| NDK_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../..')) |
| sys.path.append(os.path.join(NDK_PATH, "python-packages")) |
| 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") |
| |
| 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) |
| |
| debug_group.add_argument( |
| "--stdcxx-py-pr", dest="stdcxxpypr", |
| help="use C++ library pretty-printer", |
| choices=["auto", "none", "gnustl", "stlport"], |
| default="auto") |
| |
| |
| 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.getcwdu() |
| 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] |
| |
| |
| 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 get_gdbserver_path(args, package_name, app_data_dir, arch): |
| app_gdbserver_path = "{}/lib/gdbserver".format(app_data_dir) |
| cmd = ["ls", app_gdbserver_path, "2>/dev/null"] |
| cmd = get_run_as_cmd(package_name, cmd) |
| (rc, _, _) = args.device.shell_nocheck(cmd) |
| if rc == 0: |
| log("Found app gdbserver: {}".format(app_gdbserver_path)) |
| return app_gdbserver_path |
| |
| # We need to upload our gdbserver |
| log("App gdbserver not found at {}, uploading.".format(app_gdbserver_path)) |
| local_path = "{}/prebuilt/android-{}/gdbserver/gdbserver" |
| local_path = local_path.format(NDK_PATH, arch) |
| remote_path = "/data/local/tmp/{}-gdbserver".format(arch) |
| args.device.push(local_path, remote_path) |
| |
| # Copy gdbserver 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 = "{}/{}-gdbserver".format(app_data_dir, arch) |
| log("Copying gdbserver to {}.".format(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 gdbserver to {}.".format(destination)) |
| (rc, _, _) = args.device.shell_nocheck(["run-as", package_name, |
| "chmod", "700", destination]) |
| if rc != 0: |
| error("Failed to chmod gdbserver at {}.".format(destination)) |
| |
| remote_path = destination |
| |
| log("Uploaded gdbserver to {}".format(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_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)), |
| ])) |
| |
| # Set up the pretty printer if needed |
| if args.pypr_dir is not None and args.pypr_fn is not None: |
| gdb_commands += """ |
| python |
| import sys |
| sys.path.append("{pypr_dir}") |
| from printers import {pypr_fn} |
| {pypr_fn}(None) |
| end""".format(pypr_dir=args.pypr_dir.replace("\\", "/"), pypr_fn=args.pypr_fn) |
| |
| 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 detect_stl_pretty_printer(args): |
| stl = dump_var(args, "APP_STL") |
| if not stl: |
| detected = "none" |
| if args.stdcxxpypr == "auto": |
| log("APP_STL not found, disabling pretty printer") |
| elif stl.startswith("stlport"): |
| detected = "stlport" |
| elif stl.startswith("gnustl"): |
| detected = "gnustl" |
| else: |
| detected = "none" |
| |
| if args.stdcxxpypr == "auto": |
| log("Detected pretty printer: {}".format(detected)) |
| return detected |
| if detected != args.stdcxxpypr and args.stdcxxpypr != "none": |
| print("WARNING: detected APP_STL ('{}') does not match pretty printer".format(detected)) |
| log("Using specified pretty printer: {}".format(args.stdcxxpypr)) |
| return args.stdcxxpypr |
| |
| |
| def find_pretty_printer(pretty_printer): |
| if pretty_printer == "gnustl": |
| path = os.path.join("libstdcxx", "gcc-4.9") |
| function = "register_libstdcxx_printers" |
| elif pretty_printer == "stlport": |
| path = os.path.join("stlport", "stlport") |
| function = "register_stlport_printers" |
| pp_path = os.path.join( |
| ndk_bin_path(), "..", "share", "pretty-printers", path) |
| return pp_path, function |
| |
| |
| 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)) |
| 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 |
| |
| 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"]) |
| 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) |
| |
| out_dir = os.path.join(project, (dump_var(args, "TARGET_OUT", abi))) |
| out_dir = os.path.realpath(out_dir) |
| |
| pretty_printer = detect_stl_pretty_printer(args) |
| if pretty_printer != "none": |
| (args.pypr_dir, args.pypr_fn) = find_pretty_printer(pretty_printer) |
| else: |
| (args.pypr_dir, args.pypr_fn) = (None, None) |
| |
| app_data_dir = get_app_data_dir(args, pkg_name) |
| arch = abi_to_arch(abi) |
| gdbserver_path = get_gdbserver_path(args, pkg_name, app_data_dir, arch) |
| |
| # Kill the process and gdbserver if requested. |
| if args.force: |
| kill_pids = gdbrunner.get_pids(device, gdbserver_path) |
| if args.launch: |
| kill_pids += gdbrunner.get_pids(device, pkg_name) |
| kill_pids = map(str, 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 gdbserver...") |
| gdbrunner.start_gdbserver( |
| device, None, gdbserver_path, |
| target_pid=pid, run_cmd=None, debug_socket=debug_socket, |
| port=args.port, run_as_cmd=["run-as", pkg_name]) |
| |
| gdb_path = os.path.join(ndk_bin_path(), "gdb") |
| |
| # Start jdb to unblock the application if necessary. |
| jdb_pid = pid if (args.launch and not args.nowait) else None |
| |
| # Start gdb. |
| gdb_commands = generate_gdb_script(args, out_dir, zygote_path, app_64bit, jdb_pid) |
| gdb_flags = [] |
| if args.tui: |
| gdb_flags.append("--tui") |
| gdbrunner.start_gdb(gdb_path, gdb_commands, gdb_flags) |
| |
| if __name__ == "__main__": |
| main() |