696 lines
24 KiB
Python
Executable file
696 lines
24 KiB
Python
Executable file
#!/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 multiprocessing
|
|
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/host-tools/bin
|
|
NDK_PATH = os.path.normpath(os.path.join(os.path.dirname(__file__), '../..'))
|
|
sys.path.append(os.path.join(NDK_PATH, "python-packages"))
|
|
import gdbrunner
|
|
|
|
|
|
def log(msg):
|
|
logger = logging.getLogger(__name__)
|
|
logger.info(msg)
|
|
|
|
|
|
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.0,
|
|
help="Delay in seconds to wait after starting activity.\n"
|
|
"This may be necessary 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()
|
|
|
|
class NoopAction(argparse.Action):
|
|
def __call__(self, *args, **kwargs):
|
|
pass
|
|
|
|
# Action for --attach is a noop, because --launch's action will store a
|
|
# False in launch if --launch isn't specified.
|
|
start_group.add_argument(
|
|
"--attach", action=NoopAction, nargs=0,
|
|
help="Attach to application [default]")
|
|
|
|
start_group.add_argument(
|
|
"--launch", action="store_true", dest="launch",
|
|
help="Launch application activity (defaults to main activity, "
|
|
"configurable with --launch-activity)")
|
|
|
|
start_group.add_argument(
|
|
"--launch-list", action="store_true",
|
|
help="List all launchable activity names from manifest")
|
|
|
|
app_group.add_argument(
|
|
"--launch-activity", action="store", metavar="ACTIVITY",
|
|
dest="launch_target", help="Launch specified application activity")
|
|
|
|
|
|
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)")
|
|
|
|
debug_group.add_argument(
|
|
"-t", "--tui", action="store_true", dest="tui",
|
|
help="Use GDB's tui mode")
|
|
|
|
debug_group.add_argument(
|
|
"--stdcxx-py-pr", dest="stdcxxpypr",
|
|
help="Use C++ library pretty-printer",
|
|
choices=["auto", "none", "gnustl", "stlport"],
|
|
default="none")
|
|
|
|
|
|
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 is_debuggable(xmlroot):
|
|
applications = xmlroot.findall("application")
|
|
if len(applications) > 1:
|
|
error("Multiple application tags found in AndroidManifest.xml")
|
|
debuggable_attrib = "{}debuggable".format(ANDROID_XMLNS)
|
|
if debuggable_attrib in applications[0].attrib:
|
|
debuggable = applications[0].attrib[debuggable_attrib]
|
|
if debuggable == "true":
|
|
return True
|
|
elif debuggable == "false":
|
|
return False
|
|
else:
|
|
msg = "Unexpected android:debuggable value: '{}'"
|
|
error(msg.format(debuggable))
|
|
return False
|
|
|
|
|
|
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():
|
|
path = os.path.join(NDK_PATH, "host-tools", "bin")
|
|
if not os.path.exists(path):
|
|
error("Failed to find ndk binary path, should be at '{}'".format(path))
|
|
|
|
return path
|
|
|
|
|
|
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()
|
|
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:
|
|
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)
|
|
|
|
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(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))
|
|
|
|
debuggable = is_debuggable(manifest_root)
|
|
if not debuggable:
|
|
error("Application is not marked as debuggable in its manifest.")
|
|
|
|
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
|
|
if len(args.activities) == 0:
|
|
error("No launchable activities found.")
|
|
|
|
if args.launch_target is None:
|
|
args.launch_target = args.activities[0]
|
|
|
|
if len(args.activities) > 1:
|
|
print("WARNING: Multiple launchable activities found, choosing"
|
|
" '{}'.".format(args.activities[0]))
|
|
else:
|
|
canonicalize = canonicalize_activity(args.package_name)
|
|
activity_name = canonicalize(args.launch_target)
|
|
|
|
if activity_name not in args.activities:
|
|
msg = "Could not find launchable activity: '{}'."
|
|
error(msg.format(activity_name))
|
|
args.launch_target = activity_name
|
|
return args.launch_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()[0]
|
|
|
|
|
|
def get_api_level(device_props):
|
|
# Check the device API level
|
|
if "ro.build.version.sdk" not in device_props:
|
|
error("Failed to find target device's supported API level.\n"
|
|
"ndk-gdb only supports devices running Android 2.2 or higher.")
|
|
api_level = int(device_props["ro.build.version.sdk"])
|
|
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)
|
|
|
|
device_props = args.device.get_props()
|
|
|
|
new_abi_props = ["ro.product.cpu.abilist"]
|
|
old_abi_props = ["ro.product.cpu.abi", "ro.product.cpu.abi2"]
|
|
abi_props = new_abi_props
|
|
if len(set(new_abi_props).intersection(device_props.keys())) == 0:
|
|
abi_props = old_abi_props
|
|
|
|
device_abis = [device_props[key].split(",") for key in abi_props]
|
|
|
|
# Flatten the list.
|
|
device_abis = reduce(operator.add, device_abis)
|
|
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_app_data_dir(args, package_name):
|
|
cmd = ["/system/bin/sh", "-c", "pwd", "2>/dev/null"]
|
|
cmd = gdbrunner.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()
|
|
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 = gdbrunner.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 = "{}/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.props) >= 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, is64bit):
|
|
required_files = []
|
|
libraries = ["libc.so", "libm.so", "libdl.so"]
|
|
|
|
if is64bit:
|
|
required_files = ["/system/bin/app_process64", "/system/bin/linker64"]
|
|
library_path = "/system/lib64"
|
|
else:
|
|
required_files = ["/system/bin/app_process", "/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)
|
|
|
|
|
|
def generate_gdb_script(args, sysroot, binary_path, is64bit, connect_timeout=5):
|
|
gdb_commands = "file '{}'\n".format(binary_path)
|
|
|
|
solib_search_path = [sysroot, "{}/system/bin".format(sysroot)]
|
|
if is64bit:
|
|
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)
|
|
|
|
# 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, 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_PATH, "host-tools", "share", "pretty-printers", path)
|
|
return pp_path, function
|
|
|
|
|
|
def main():
|
|
args = handle_args()
|
|
device = args.device
|
|
|
|
if device is None:
|
|
error("Could not find a unique connected device/emulator.")
|
|
|
|
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())))
|
|
|
|
args.props = device.get_props()
|
|
|
|
project = find_project(args)
|
|
parse_manifest(args)
|
|
pkg_name = args.package_name
|
|
|
|
if args.launch is False:
|
|
log("Attaching to existing application process.")
|
|
else:
|
|
launch_target = select_target(args)
|
|
log("Selected target activity: '{}'".format(launch_target))
|
|
|
|
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, launch_target)
|
|
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
|
|
is64bit = "64" in abi
|
|
pull_binaries(device, out_dir, is64bit)
|
|
if is64bit:
|
|
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 = os.path.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, user=pkg_name)
|
|
|
|
gdb_path = os.path.join(ndk_bin_path(), "gdb")
|
|
|
|
# Start jdb to unblock the application if necessary.
|
|
if args.launch and not args.nowait:
|
|
# Do this in a separate process before starting gdb, since jdb won't
|
|
# connect until gdb connects and continues.
|
|
def start_jdb():
|
|
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 = [args.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)
|
|
jdb.stdin.write("exit\n")
|
|
jdb.wait()
|
|
log("JDB finished unblocking application.")
|
|
|
|
jdb_process = multiprocessing.Process(target=start_jdb)
|
|
jdb_process.start()
|
|
|
|
|
|
# Start gdb.
|
|
gdb_commands = generate_gdb_script(args, out_dir, zygote_path, is64bit)
|
|
gdb_flags = []
|
|
if args.tui:
|
|
gdb_flags.append("--tui")
|
|
gdbrunner.start_gdb(gdb_path, gdb_commands, gdb_flags)
|
|
|
|
if __name__ == "__main__":
|
|
main()
|