blob: f32880499e4f4b4c7ac7769bfe0df9ceb176ef02 [file] [log] [blame]
Dan Albert2a18e9f2018-05-17 09:13:22 -07001#!/usr/bin/env python
2#
3# Copyright (C) 2015 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17
18from __future__ import print_function
19
20import argparse
21import contextlib
22import os
23import operator
24import posixpath
25import signal
26import subprocess
27import sys
28import time
29import xml.etree.cElementTree as ElementTree
30
31import logging
32
33# Shared functions across gdbclient.py and ndk-gdb.py.
34# ndk-gdb is installed to $NDK/prebuilt/<platform>/bin
35NDK_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../..'))
36sys.path.append(os.path.join(NDK_PATH, "python-packages"))
37import adb
38import gdbrunner
39
40
41def log(msg):
42 logger = logging.getLogger(__name__)
43 logger.info(msg)
44
45
46def enable_verbose_logging():
47 logger = logging.getLogger(__name__)
48 handler = logging.StreamHandler(sys.stdout)
49 formatter = logging.Formatter()
50
51 handler.setFormatter(formatter)
52 logger.addHandler(handler)
53 logger.propagate = False
54
55 logger.setLevel(logging.INFO)
56
57
58def error(msg):
59 sys.exit("ERROR: {}".format(msg))
60
61
62class ArgumentParser(gdbrunner.ArgumentParser):
63 def __init__(self):
64 super(ArgumentParser, self).__init__()
65 self.add_argument(
66 "--verbose", "-v", action="store_true",
67 help="enable verbose mode")
68
69 self.add_argument(
70 "--force", "-f", action="store_true",
71 help="kill existing debug session if it exists")
72
73 self.add_argument(
74 "--port", type=int, nargs="?", default="5039",
75 help="override the port used on the host")
76
77 self.add_argument(
78 "--delay", type=float, default=0.25,
79 help="delay in seconds to wait after starting activity.\n"
80 "defaults to 0.25, higher values may be needed on slower devices.")
81
82 self.add_argument(
83 "-p", "--project", dest="project",
84 help="specify application project path")
85
86 app_group = self.add_argument_group("target selection")
87 start_group = app_group.add_mutually_exclusive_group()
88
89 start_group.add_argument(
90 "--attach", nargs='?', dest="package_name", metavar="PKG_NAME",
91 help="attach to application (default)\n"
92 "autodetects PKG_NAME if not specified")
93
94 # NB: args.launch can be False (--attach), None (--launch), or a string
95 start_group.add_argument(
96 "--launch", nargs='?', dest="launch", default=False,
97 metavar="ACTIVITY",
98 help="launch application activity\n"
99 "launches main activity if ACTIVITY not specified")
100
101 start_group.add_argument(
102 "--launch-list", action="store_true",
103 help="list all launchable activity names from manifest")
104
105 debug_group = self.add_argument_group("debugging options")
106 debug_group.add_argument(
107 "-x", "--exec", dest="exec_file",
108 help="execute gdb commands in EXEC_FILE after connection")
109
110 debug_group.add_argument(
111 "--nowait", action="store_true",
112 help="do not wait for debugger to attach (may miss early JNI "
113 "breakpoints)")
114
115 if sys.platform.startswith("win"):
116 tui_help = argparse.SUPPRESS
117 else:
118 tui_help = "use GDB's tui mode"
119
120 debug_group.add_argument(
121 "-t", "--tui", action="store_true", dest="tui",
122 help=tui_help)
123
124 debug_group.add_argument(
125 "--stdcxx-py-pr", dest="stdcxxpypr",
126 help="use C++ library pretty-printer",
127 choices=["auto", "none", "gnustl", "stlport"],
128 default="auto")
129
130
131def extract_package_name(xmlroot):
132 if "package" in xmlroot.attrib:
133 return xmlroot.attrib["package"]
134 error("Failed to find package name in AndroidManifest.xml")
135
136
137ANDROID_XMLNS = "{http://schemas.android.com/apk/res/android}"
138def extract_launchable(xmlroot):
139 '''
140 A given application can have several activities, and each activity
141 can have several intent filters. We want to only list, in the final
142 output, the activities which have a intent-filter that contains the
143 following elements:
144
145 <action android:name="android.intent.action.MAIN" />
146 <category android:name="android.intent.category.LAUNCHER" />
147 '''
148 launchable_activities = []
149 application = xmlroot.findall("application")[0]
150
151 main_action = "android.intent.action.MAIN"
152 launcher_category = "android.intent.category.LAUNCHER"
153 name_attrib = "{}name".format(ANDROID_XMLNS)
154
155 for activity in application.iter("activity"):
156 if name_attrib not in activity.attrib:
157 continue
158
159 for intent_filter in activity.iter("intent-filter"):
160 found_action = False
161 found_category = False
162 for child in intent_filter:
163 if child.tag == "action":
164 if not found_action and name_attrib in child.attrib:
165 if child.attrib[name_attrib] == main_action:
166 found_action = True
167 if child.tag == "category":
168 if not found_category and name_attrib in child.attrib:
169 if child.attrib[name_attrib] == launcher_category:
170 found_category = True
171 if found_action and found_category:
172 launchable_activities.append(activity.attrib[name_attrib])
173 return launchable_activities
174
175
176def ndk_bin_path():
177 return os.path.dirname(os.path.realpath(__file__))
178
179
180def handle_args():
181 def find_program(program, paths):
182 '''Find a binary in paths'''
183 exts = [""]
184 if sys.platform.startswith("win"):
185 exts += [".exe", ".bat", ".cmd"]
186 for path in paths:
187 if os.path.isdir(path):
188 for ext in exts:
189 full = path + os.sep + program + ext
190 if os.path.isfile(full):
191 return full
192 return None
193
194 # FIXME: This is broken for PATH that contains quoted colons.
195 paths = os.environ["PATH"].replace('"', '').split(os.pathsep)
196
197 args = ArgumentParser().parse_args()
198
199 if args.tui and sys.platform.startswith("win"):
200 error("TUI is unsupported on Windows.")
201
202 ndk_bin = ndk_bin_path()
203 args.make_cmd = find_program("make", [ndk_bin])
204 args.jdb_cmd = find_program("jdb", paths)
205 if args.make_cmd is None:
206 error("Failed to find make in '{}'".format(ndk_bin))
207 if args.jdb_cmd is None:
208 print("WARNING: Failed to find jdb on your path, defaulting to "
209 "--nowait")
210 args.nowait = True
211
212 if args.verbose:
213 enable_verbose_logging()
214
215 return args
216
217
218def find_project(args):
219 manifest_name = "AndroidManifest.xml"
220 if args.project is not None:
221 log("Using project directory: {}".format(args.project))
222 args.project = os.path.realpath(os.path.expanduser(args.project))
223 if not os.path.exists(os.path.join(args.project, manifest_name)):
224 msg = "could not find AndroidManifest.xml in '{}'"
225 error(msg.format(args.project))
226 else:
227 # Walk upwards until we find AndroidManifest.xml, or run out of path.
228 current_dir = os.getcwdu()
229 while not os.path.exists(os.path.join(current_dir, manifest_name)):
230 parent_dir = os.path.dirname(current_dir)
231 if parent_dir == current_dir:
232 error("Could not find AndroidManifest.xml in current"
233 " directory or a parent directory.\n"
234 " Launch this script from inside a project, or"
235 " use --project=<path>.")
236 current_dir = parent_dir
237 args.project = current_dir
238 log("Using project directory: {} ".format(args.project))
239 args.manifest_path = os.path.join(args.project, manifest_name)
240 return args.project
241
242
243def canonicalize_activity(package_name, activity_name):
244 if activity_name.startswith("."):
245 return "{}{}".format(package_name, activity_name)
246 return activity_name
247
248
249def parse_manifest(args):
250 manifest = ElementTree.parse(args.manifest_path)
251 manifest_root = manifest.getroot()
252 package_name = extract_package_name(manifest_root)
253 log("Found package name: {}".format(package_name))
254
255 activities = extract_launchable(manifest_root)
256 activities = [canonicalize_activity(package_name, a) for a in activities]
257
258 if args.launch_list:
259 print("Launchable activities: {}".format(", ".join(activities)))
260 sys.exit(0)
261
262 args.activities = activities
263 args.package_name = package_name
264
265
266def select_target(args):
267 assert args.launch != False
268
269 if len(args.activities) == 0:
270 error("No launchable activities found.")
271
272 if args.launch is None:
273 target = args.activities[0]
274
275 if len(args.activities) > 1:
276 print("WARNING: Multiple launchable activities found, choosing"
277 " '{}'.".format(args.activities[0]))
278 else:
279 activity_name = canonicalize_activity(args.package_name, args.launch)
280
281 if activity_name not in args.activities:
282 msg = "Could not find launchable activity: '{}'."
283 error(msg.format(activity_name))
284 target = activity_name
285 return target
286
287
288@contextlib.contextmanager
289def cd(path):
290 curdir = os.getcwd()
291 os.chdir(path)
292 os.environ["PWD"] = path
293 try:
294 yield
295 finally:
296 os.environ["PWD"] = curdir
297 os.chdir(curdir)
298
299
300def dump_var(args, variable, abi=None):
301 make_args = [args.make_cmd, "--no-print-dir", "-f",
302 os.path.join(NDK_PATH, "build/core/build-local.mk"),
303 "-C", args.project, "DUMP_{}".format(variable)]
304
305 if abi is not None:
306 make_args.append("APP_ABI={}".format(abi))
307
308 with cd(args.project):
309 try:
310 make_output = subprocess.check_output(make_args, cwd=args.project)
311 except subprocess.CalledProcessError:
312 error("Failed to retrieve application ABI from Android.mk.")
313 return make_output.splitlines()[-1]
314
315
316def get_api_level(device):
317 # Check the device API level
318 try:
319 api_level = int(device.get_prop("ro.build.version.sdk"))
320 except (TypeError, ValueError):
321 error("Failed to find target device's supported API level.\n"
322 "ndk-gdb only supports devices running Android 2.2 or higher.")
323 if api_level < 8:
324 error("ndk-gdb only supports devices running Android 2.2 or higher.\n"
325 "(expected API level 8, actual: {})".format(api_level))
326
327 return api_level
328
329
330def fetch_abi(args):
331 '''
332 Figure out the intersection of which ABIs the application is built for and
333 which ones the device supports, then pick the one preferred by the device,
334 so that we know which gdbserver to push and run on the device.
335 '''
336
337 app_abis = dump_var(args, "APP_ABI").split(" ")
338 if "all" in app_abis:
339 app_abis = dump_var(args, "NDK_ALL_ABIS").split(" ")
340 app_abis_msg = "Application ABIs: {}".format(", ".join(app_abis))
341 log(app_abis_msg)
342
343 new_abi_props = ["ro.product.cpu.abilist"]
344 old_abi_props = ["ro.product.cpu.abi", "ro.product.cpu.abi2"]
345 abi_props = new_abi_props
346 if args.device.get_prop("ro.product.cpu.abilist") is None:
347 abi_props = old_abi_props
348
349 device_abis = []
350 for key in abi_props:
351 value = args.device.get_prop(key)
352 if value is not None:
353 device_abis.extend(value.split(","))
354
355 device_abis_msg = "Device ABIs: {}".format(", ".join(device_abis))
356 log(device_abis_msg)
357
358 for abi in device_abis:
359 if abi in app_abis:
360 # TODO(jmgao): Do we expect gdb to work with ARM-x86 translation?
361 log("Selecting ABI: {}".format(abi))
362 return abi
363
364 msg = "Application cannot run on the selected device."
365
366 # Don't repeat ourselves.
367 if not args.verbose:
368 msg += "\n{}\n{}".format(app_abis_msg, device_abis_msg)
369
370 error(msg)
371
372
373def get_run_as_cmd(user, cmd):
374 return ["run-as", user] + cmd
375
376
377def get_app_data_dir(args, package_name):
378 cmd = ["/system/bin/sh", "-c", "pwd", "2>/dev/null"]
379 cmd = get_run_as_cmd(package_name, cmd)
380 (rc, stdout, _) = args.device.shell_nocheck(cmd)
381 if rc != 0:
382 error("Could not find application's data directory. Are you sure that "
383 "the application is installed and debuggable?")
384 data_dir = stdout.strip()
385
386 # Applications with minSdkVersion >= 24 will have their data directories
387 # created with rwx------ permissions, preventing adbd from forwarding to
388 # the gdbserver socket. To be safe, if we're on a device >= 24, always
389 # chmod the directory.
390 if get_api_level(args.device) >= 24:
391 chmod_cmd = ["/system/bin/chmod", "a+x", data_dir]
392 chmod_cmd = get_run_as_cmd(package_name, chmod_cmd)
393 (rc, _, _) = args.device.shell_nocheck(chmod_cmd)
394 if rc != 0:
395 error("Failed to make application data directory world executable")
396
397 log("Found application data directory: {}".format(data_dir))
398 return data_dir
399
400
401def abi_to_arch(abi):
402 if abi.startswith("armeabi"):
403 return "arm"
404 elif abi == "arm64-v8a":
405 return "arm64"
406 else:
407 return abi
408
409
410def get_gdbserver_path(args, package_name, app_data_dir, arch):
411 app_gdbserver_path = "{}/lib/gdbserver".format(app_data_dir)
412 cmd = ["ls", app_gdbserver_path, "2>/dev/null"]
413 cmd = get_run_as_cmd(package_name, cmd)
414 (rc, _, _) = args.device.shell_nocheck(cmd)
415 if rc == 0:
416 log("Found app gdbserver: {}".format(app_gdbserver_path))
417 return app_gdbserver_path
418
419 # We need to upload our gdbserver
420 log("App gdbserver not found at {}, uploading.".format(app_gdbserver_path))
421 local_path = "{}/prebuilt/android-{}/gdbserver/gdbserver"
422 local_path = local_path.format(NDK_PATH, arch)
423 remote_path = "/data/local/tmp/{}-gdbserver".format(arch)
424 args.device.push(local_path, remote_path)
425
426 # Copy gdbserver into the data directory on M+, because selinux prevents
427 # execution of binaries directly from /data/local/tmp.
428 if get_api_level(args.device) >= 23:
429 destination = "{}/{}-gdbserver".format(app_data_dir, arch)
430 log("Copying gdbserver to {}.".format(destination))
431 cmd = ["cat", remote_path, "|", "run-as", package_name,
432 "sh", "-c", "'cat > {}'".format(destination)]
433 (rc, _, _) = args.device.shell_nocheck(cmd)
434 if rc != 0:
435 error("Failed to copy gdbserver to {}.".format(destination))
436 (rc, _, _) = args.device.shell_nocheck(["run-as", package_name,
437 "chmod", "700", destination])
438 if rc != 0:
439 error("Failed to chmod gdbserver at {}.".format(destination))
440
441 remote_path = destination
442
443 log("Uploaded gdbserver to {}".format(remote_path))
444 return remote_path
445
446
447def pull_binaries(device, out_dir, app_64bit):
448 required_files = []
449 libraries = ["libc.so", "libm.so", "libdl.so"]
450
451 if app_64bit:
452 required_files = ["/system/bin/app_process64", "/system/bin/linker64"]
453 library_path = "/system/lib64"
454 else:
455 required_files = ["/system/bin/linker"]
456 library_path = "/system/lib"
457
458 for library in libraries:
459 required_files.append(posixpath.join(library_path, library))
460
461 for required_file in required_files:
462 # os.path.join not used because joining absolute paths will pick the last one
463 local_path = os.path.realpath(out_dir + required_file)
464 local_dirname = os.path.dirname(local_path)
465 if not os.path.isdir(local_dirname):
466 os.makedirs(local_dirname)
467 log("Pulling '{}' to '{}'".format(required_file, local_path))
468 device.pull(required_file, local_path)
469
470 # /system/bin/app_process is 32-bit on 32-bit devices, but a symlink to
471 # app_process64 on 64-bit. If we need the 32-bit version, try to pull
472 # app_process32, and if that fails, pull app_process.
473 if not app_64bit:
474 destination = os.path.realpath(out_dir + "/system/bin/app_process")
475 try:
476 device.pull("/system/bin/app_process32", destination)
477 except:
478 device.pull("/system/bin/app_process", destination)
479
480def generate_gdb_script(args, sysroot, binary_path, app_64bit, jdb_pid, connect_timeout=5):
481 if sys.platform.startswith("win"):
482 # GDB expects paths to use forward slashes.
483 sysroot = sysroot.replace("\\", "/")
484 binary_path = binary_path.replace("\\", "/")
485
486 gdb_commands = "set osabi GNU/Linux\n"
487 gdb_commands += "file '{}'\n".format(binary_path)
488
489 solib_search_path = [sysroot, "{}/system/bin".format(sysroot)]
490 if app_64bit:
491 solib_search_path.append("{}/system/lib64".format(sysroot))
492 else:
493 solib_search_path.append("{}/system/lib".format(sysroot))
494 solib_search_path = os.pathsep.join(solib_search_path)
495 gdb_commands += "set solib-absolute-prefix {}\n".format(sysroot)
496 gdb_commands += "set solib-search-path {}\n".format(solib_search_path)
497
498 # Try to connect for a few seconds, sometimes the device gdbserver takes
499 # a little bit to come up, especially on emulators.
500 gdb_commands += """
501python
502
503def target_remote_with_retry(target, timeout_seconds):
504 import time
505 end_time = time.time() + timeout_seconds
506 while True:
507 try:
508 gdb.execute('target remote ' + target)
509 return True
510 except gdb.error as e:
511 time_left = end_time - time.time()
512 if time_left < 0 or time_left > timeout_seconds:
513 print("Error: unable to connect to device.")
514 print(e)
515 return False
516 time.sleep(min(0.25, time_left))
517
518target_remote_with_retry(':{}', {})
519
520end
521""".format(args.port, connect_timeout)
522
523 if jdb_pid is not None:
524 # After we've interrupted the app, reinvoke ndk-gdb.py to start jdb and
525 # wake up the app.
526 gdb_commands += """
527python
528def start_jdb_to_unblock_app():
529 import subprocess
530 subprocess.Popen({})
531start_jdb_to_unblock_app()
532end
533 """.format(repr(
534 [
535 sys.executable,
536 os.path.realpath(__file__),
537 "--internal-wakeup-pid-with-jdb",
538 args.device.adb_path,
539 args.device.serial,
540 args.jdb_cmd,
541 str(jdb_pid),
542 str(bool(args.verbose)),
543 ]))
544
545 # Set up the pretty printer if needed
546 if args.pypr_dir is not None and args.pypr_fn is not None:
547 gdb_commands += """
548python
549import sys
550sys.path.append("{pypr_dir}")
551from printers import {pypr_fn}
552{pypr_fn}(None)
553end""".format(pypr_dir=args.pypr_dir.replace("\\", "/"), pypr_fn=args.pypr_fn)
554
555 if args.exec_file is not None:
556 try:
557 exec_file = open(args.exec_file, "r")
558 except IOError:
559 error("Failed to open GDB exec file: '{}'.".format(args.exec_file))
560
561 with exec_file:
562 gdb_commands += exec_file.read()
563
564 return gdb_commands
565
566
567def detect_stl_pretty_printer(args):
568 stl = dump_var(args, "APP_STL")
569 if not stl:
570 detected = "none"
571 if args.stdcxxpypr == "auto":
572 log("APP_STL not found, disabling pretty printer")
573 elif stl.startswith("stlport"):
574 detected = "stlport"
575 elif stl.startswith("gnustl"):
576 detected = "gnustl"
577 else:
578 detected = "none"
579
580 if args.stdcxxpypr == "auto":
581 log("Detected pretty printer: {}".format(detected))
582 return detected
583 if detected != args.stdcxxpypr and args.stdcxxpypr != "none":
584 print("WARNING: detected APP_STL ('{}') does not match pretty printer".format(detected))
585 log("Using specified pretty printer: {}".format(args.stdcxxpypr))
586 return args.stdcxxpypr
587
588
589def find_pretty_printer(pretty_printer):
590 if pretty_printer == "gnustl":
591 path = os.path.join("libstdcxx", "gcc-4.9")
592 function = "register_libstdcxx_printers"
593 elif pretty_printer == "stlport":
594 path = os.path.join("stlport", "stlport")
595 function = "register_stlport_printers"
596 pp_path = os.path.join(
597 ndk_bin_path(), "..", "share", "pretty-printers", path)
598 return pp_path, function
599
600
601def start_jdb(adb_path, serial, jdb_cmd, pid, verbose):
602 pid = int(pid)
603 device = adb.get_device(serial, adb_path=adb_path)
604 if verbose == "True":
605 enable_verbose_logging()
606
607 log("Starting jdb to unblock application.")
608
609 # Do setup stuff to keep ^C in the parent from killing us.
610 signal.signal(signal.SIGINT, signal.SIG_IGN)
611 windows = sys.platform.startswith("win")
612 if not windows:
613 os.setpgrp()
614
615 jdb_port = 65534
616 device.forward("tcp:{}".format(jdb_port), "jdwp:{}".format(pid))
617 jdb_cmd = [jdb_cmd, "-connect",
618 "com.sun.jdi.SocketAttach:hostname=localhost,port={}".format(jdb_port)]
619
620 flags = subprocess.CREATE_NEW_PROCESS_GROUP if windows else 0
621 jdb = subprocess.Popen(jdb_cmd,
622 stdin=subprocess.PIPE,
623 stdout=subprocess.PIPE,
624 stderr=subprocess.STDOUT,
625 creationflags=flags)
626
627 # Wait until jdb can communicate with the app. Once it can, the app will
628 # start polling for a Java debugger (e.g. every 200ms). We need to wait
629 # a while longer then so that the app notices jdb.
630 jdb_magic = "__verify_jdb_has_started__"
631 jdb.stdin.write('print "{}"\n'.format(jdb_magic))
632 saw_magic_str = False
633 while True:
634 line = jdb.stdout.readline()
635 if line == "":
636 break
637 log("jdb output: " + line.rstrip())
638 if jdb_magic in line and not saw_magic_str:
639 saw_magic_str = True
640 time.sleep(0.3)
641 jdb.stdin.write("exit\n")
642 jdb.wait()
643 if saw_magic_str:
644 log("JDB finished unblocking application.")
645 else:
646 log("error: did not find magic string in JDB output.")
647
648
649def main():
650 if sys.argv[1:2] == ["--internal-wakeup-pid-with-jdb"]:
651 return start_jdb(*sys.argv[2:])
652
653 args = handle_args()
654 device = args.device
655
656 if device is None:
657 error("Could not find a unique connected device/emulator.")
658
659 # Warn on old Pixel C firmware (b/29381985). Newer devices may have Yama
660 # enabled but still work with ndk-gdb (b/19277529).
661 yama_check = device.shell_nocheck(["cat", "/proc/sys/kernel/yama/ptrace_scope", "2>/dev/null"])
662 if (yama_check[0] == 0 and yama_check[1].rstrip() not in ["", "0"] and
663 (device.get_prop("ro.build.product"), device.get_prop("ro.product.name")) == ("dragon", "ryu")):
664 print("WARNING: The device uses Yama ptrace_scope to restrict debugging. ndk-gdb will")
665 print(" likely be unable to attach to a process. With root access, the restriction")
666 print(" can be lifted by writing 0 to /proc/sys/kernel/yama/ptrace_scope. Consider")
667 print(" upgrading your Pixel C to MXC89L or newer, where Yama is disabled.")
668
669 adb_version = subprocess.check_output(device.adb_cmd + ["version"])
670 log("ADB command used: '{}'".format(" ".join(device.adb_cmd)))
671 log("ADB version: {}".format(" ".join(adb_version.splitlines())))
672
673 project = find_project(args)
674 if args.package_name:
675 log("Attaching to specified package: {}".format(args.package_name))
676 else:
677 parse_manifest(args)
678
679 pkg_name = args.package_name
680
681 if args.launch is False:
682 log("Attaching to existing application process.")
683 else:
684 args.launch = select_target(args)
685 log("Selected target activity: '{}'".format(args.launch))
686
687 abi = fetch_abi(args)
688
689 out_dir = os.path.join(project, (dump_var(args, "TARGET_OUT", abi)))
690 out_dir = os.path.realpath(out_dir)
691
692 pretty_printer = detect_stl_pretty_printer(args)
693 if pretty_printer != "none":
694 (args.pypr_dir, args.pypr_fn) = find_pretty_printer(pretty_printer)
695 else:
696 (args.pypr_dir, args.pypr_fn) = (None, None)
697
698 app_data_dir = get_app_data_dir(args, pkg_name)
699 arch = abi_to_arch(abi)
700 gdbserver_path = get_gdbserver_path(args, pkg_name, app_data_dir, arch)
701
702 # Kill the process and gdbserver if requested.
703 if args.force:
704 kill_pids = gdbrunner.get_pids(device, gdbserver_path)
705 if args.launch:
706 kill_pids += gdbrunner.get_pids(device, pkg_name)
707 kill_pids = map(str, kill_pids)
708 if kill_pids:
709 log("Killing processes: {}".format(", ".join(kill_pids)))
710 device.shell_nocheck(["run-as", pkg_name, "kill", "-9"] + kill_pids)
711
712 # Launch the application if needed, and get its pid
713 if args.launch:
714 am_cmd = ["am", "start"]
715 if not args.nowait:
716 am_cmd.append("-D")
717 component_name = "{}/{}".format(pkg_name, args.launch)
718 am_cmd.append(component_name)
719 log("Launching activity {}...".format(component_name))
720 (rc, _, _) = device.shell_nocheck(am_cmd)
721 if rc != 0:
722 error("Failed to start {}".format(component_name))
723
724 if args.delay > 0.0:
725 log("Sleeping for {} seconds.".format(args.delay))
726 time.sleep(args.delay)
727
728 pids = gdbrunner.get_pids(device, pkg_name)
729 if len(pids) == 0:
730 error("Failed to find running process '{}'".format(pkg_name))
731 if len(pids) > 1:
732 error("Multiple running processes named '{}'".format(pkg_name))
733 pid = pids[0]
734
735 # Pull the linker, zygote, and notable system libraries
736 app_64bit = "64" in abi
737 pull_binaries(device, out_dir, app_64bit)
738 if app_64bit:
739 zygote_path = os.path.join(out_dir, "system", "bin", "app_process64")
740 else:
741 zygote_path = os.path.join(out_dir, "system", "bin", "app_process")
742
743 # Start gdbserver.
744 debug_socket = posixpath.join(app_data_dir, "debug_socket")
745 log("Starting gdbserver...")
746 gdbrunner.start_gdbserver(
747 device, None, gdbserver_path,
748 target_pid=pid, run_cmd=None, debug_socket=debug_socket,
749 port=args.port, run_as_cmd=["run-as", pkg_name])
750
751 gdb_path = os.path.join(ndk_bin_path(), "gdb")
752
753 # Start jdb to unblock the application if necessary.
754 jdb_pid = pid if (args.launch and not args.nowait) else None
755
756 # Start gdb.
757 gdb_commands = generate_gdb_script(args, out_dir, zygote_path, app_64bit, jdb_pid)
758 gdb_flags = []
759 if args.tui:
760 gdb_flags.append("--tui")
761 gdbrunner.start_gdb(gdb_path, gdb_commands, gdb_flags)
762
763if __name__ == "__main__":
764 main()