aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDan Albert <danalbert@google.com>2023-05-16 15:37:29 -0700
committerDan Albert <danalbert@google.com>2023-05-16 15:40:21 -0700
commit643983c12d66ff42cbb63e8f60ba3f0060e0a49d (patch)
treeabbdeaf32ee4d13f203a43d604c21bed75a5e9a9
parent54030b8294ae930b9a7a689e4a8a4ecc00e601c9 (diff)
downloadndk-643983c12d66ff42cbb63e8f60ba3f0060e0a49d.tar.gz
Make ndkgdb mostly mypy clean.
Prep work to make tests easier to write. This isn't complete and can't be until gdbrunner is a real package with type annotations. I'll do that next. Bug: None Test: mypy ndkgdb.py Change-Id: Ib312c7d8084b4480cd3789534f16f0923d52bce3
-rwxr-xr-xndkgdb.py139
1 files changed, 87 insertions, 52 deletions
diff --git a/ndkgdb.py b/ndkgdb.py
index ab821fbe6..55def8358 100755
--- a/ndkgdb.py
+++ b/ndkgdb.py
@@ -27,19 +27,23 @@ import signal
import subprocess
import sys
import time
-import xml.etree.ElementTree as ElementTree
+from collections.abc import Iterator
+from typing import NoReturn
+from xml.etree import ElementTree
+from xml.etree.ElementTree import Element
import adb
import gdbrunner
NDK_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../.."))
-def log(msg):
+
+def log(msg: str) -> None:
logger = logging.getLogger(__name__)
logger.info(msg)
-def enable_verbose_logging():
+def enable_verbose_logging() -> None:
logger = logging.getLogger(__name__)
handler = logging.StreamHandler(sys.stdout)
formatter = logging.Formatter()
@@ -51,12 +55,12 @@ def enable_verbose_logging():
logger.setLevel(logging.INFO)
-def error(msg):
+def error(msg: str) -> NoReturn:
sys.exit("ERROR: {}".format(msg))
class ArgumentParser(gdbrunner.ArgumentParser):
- def __init__(self):
+ def __init__(self) -> None:
super(ArgumentParser, self).__init__()
self.add_argument(
"--verbose", "-v", action="store_true", help="enable verbose mode"
@@ -149,7 +153,7 @@ class ArgumentParser(gdbrunner.ArgumentParser):
)
-def extract_package_name(xmlroot):
+def extract_package_name(xmlroot: Element) -> str:
if "package" in xmlroot.attrib:
return xmlroot.attrib["package"]
error("Failed to find package name in AndroidManifest.xml")
@@ -158,7 +162,7 @@ def extract_package_name(xmlroot):
ANDROID_XMLNS = "{http://schemas.android.com/apk/res/android}"
-def extract_launchable(xmlroot):
+def extract_launchable(xmlroot: Element) -> list[str]:
"""
A given application can have several activities, and each activity
can have several intent filters. We want to only list, in the final
@@ -196,12 +200,12 @@ def extract_launchable(xmlroot):
return launchable_activities
-def ndk_bin_path():
+def ndk_bin_path() -> str:
return os.path.dirname(os.path.realpath(__file__))
-def handle_args():
- def find_program(program, paths):
+def handle_args() -> argparse.Namespace:
+ def find_program(program: str, paths: list[str]) -> str | None:
"""Find a binary in paths"""
exts = [""]
if sys.platform.startswith("win"):
@@ -217,7 +221,7 @@ def handle_args():
# FIXME: This is broken for PATH that contains quoted colons.
paths = os.environ["PATH"].replace('"', "").split(os.pathsep)
- args = ArgumentParser().parse_args()
+ args: argparse.Namespace = ArgumentParser().parse_args()
if args.tui and sys.platform.startswith("win"):
error("TUI is unsupported on Windows.")
@@ -237,9 +241,10 @@ def handle_args():
return args
-def find_project(args):
+def find_project(args: argparse.Namespace) -> str:
manifest_name = "AndroidManifest.xml"
- if args.project is not None:
+ project: str | None = args.project
+ if 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)):
@@ -260,17 +265,19 @@ def find_project(args):
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
+ assert project is not None
+ args.manifest_path = os.path.join(project, manifest_name)
+ args.project = project
+ return project
-def canonicalize_activity(package_name, activity_name):
+def canonicalize_activity(package_name: str, activity_name: str) -> str:
if activity_name.startswith("."):
return "{}{}".format(package_name, activity_name)
return activity_name
-def parse_manifest(args):
+def parse_manifest(args: argparse.Namespace) -> None:
manifest = ElementTree.parse(args.manifest_path)
manifest_root = manifest.getroot()
package_name = extract_package_name(manifest_root)
@@ -287,12 +294,13 @@ def parse_manifest(args):
args.package_name = package_name
-def select_target(args):
+def select_target(args: argparse.Namespace) -> str:
assert args.launch != False
if len(args.activities) == 0:
error("No launchable activities found.")
+ target: str
if args.launch is None:
target = args.activities[0]
@@ -312,7 +320,7 @@ def select_target(args):
@contextlib.contextmanager
-def cd(path):
+def cd(path: str) -> Iterator[None]:
curdir = os.getcwd()
os.chdir(path)
os.environ["PWD"] = path
@@ -323,7 +331,7 @@ def cd(path):
os.chdir(curdir)
-def dump_var(args, variable, abi=None):
+def dump_var(args: argparse.Namespace, variable: str, abi: str | None = None) -> str:
make_args = [
args.make_cmd,
"--no-print-dir",
@@ -345,11 +353,14 @@ def dump_var(args, variable, abi=None):
return make_output.splitlines()[-1].decode()
-def get_api_level(device):
+def get_api_level(device: adb.AndroidDevice) -> int:
# Check the device API level
try:
- api_level = int(device.get_prop("ro.build.version.sdk"))
- except (TypeError, ValueError):
+ api_str = device.get_prop("ro.build.version.sdk")
+ if api_str is None:
+ raise KeyError
+ api_level = int()
+ except (ValueError, KeyError):
error(
"Failed to find target device's supported API level.\n"
"ndk-gdb only supports devices running Android 2.2 or higher."
@@ -363,7 +374,7 @@ def get_api_level(device):
return api_level
-def fetch_abi(args):
+def fetch_abi(args: argparse.Namespace) -> str:
"""
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,
@@ -382,7 +393,7 @@ def fetch_abi(args):
if args.device.get_prop("ro.product.cpu.abilist") is None:
abi_props = old_abi_props
- device_abis = []
+ device_abis: list[str] = []
for key in abi_props:
value = args.device.get_prop(key)
if value is not None:
@@ -406,14 +417,15 @@ def fetch_abi(args):
error(msg)
-def get_run_as_cmd(user, cmd):
+def get_run_as_cmd(user: str, cmd: list[str]) -> list[str]:
return ["run-as", user] + cmd
-def get_app_data_dir(args, package_name):
+def get_app_data_dir(args: argparse.Namespace, package_name: str) -> str:
cmd = ["/system/bin/sh", "-c", "pwd", "2>/dev/null"]
cmd = get_run_as_cmd(package_name, cmd)
- (rc, stdout, _) = args.device.shell_nocheck(cmd)
+ device: adb.AndroidDevice = args.device
+ (rc, stdout, _) = device.shell_nocheck(cmd)
if rc != 0:
error(
"Could not find application's data directory. Are you sure that "
@@ -436,7 +448,7 @@ def get_app_data_dir(args, package_name):
return data_dir
-def abi_to_arch(abi):
+def abi_to_arch(abi: str) -> str:
if abi.startswith("armeabi"):
return "arm"
elif abi == "arm64-v8a":
@@ -445,7 +457,7 @@ def abi_to_arch(abi):
return abi
-def abi_to_llvm_arch(abi):
+def abi_to_llvm_arch(abi: str) -> str:
if abi.startswith("armeabi"):
return "arm"
elif abi == "arm64-v8a":
@@ -456,7 +468,7 @@ def abi_to_llvm_arch(abi):
return "x86_64"
-def get_llvm_host_name():
+def get_llvm_host_name() -> str:
platform = sys.platform
if platform.startswith("win"):
return "windows-x86_64"
@@ -466,14 +478,14 @@ def get_llvm_host_name():
return "linux-x86_64"
-def get_python_executable(toolchain_path):
+def get_python_executable(toolchain_path: str) -> str:
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):
+def get_lldb_path(toolchain_path: str) -> str | None:
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):
@@ -481,7 +493,7 @@ def get_lldb_path(toolchain_path):
return None
-def get_llvm_package_version(llvm_toolchain_dir):
+def get_llvm_package_version(llvm_toolchain_dir: str) -> str:
version_file_path = os.path.join(llvm_toolchain_dir, "AndroidVersion.txt")
try:
version_file = open(version_file_path, "r")
@@ -495,8 +507,13 @@ def get_llvm_package_version(llvm_toolchain_dir):
def get_debugger_server_path(
- args, package_name, app_data_dir, arch, server_name, local_path
-):
+ args: argparse.Namespace,
+ package_name: str,
+ app_data_dir: str,
+ arch: str,
+ server_name: str,
+ local_path: str,
+) -> str:
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)
@@ -544,7 +561,7 @@ def get_debugger_server_path(
return remote_path
-def pull_binaries(device, out_dir, app_64bit):
+def pull_binaries(device: adb.AndroidDevice, out_dir: str, app_64bit: bool) -> None:
required_files = []
libraries = ["libc.so", "libm.so", "libdl.so"]
@@ -579,8 +596,13 @@ def pull_binaries(device, out_dir, app_64bit):
def generate_lldb_script(
- args, sysroot, binary_path, app_64bit, jdb_pid, llvm_toolchain_dir
-):
+ args: argparse.Namespace,
+ sysroot: str,
+ binary_path: str,
+ app_64bit: bool,
+ jdb_pid: int,
+ llvm_toolchain_dir: str,
+) -> str:
lldb_commands = []
solib_search_paths = [
"{}/system/bin".format(sysroot),
@@ -642,8 +664,13 @@ exit()
def generate_gdb_script(
- args, sysroot, binary_path, app_64bit, jdb_pid, connect_timeout=5
-):
+ args: argparse.Namespace,
+ sysroot: str,
+ binary_path: str,
+ app_64bit: bool,
+ jdb_pid: int,
+ connect_timeout: int = 5,
+) -> str:
if sys.platform.startswith("win"):
# GDB expects paths to use forward slashes.
sysroot = sysroot.replace("\\", "/")
@@ -652,12 +679,12 @@ def generate_gdb_script(
gdb_commands = "set osabi GNU/Linux\n"
gdb_commands += "file '{}'\n".format(binary_path)
- solib_search_path = [sysroot, "{}/system/bin".format(sysroot)]
+ solib_search_paths = [sysroot, "{}/system/bin".format(sysroot)]
if app_64bit:
- solib_search_path.append("{}/system/lib64".format(sysroot))
+ solib_search_paths.append("{}/system/lib64".format(sysroot))
else:
- solib_search_path.append("{}/system/lib".format(sysroot))
- solib_search_path = os.pathsep.join(solib_search_path)
+ solib_search_paths.append("{}/system/lib".format(sysroot))
+ solib_search_path = os.pathsep.join(solib_search_paths)
gdb_commands += "set solib-absolute-prefix {}\n".format(sysroot)
gdb_commands += "set solib-search-path {}\n".format(solib_search_path)
@@ -725,8 +752,10 @@ end
return gdb_commands
-def start_jdb(adb_path, serial, jdb_cmd, pid, verbose):
- pid = int(pid)
+def start_jdb(
+ adb_path: str, serial: str, jdb_cmd: str, pid_str: str, verbose: str
+) -> None:
+ pid = int(pid_str)
device = adb.get_device(serial, adb_path=adb_path)
if verbose == "True":
enable_verbose_logging()
@@ -741,26 +770,32 @@ def start_jdb(adb_path, serial, jdb_cmd, pid, verbose):
jdb_port = 65534
device.forward("tcp:{}".format(jdb_port), "jdwp:{}".format(pid))
- jdb_cmd = [
+ jdb_args = [
jdb_cmd,
"-connect",
"com.sun.jdi.SocketAttach:hostname=localhost,port={}".format(jdb_port),
]
- flags = subprocess.CREATE_NEW_PROCESS_GROUP if windows else 0
+ if sys.platform == "win32":
+ flags = subprocess.CREATE_NEW_PROCESS_GROUP
+ else:
+ flags = 0
jdb = subprocess.Popen(
- jdb_cmd,
+ jdb_args,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
creationflags=flags,
+ text=True,
)
+ assert jdb.stdin is not None
+ assert jdb.stdout is not None
# 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"))
+ jdb.stdin.write('print "{}"\n'.format(jdb_magic))
saw_magic_str = False
while True:
line = jdb.stdout.readline()
@@ -778,7 +813,7 @@ def start_jdb(adb_path, serial, jdb_cmd, pid, verbose):
log("error: did not find magic string in JDB output.")
-def main():
+def main() -> None:
if sys.argv[1:2] == ["--internal-wakeup-pid-with-jdb"]:
return start_jdb(*sys.argv[2:])