Home | History | Annotate | Download | only in ndk
      1 #!/bin/env python
      2 
      3 r'''
      4  Copyright (C) 2010 The Android Open Source Project
      5  Copyright (C) 2012 Ray Donnelly <mingw.android (at] gmail.com>
      6 
      7  Licensed under the Apache License, Version 2.0 (the "License");
      8  you may not use this file except in compliance with the License.
      9  You may obtain a copy of the License at
     10 
     11       http://www.apache.org/licenses/LICENSE-2.0
     12 
     13  Unless required by applicable law or agreed to in writing, software
     14  distributed under the License is distributed on an "AS IS" BASIS,
     15  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     16  See the License for the specific language governing permissions and
     17  limitations under the License.
     18 
     19 
     20  This wrapper script is used to launch a native debugging session
     21  on a given NDK application. The application must be debuggable, i.e.
     22  its android:debuggable attribute must be set to 'true' in the
     23  <application> element of its manifest.
     24 
     25  See docs/NDK-GDB.TXT for usage description. Essentially, you just
     26  need to launch ndk-gdb-py from your application project directory
     27  after doing ndk-build && ant debug && \
     28   adb install && <start-application-on-device>
     29 '''
     30 
     31 import sys, os, argparse, subprocess, types
     32 import xml.etree.cElementTree as ElementTree
     33 import shutil
     34 from threading import Thread
     35 try:
     36     from Queue import Queue, Empty
     37 except ImportError:
     38     from queue import Queue, Empty  # python 3.x
     39 
     40 def find_program(program, extra_paths = []):
     41     ''' extra_paths are searched before PATH '''
     42     PATHS = extra_paths+os.environ['PATH'].split(os.pathsep)
     43     exts = ['']
     44     if sys.platform.startswith('win'):
     45         exts += ['.exe', '.bat', '.cmd']
     46     for path in PATHS:
     47         if os.path.isdir(path):
     48             for ext in exts:
     49                 full = path + os.sep + program + ext
     50                 if os.path.isfile(full):
     51                     return True, full
     52     return False, None
     53 
     54 # Return the prebuilt bin path for the host os.
     55 def ndk_bin_path(ndk):
     56     if sys.platform.startswith('linux'):
     57         return ndk+os.sep+'prebuilt/linux-x86/bin'
     58     elif sys.platform.startswith('darwin'):
     59         return ndk+os.sep+'prebuilt/darwin-x86/bin'
     60     elif sys.platform.startswith('win'):
     61         return ndk+os.sep+'prebuilt/windows/bin'
     62     return ndk+os.sep+'prebuilt/UNKNOWN/bin'
     63 
     64 VERBOSE = False
     65 PROJECT = None
     66 PYTHON_CMD = None
     67 ADB_CMD = None
     68 GNUMAKE_CMD = None
     69 
     70 OPTION_FORCE = None
     71 OPTION_EXEC = None
     72 OPTION_START = None
     73 OPTION_LAUNCH = None
     74 OPTION_LAUNCH_LIST = None
     75 OPTION_TUI = None
     76 
     77 
     78 DEBUG_PORT = 5039
     79 
     80 # Name of the manifest file
     81 MANIFEST = 'AndroidManifest.xml'
     82 
     83 # Delay in seconds between launching the activity and attaching gdbserver on it.
     84 # This is needed because there is no way to know when the activity has really
     85 # started, and sometimes this takes a few seconds.
     86 #
     87 DELAY = 2.0
     88 NDK = os.path.abspath(os.path.dirname(sys.argv[0]))
     89 DEVICE_SERIAL = ''
     90 ADB_FLAGS = ''
     91 
     92 def log(string):
     93     global VERBOSE
     94     if VERBOSE:
     95         print(string)
     96 
     97 def error(string, errcode=1):
     98     print('ERROR: %s' % (string))
     99     exit(errcode)
    100 
    101 def handle_args():
    102     global VERBOSE, DEBUG_PORT, DELAY, DEVICE_SERIAL
    103     global PYTHON_CMD, GNUMAKE_CMD, ADB_CMD, ADB_FLAGS
    104     global PROJECT, NDK
    105     global OPTION_START, OPTION_LAUNCH, OPTION_LAUNCH_LIST
    106     global OPTION_FORCE, OPTION_EXEC, OPTION_TUI
    107 
    108     parser = argparse.ArgumentParser(description='''
    109     Setup a gdb debugging session for your Android NDK application.
    110     Read ''' + NDK + '''/docs/NDK-GDB.html for complete usage instructions.''')
    111 
    112     parser.add_argument( '--verbose',
    113                          help='Enable verbose mode', action='store_true', dest='verbose')
    114 
    115     parser.add_argument( '--force',
    116                          help='Kill existing debug session if it exists',
    117                          action='store_true')
    118 
    119     parser.add_argument( '--start',
    120                          help='Launch application instead of attaching to existing one',
    121                          action='store_true')
    122 
    123     parser.add_argument( '--launch',
    124                          help='Same as --start, but specify activity name (see below)',
    125                          dest='launch_name', nargs=1)
    126 
    127     parser.add_argument( '--launch-list',
    128                          help='List all launchable activity names from manifest',
    129                          action='store_true')
    130 
    131     parser.add_argument( '--delay',
    132                          help='Delay in seconds between activity start and gdbserver attach',
    133                          type=float, default=DELAY,
    134                          dest='delay')
    135 
    136     parser.add_argument( '-p', '--project',
    137                          help='Specify application project path',
    138                          dest='project')
    139 
    140     parser.add_argument( '--port',
    141                          help='Use tcp:localhost:<DEBUG_PORT> to communicate with gdbserver',
    142                          type=int, default=DEBUG_PORT,
    143                          dest='debug_port')
    144 
    145     parser.add_argument( '-x', '--exec',
    146                          help='Execute gdb initialization commands in <EXEC_FILE> after connection',
    147                          dest='exec_file')
    148 
    149     parser.add_argument( '--adb',
    150                          help='Use specific adb command',
    151                          dest='adb_cmd')
    152 
    153     parser.add_argument( '--awk',
    154                          help='Use specific awk command (unused flag retained for compatability)')
    155 
    156     parser.add_argument( '-e',
    157                          help='Connect to single emulator instance....(either this,)',
    158                          action='store_true', dest='emulator')
    159 
    160     parser.add_argument( '-d',
    161                          help='Connect to single target device........(this,)',
    162                          action='store_true', dest='device')
    163 
    164     parser.add_argument( '-s',
    165                          help='Connect to specific emulator or device.(or this)',
    166                          default=DEVICE_SERIAL,
    167                          dest='device_serial')
    168 
    169     parser.add_argument( '-t','--tui',
    170                          help='Use tui mode',
    171                          action='store_true', dest='tui')
    172 
    173     args = parser.parse_args()
    174 
    175     VERBOSE = args.verbose
    176 
    177     ndk_bin = ndk_bin_path(NDK)
    178     (found_python,  PYTHON_CMD)  = find_program('python', [ndk_bin])
    179     (found_adb,     ADB_CMD)     = find_program('adb',    [ndk_bin])
    180     (found_gnumake, GNUMAKE_CMD) = find_program('make',   [ndk_bin])
    181 
    182     if not found_gnumake:
    183         error('Failed to find GNU make')
    184 
    185     log('Android NDK installation path: %s' % (NDK))
    186 
    187     if args.device:
    188         ADB_FLAGS = '-d'
    189     if args.emulator:
    190         if ADB_FLAGS != '':
    191             parser.print_help()
    192             exit(1)
    193         ADB_FLAGS = '-e'
    194     if args.device_serial != '':
    195         DEVICE_SERIAL = args.device_serial
    196         if ADB_FLAGS != '':
    197             parser.print_help()
    198             exit(1)
    199         ADB_FLAGS = '-s'
    200     if args.adb_cmd != None:
    201         log('Using specific adb command: %s' % (args.adb_cmd))
    202         ADB_CMD = args.adb_cmd
    203     if ADB_CMD is None:
    204         error('''The 'adb' tool is not in your path.
    205        You can change your PATH variable, or use
    206        --adb=<executable> to point to a valid one.''')
    207     if not os.path.isfile(ADB_CMD):
    208         error('Could not run ADB with: %s' % (ADB_CMD))
    209 
    210     if args.project != None:
    211         PROJECT = args.project
    212 
    213     if args.start != None:
    214         OPTION_START = args.start
    215 
    216     if args.launch_name != None:
    217         OPTION_LAUNCH = args.launch_name
    218 
    219     if args.launch_list != None:
    220         OPTION_LAUNCH_LIST = args.launch_list
    221 
    222     if args.force != None:
    223         OPTION_FORCE = args.force
    224 
    225     if args.exec_file != None:
    226         OPTION_EXEC = args.exec_file
    227 
    228     if args.tui != False:
    229         OPTION_TUI = True
    230 
    231     if args.delay != None:
    232         DELAY = args.delay
    233 
    234 def get_build_var(var):
    235     global GNUMAKE_CMD, NDK, PROJECT
    236     text = subprocess.check_output([GNUMAKE_CMD,
    237                                   '--no-print-dir',
    238                                   '-f',
    239                                   NDK+'/build/core/build-local.mk',
    240                                   '-C',
    241                                   PROJECT,
    242                                   'DUMP_'+var]
    243                                   )
    244     # replace('\r', '') due to Windows crlf (\r\n)
    245     #  ...universal_newlines=True causes bytes to be returned
    246     #     rather than a str
    247     return text.decode('ascii').replace('\r', '').splitlines()[0]
    248 
    249 def get_build_var_for_abi(var, abi):
    250     global GNUMAKE_CMD, NDK, PROJECT
    251     text = subprocess.check_output([GNUMAKE_CMD,
    252                                    '--no-print-dir',
    253                                    '-f',
    254                                    NDK+'/build/core/build-local.mk',
    255                                    '-C',
    256                                    PROJECT,
    257                                    'DUMP_'+var,
    258                                    'APP_ABI='+abi],
    259                                    )
    260     return text.decode('ascii').replace('\r', '').splitlines()[0]
    261 
    262 # Silent if gdb is running in tui mode to keep things tidy.
    263 def output_gdbserver(text):
    264     if not OPTION_TUI or OPTION_TUI != 'running':
    265         print(text)
    266 
    267 def background_spawn(args, redirect_stderr, output_fn):
    268 
    269     def async_stdout(out, queue, output_fn):
    270         for line in iter(out.readline, b''):
    271             output_fn(line.replace('\r', '').replace('\n', ''))
    272         out.close()
    273 
    274     def async_stderr(out, queue, output_fn):
    275         for line in iter(out.readline, b''):
    276             output_fn(line.replace('\r', '').replace('\n', ''))
    277         out.close()
    278 
    279     if redirect_stderr:
    280         used_stderr = subprocess.PIPE
    281     else:
    282         used_stderr = subprocess.STDOUT
    283     p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=used_stderr,
    284                          bufsize=1, close_fds='posix' in sys.builtin_module_names)
    285     qo = Queue()
    286     to = Thread(target=async_stdout, args=(p.stdout, qo, output_fn))
    287     to.daemon = True
    288     to.start()
    289     if redirect_stderr:
    290         te = Thread(target=async_stderr, args=(p.stderr, qo, output_fn))
    291         te.daemon = True
    292         te.start()
    293 
    294 def adb_cmd(redirect_stderr, args, log_command=False, adb_trace=False, background=False):
    295     global ADB_CMD, ADB_FLAGS, DEVICE_SERIAL
    296     fullargs = [ADB_CMD]
    297     if ADB_FLAGS != '':
    298         fullargs += [ADB_FLAGS]
    299     if DEVICE_SERIAL != '':
    300         fullargs += [DEVICE_SERIAL]
    301     if isinstance(args, str):
    302         fullargs.append(args)
    303     else:
    304         fullargs += [arg for arg in args]
    305     new_env = os.environ.copy()
    306     retval = 0
    307     if adb_trace:
    308         new_env["ADB_TRACE"] = "1"
    309     if background:
    310         if log_command:
    311             log('## COMMAND: adb_cmd %s [BACKGROUND]' % (' '.join(args)))
    312         background_spawn(fullargs, redirect_stderr, output_gdbserver)
    313         return 0, ''
    314     else:
    315         if log_command:
    316             log('## COMMAND: adb_cmd %s' % (' '.join(args)))
    317         try:
    318             if redirect_stderr:
    319                 text = subprocess.check_output(fullargs,
    320                                                stderr=subprocess.STDOUT,
    321                                                env=new_env
    322                                                )
    323             else:
    324                 text = subprocess.check_output(fullargs,
    325                                                env=new_env
    326                                                )
    327         except subprocess.CalledProcessError as e:
    328             retval = e.returncode
    329             text = e.output
    330         # rstrip() because of final newline.
    331         return retval, text.decode('ascii').replace('\r', '').rstrip()
    332 
    333 def _adb_var_shell(args, redirect_stderr=False, log_command=True):
    334     if log_command:
    335         log('## COMMAND: adb_cmd shell %s' % (' '.join(args)))
    336     arg_str = str(' '.join(args)+' ; echo $?')
    337     adb_ret,output = adb_cmd(redirect_stderr=redirect_stderr,
    338                            args=['shell', arg_str], log_command=False)
    339     output = output.splitlines()
    340     retcode = int(output.pop())
    341     return retcode,'\n'.join(output)
    342 
    343 def adb_var_shell(args, log_command=False):
    344     return _adb_var_shell(args, redirect_stderr=False, log_command=log_command)
    345 
    346 def adb_var_shell2(args, log_command=False):
    347     return _adb_var_shell(args, redirect_stderr=True, log_command=log_command)
    348 
    349 # Return the PID of a given package or program, or 0 if it doesn't run
    350 # $1: Package name ("com.example.hellojni") or program name ("/lib/gdbserver")
    351 # Out: PID number, or 0 if not running
    352 #
    353 def get_pid_of(package_name):
    354     '''
    355     Some custom ROMs use busybox instead of toolbox for ps.
    356     Without -w, busybox truncates the output, and very long
    357     package names like com.exampleisverylongtoolongbyfar.plasma
    358     exceed the limit.
    359     '''
    360     ps_command = 'ps'
    361     retcode,output = adb_cmd(False, ['shell', 'readlink $(which ps)'])
    362     if output:
    363         output = output.replace('\r', '').splitlines()[0]
    364     if output == 'busybox':
    365         ps_command = 'ps -w'
    366     retcode,output = adb_cmd(False,['shell', ps_command])
    367     output = output.replace('\r', '').splitlines()
    368     columns = output.pop(0).split()
    369     try:
    370         PID_column = columns.index('PID')
    371     except:
    372         PID_column = 1
    373     while output:
    374         columns = output.pop().split()
    375         if columns.pop() == package_name:
    376             return 0,int(columns[PID_column])
    377     return 1,0
    378 
    379 def extract_package_name(xmlfile):
    380     '''
    381     The name itself is the value of the 'package' attribute in the
    382     'manifest' element.
    383     '''
    384     tree = ElementTree.ElementTree(file=xmlfile)
    385     root = tree.getroot()
    386     if 'package' in root.attrib:
    387         return root.attrib['package']
    388     return None
    389 
    390 def extract_debuggable(xmlfile):
    391     '''
    392     simply extract the 'android:debuggable' attribute value from
    393     the first <manifest><application> element we find.
    394     '''
    395     tree = ElementTree.ElementTree(file=xmlfile)
    396     root = tree.getroot()
    397     for application in root.iter('application'):
    398         for k in application.attrib.keys():
    399             if str(k).endswith('debuggable'):
    400                 return application.attrib[k] == 'true'
    401     return False
    402 
    403 def extract_launchable(xmlfile):
    404     '''
    405     A given application can have several activities, and each activity
    406     can have several intent filters. We want to only list, in the final
    407     output, the activities which have a intent-filter that contains the
    408     following elements:
    409 
    410       <action android:name="android.intent.action.MAIN" />
    411       <category android:name="android.intent.category.LAUNCHER" />
    412     '''
    413     tree = ElementTree.ElementTree(file=xmlfile)
    414     root = tree.getroot()
    415     launchable_activities = []
    416     for application in root.iter('application'):
    417         for activity in application.iter('activity'):
    418             for intent_filter in activity.iter('intent-filter'):
    419                 found_action_MAIN = False
    420                 found_category_LAUNCHER = False
    421                 for child in intent_filter:
    422                     if child.tag == 'action':
    423                         if True in [str(child.attrib[k]).endswith('MAIN') for k in child.attrib.keys()]:
    424                             found_action_MAIN = True
    425                     if child.tag == 'category':
    426                         if True in [str(child.attrib[k]).endswith('LAUNCHER') for k in child.attrib.keys()]:
    427                             found_category_LAUNCHER = True
    428                 if found_action_MAIN and found_category_LAUNCHER:
    429                     names = [str(activity.attrib[k]) for k in activity.attrib.keys() if str(k).endswith('name')]
    430                     for name in names:
    431                         if name[0] != '.':
    432                             name = '.'+name
    433                         launchable_activities.append(name)
    434     return launchable_activities
    435 
    436 def main():
    437     global ADB_CMD, NDK, PROJECT
    438     global OPTION_START, OPTION_LAUNCH, OPTION_LAUNCH_LIST
    439     global OPTION_FORCE, OPTION_EXEC, OPTION_TUI
    440 
    441     if NDK.find(' ')!=-1:
    442         error('NDK path cannot contain space')
    443     handle_args()
    444     if OPTION_EXEC:
    445         if not os.path.isfile(OPTION_EXEC):
    446             error('Invalid initialization file: %s' % (OPTION_EXEC))
    447     ADB_VERSION = subprocess.check_output([ADB_CMD, 'version'],
    448                                         ).decode('ascii').replace('\r', '').splitlines()[0]
    449     log('ADB version found: %s' % (ADB_VERSION))
    450     if DEVICE_SERIAL == '':
    451         log('Using ADB flags: %s' % (ADB_FLAGS))
    452     else:
    453         log('Using ADB flags: %s "%s"' % (ADB_FLAGS,DEVICE_SERIAL))
    454 
    455     if PROJECT != None:
    456         log('Using specified project path: %s' % (PROJECT))
    457         if not os.path.isdir(PROJECT):
    458             error('Your --project option does not point to a directory!')
    459         if not os.path.isfile(PROJECT+os.sep+MANIFEST):
    460             error('''Your --project does not point to an Android project path!
    461        It is missing a %s file.''' % (MANIFEST))
    462     else:
    463         # Assume we are in the project directory
    464         if os.path.isfile(MANIFEST):
    465             PROJECT = '.'
    466         else:
    467             PROJECT = ''
    468             CURDIR = os.getcwd()
    469 
    470             while CURDIR != os.path.dirname(CURDIR):
    471                 if os.path.isfile(CURDIR+os.sep+MANIFEST):
    472                     PROJECT=CURDIR
    473                     break
    474                 CURDIR = os.path.dirname(CURDIR)
    475 
    476             if not os.path.isdir(PROJECT):
    477                 error('Launch this script from an application project directory, or use --project=<path>.')
    478         log('Using auto-detected project path: %s' % (PROJECT))
    479 
    480     PACKAGE_NAME = extract_package_name(PROJECT+os.sep+MANIFEST)
    481     if PACKAGE_NAME is None:
    482         PACKAGE_NAME = '<none>'
    483     log('Found package name: %s' % (PACKAGE_NAME))
    484     if PACKAGE_NAME is '<none>':
    485         error('''Could not extract package name from %s.
    486        Please check that the file is well-formed!''' % (PROJECT+os.sep+MANIFEST))
    487     if OPTION_LAUNCH_LIST:
    488         log('Extracting list of launchable activities from manifest:')
    489         print(' '.join(extract_launchable(PROJECT+os.sep+MANIFEST)))
    490         exit(0)
    491     APP_ABIS = get_build_var('APP_ABI').split(' ')
    492     if 'all' in APP_ABIS:
    493         ALL_ABIS = get_build_var('NDK_ALL_ABIS').split(' ')
    494         APP_ABIS = APP_ABIS[:APP_ABIS.index('all')]+ALL_ABIS+APP_ABIS[APP_ABIS.index('all')+1:]
    495     log('ABIs targetted by application: %s' % (' '.join(APP_ABIS)))
    496 
    497     retcode,ADB_TEST = adb_cmd(True,['shell', 'ls'])
    498     if retcode != 0:
    499         print(ADB_TEST)
    500         error('''Could not connect to device or emulator!
    501        Please check that an emulator is running or a device is connected
    502        through USB to this machine. You can use -e, -d and -s <serial>
    503        in case of multiple ones.''')
    504 
    505     retcode,API_LEVEL = adb_var_shell(['getprop', 'ro.build.version.sdk'])
    506     if retcode != 0 or API_LEVEL == '':
    507         error('''Could not find target device's supported API level!
    508 ndk-gdb will only work if your device is running Android 2.2 or higher.''')
    509     API_LEVEL = int(API_LEVEL)
    510     log('Device API Level: %d' % (API_LEVEL))
    511     if API_LEVEL < 8:
    512         error('''ndk-gdb requires a target device running Android 2.2 (API level 8) or higher.
    513 The target device is running API level %d!''' % (API_LEVEL))
    514     COMPAT_ABI = []
    515     _,CPU_ABI1 = adb_var_shell(['getprop', 'ro.product.cpu.abi'])
    516     _,CPU_ABI2 = adb_var_shell(['getprop', 'ro.product.cpu.abi2'])
    517     # Both CPU_ABI1 and CPU_ABI2 may contain multiple comma-delimited abis.
    518     # Concatanate CPU_ABI1 and CPU_ABI2.
    519     CPU_ABIS = CPU_ABI1.split(',')+CPU_ABI2.split(',')
    520     log('Device CPU ABIs: %s' % (' '.join(CPU_ABIS)))
    521     COMPAT_ABI = [ABI for ABI in CPU_ABIS if ABI in APP_ABIS]
    522 
    523     if not len(COMPAT_ABI):
    524         error('''The device does not support the application's targetted CPU ABIs!
    525        Device supports:  %s
    526        Package supports: %s''' % (' '.join(CPU_ABIS),' '.join(APP_ABIS)))
    527     COMPAT_ABI = COMPAT_ABI[0]
    528     log('Compatible device ABI: %s' % (COMPAT_ABI))
    529     GDBSETUP_INIT = get_build_var_for_abi('NDK_APP_GDBSETUP', COMPAT_ABI)
    530     log('Using gdb setup init: %s' % (GDBSETUP_INIT))
    531 
    532     TOOLCHAIN_PREFIX = get_build_var_for_abi('TOOLCHAIN_PREFIX', COMPAT_ABI)
    533     log('Using toolchain prefix: %s' % (TOOLCHAIN_PREFIX))
    534 
    535     APP_OUT = get_build_var_for_abi('TARGET_OUT', COMPAT_ABI)
    536     log('Using app out directory: %s' % (APP_OUT))
    537     DEBUGGABLE = extract_debuggable(PROJECT+os.sep+MANIFEST)
    538     log('Found debuggable flag: %s' % ('true' if DEBUGGABLE==True else 'false'))
    539     # If gdbserver exists, then we built with 'ndk-build NDK_DEBUG=1' and it's
    540     # ok to not have android:debuggable set to true in the original manifest.
    541     # However, if this is not the case, then complain!!
    542     #
    543     gdbserver_path = os.path.join(PROJECT,'libs',COMPAT_ABI,'gdbserver')
    544     if not DEBUGGABLE:
    545         if os.path.isfile(gdbserver_path):
    546             log('Found gdbserver under libs/%s, assuming app was built with NDK_DEBUG=1' % (COMPAT_ABI))
    547         else:
    548             error('''Package %s is not debuggable ! You can fix that in two ways:
    549 
    550   - Rebuilt with the NDK_DEBUG=1 option when calling 'ndk-build'.
    551 
    552   - Modify your manifest to set android:debuggable attribute to "true",
    553     then rebuild normally.
    554 
    555 After one of these, re-install to the device!''' % (PACKAGE_NAME))
    556     elif not os.path.isfile(gdbserver_path):
    557         error('''Could not find gdbserver binary under %s/libs/%s
    558        This usually means you modified your AndroidManifest.xml to set
    559        the android:debuggable flag to 'true' but did not rebuild the
    560        native binaries. Please call 'ndk-build' to do so,
    561        *then* re-install to the device!''' % (PROJECT,COMPAT_ABI))
    562 
    563     # Let's check that 'gdbserver' is properly installed on the device too. If this
    564     # is not the case, the user didn't install the proper package after rebuilding.
    565     #
    566     retcode,DEVICE_GDBSERVER = adb_var_shell2(['ls', '/data/data/%s/lib/gdbserver' % (PACKAGE_NAME)])
    567     if retcode:
    568         error('''Non-debuggable application installed on the target device.
    569        Please re-install the debuggable version!''')
    570     log('Found device gdbserver: %s' % (DEVICE_GDBSERVER))
    571 
    572     # Find the <dataDir> of the package on the device
    573     retcode,DATA_DIR = adb_var_shell2(['run-as', PACKAGE_NAME, '/system/bin/sh', '-c', 'pwd'])
    574     if retcode or DATA_DIR == '':
    575         error('''Could not extract package's data directory. Are you sure that
    576        your installed application is debuggable?''')
    577     log("Found data directory: '%s'" % (DATA_DIR))
    578 
    579     # Launch the activity if needed
    580     if OPTION_START:
    581         if not OPTION_LAUNCH:
    582             OPTION_LAUNCH = extract_launchable(PROJECT+os.sep+MANIFEST)
    583             if not len(OPTION_LAUNCH):
    584                 error('''Could not extract name of launchable activity from manifest!
    585            Try to use --launch=<name> directly instead as a work-around.''')
    586             log('Found first launchable activity: %s' % (OPTION_LAUNCH[0]))
    587         if not len(OPTION_LAUNCH):
    588             error('''It seems that your Application does not have any launchable activity!
    589        Please fix your manifest file and rebuild/re-install your application.''')
    590 
    591     if len(OPTION_LAUNCH):
    592         log('Launching activity: %s/%s' % (PACKAGE_NAME,OPTION_LAUNCH[0]))
    593         retcode,LAUNCH_OUTPUT=adb_cmd(True,
    594                                       ['shell', 'am', 'start', '-n', '%s/%s' % (PACKAGE_NAME,OPTION_LAUNCH[0])],
    595                                       log_command=True)
    596         if retcode:
    597             error('''Could not launch specified activity: %s
    598        Use --launch-list to dump a list of valid values.''' % (OPTION_LAUNCH[0]))
    599 
    600         # Sleep a bit, it sometimes take one second to start properly
    601         # Note that we use the 'sleep' command on the device here.
    602         #
    603         adb_cmd(True, ['shell', 'sleep', '%f' % (DELAY)], log_command=True)
    604 
    605     # Find the PID of the application being run
    606     retcode,PID = get_pid_of(PACKAGE_NAME)
    607     log('Found running PID: %d' % (PID))
    608     if retcode or PID == 0:
    609         if len(OPTION_LAUNCH):
    610             error('''Could not extract PID of application on device/emulator.
    611        Weird, this probably means one of these:
    612 
    613          - The installed package does not match your current manifest.
    614          - The application process was terminated.
    615 
    616        Try using the --verbose option and look at its output for details.''')
    617         else:
    618             error('''Could not extract PID of application on device/emulator.
    619        Are you sure the application is already started?
    620        Consider using --start or --launch=<name> if not.''')
    621 
    622     # Check that there is no other instance of gdbserver running
    623     retcode,GDBSERVER_PID = get_pid_of('lib/gdbserver')
    624     if not retcode and not GDBSERVER_PID == 0:
    625         if not OPTION_FORCE:
    626             error('Another debug session running, Use --force to kill it.')
    627         log('Killing existing debugging session')
    628         adb_cmd(False, ['shell', 'kill -9 %s' % (GDBSERVER_PID)])
    629 
    630     # Launch gdbserver now
    631     DEBUG_SOCKET = 'debug-socket'
    632     adb_cmd(False,
    633             ['shell', 'run-as', PACKAGE_NAME, 'lib/gdbserver', '+%s' % (DEBUG_SOCKET), '--attach', str(PID)],
    634             log_command=True, adb_trace=True, background=True)
    635     log('Launched gdbserver succesfully')
    636 
    637 # Make sure gdbserver was launched - debug check.
    638 #    adb_var_shell(['sleep', '0.1'], log_command=False)
    639 #    retcode,GDBSERVER_PID = get_pid_of('lib/gdbserver')
    640 #    if retcode or GDBSERVER_PID == 0:
    641 #        error('Could not launch gdbserver on the device?')
    642 #    log('Launched gdbserver succesfully (PID=%s)' % (GDBSERVER_PID))
    643 
    644     # Setup network redirection
    645     log('Setup network redirection')
    646     retcode,_ = adb_cmd(False,
    647                         ['forward', 'tcp:%d' % (DEBUG_PORT), 'localfilesystem:%s/%s' % (DATA_DIR,DEBUG_SOCKET)],
    648                         log_command=True)
    649     if retcode:
    650         error('''Could not setup network redirection to gdbserver?
    651        Maybe using --port=<port> to use a different TCP port might help?''')
    652 
    653     # Get the app_server binary from the device
    654     APP_PROCESS = '%s/app_process' % (APP_OUT)
    655     adb_cmd(False, ['pull', '/system/bin/app_process', APP_PROCESS], log_command=True)
    656     log('Pulled app_process from device/emulator.')
    657 
    658     adb_cmd(False, ['pull', '/system/bin/linker', '%s/linker' % (APP_OUT)], log_command=True)
    659     log('Pulled linker from device/emulator.')
    660 
    661     adb_cmd(False, ['pull', '/system/lib/libc.so', '%s/libc.so' % (APP_OUT)], log_command=True)
    662     log('Pulled libc.so from device/emulator.')
    663 
    664     # Now launch the appropriate gdb client with the right init commands
    665     #
    666     GDBCLIENT = '%sgdb' % (TOOLCHAIN_PREFIX)
    667     GDBSETUP = '%s/gdb.setup' % (APP_OUT)
    668     shutil.copyfile(GDBSETUP_INIT, GDBSETUP)
    669     with open(GDBSETUP, "a") as gdbsetup:
    670         #uncomment the following to debug the remote connection only
    671         #echo "set debug remote 1" >> $GDBSETUP
    672         gdbsetup.write('file '+APP_PROCESS+'\n')
    673         gdbsetup.write('target remote :%d\n' % (DEBUG_PORT))
    674         if OPTION_EXEC:
    675             with open(OPTION_EXEC, 'r') as execfile:
    676                 for line in execfile.readline():
    677                     gdbsetup.write(line)
    678     gdbsetup.close()
    679 
    680     gdbargs = [GDBCLIENT, '-x', '%s' % (GDBSETUP)]
    681     if OPTION_TUI:
    682         gdbhelp = subprocess.check_output([GDBCLIENT, '--help']).decode('ascii')
    683         try:
    684             gdbhelp.index('--tui')
    685             gdbargs.append('--tui')
    686             OPTION_TUI = 'running'
    687         except:
    688             print('Warning: Disabled tui mode as %s does not support it' % (os.path.basename(GDBCLIENT)))
    689     subprocess.call(gdbargs)
    690 
    691 if __name__ == '__main__':
    692     main()
    693