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