Home | History | Annotate | Download | only in ndk
      1 #!/usr/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, time
     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 ADB_CMD = None
     67 GNUMAKE_CMD = None
     68 JDB_CMD = None
     69 # Extra arguments passed to the NDK build system when
     70 # querying it.
     71 GNUMAKE_FLAGS = []
     72 
     73 OPTION_FORCE = None
     74 OPTION_EXEC = None
     75 OPTION_START = None
     76 OPTION_LAUNCH = None
     77 OPTION_LAUNCH_LIST = None
     78 OPTION_TUI = None
     79 OPTION_WAIT = ['-D']
     80 OPTION_STDCXXPYPR = None
     81 
     82 PYPRPR_BASE = sys.prefix + '/share/pretty-printers/'
     83 PYPRPR_GNUSTDCXX_BASE = PYPRPR_BASE + 'libstdcxx/'
     84 
     85 DEBUG_PORT = 5039
     86 JDB_PORT = 65534
     87 
     88 # Name of the manifest file
     89 MANIFEST = 'AndroidManifest.xml'
     90 
     91 # Delay in seconds between launching the activity and attaching gdbserver on it.
     92 # This is needed because there is no way to know when the activity has really
     93 # started, and sometimes this takes a few seconds.
     94 #
     95 DELAY = 2.0
     96 NDK = os.path.abspath(os.path.dirname(sys.argv[0])).replace('\\','/')
     97 DEVICE_SERIAL = ''
     98 ADB_FLAGS = ''
     99 
    100 def log(string):
    101     global VERBOSE
    102     if VERBOSE:
    103         print(string)
    104 
    105 def error(string, errcode=1):
    106     print('ERROR: %s' % (string))
    107     exit(errcode)
    108 
    109 def handle_args():
    110     global VERBOSE, DEBUG_PORT, DELAY, DEVICE_SERIAL
    111     global GNUMAKE_CMD, GNUMAKE_FLAGS
    112     global ADB_CMD, ADB_FLAGS
    113     global JDB_CMD
    114     global PROJECT, NDK
    115     global OPTION_START, OPTION_LAUNCH, OPTION_LAUNCH_LIST
    116     global OPTION_FORCE, OPTION_EXEC, OPTION_TUI, OPTION_WAIT
    117     global OPTION_STDCXXPYPR
    118     global PYPRPR_GNUSTDCXX_BASE
    119 
    120     parser = argparse.ArgumentParser(description='''
    121 Setup a gdb debugging session for your Android NDK application.
    122 Read ''' + NDK + '''/docs/NDK-GDB.html for complete usage instructions.''',
    123     formatter_class=argparse.RawTextHelpFormatter)
    124 
    125     parser.add_argument( '--verbose',
    126                          help='Enable verbose mode', action='store_true', dest='verbose')
    127 
    128     parser.add_argument( '--force',
    129                          help='Kill existing debug session if it exists',
    130                          action='store_true')
    131 
    132     parser.add_argument( '--start',
    133                          help='Launch application instead of attaching to existing one',
    134                          action='store_true')
    135 
    136     parser.add_argument( '--launch',
    137                          help='Same as --start, but specify activity name (see below)',
    138                          dest='launch_name', nargs=1)
    139 
    140     parser.add_argument( '--launch-list',
    141                          help='List all launchable activity names from manifest',
    142                          action='store_true')
    143 
    144     parser.add_argument( '--delay',
    145                          help='Delay in seconds between activity start and gdbserver attach',
    146                          type=float, default=DELAY,
    147                          dest='delay')
    148 
    149     parser.add_argument( '-p', '--project',
    150                          help='Specify application project path',
    151                          dest='project')
    152 
    153     parser.add_argument( '--port',
    154                          help='Use tcp:localhost:<DEBUG_PORT> to communicate with gdbserver',
    155                          type=int, default=DEBUG_PORT,
    156                          dest='debug_port')
    157 
    158     parser.add_argument( '-x', '--exec',
    159                          help='Execute gdb initialization commands in <EXEC_FILE> after connection',
    160                          dest='exec_file')
    161 
    162     parser.add_argument( '--adb',
    163                          help='Use specific adb command',
    164                          dest='adb_cmd')
    165 
    166     parser.add_argument( '--awk',
    167                          help='Use specific awk command (unused flag retained for compatability)')
    168 
    169     parser.add_argument( '-e',
    170                          help='Connect to single emulator instance....(either this,)',
    171                          action='store_true', dest='emulator')
    172 
    173     parser.add_argument( '-d',
    174                          help='Connect to single target device........(this,)',
    175                          action='store_true', dest='device')
    176 
    177     parser.add_argument( '-s',
    178                          help='Connect to specific emulator or device.(or this)',
    179                          default=DEVICE_SERIAL,
    180                          dest='device_serial')
    181 
    182     parser.add_argument( '-t','--tui',
    183                          help='Use tui mode',
    184                          action='store_true', dest='tui')
    185 
    186     parser.add_argument( '--gnumake-flag',
    187                          help='Flag to pass to gnumake, e.g. NDK_TOOLCHAIN_VERSION=4.8',
    188                          action='append', dest='gnumake_flags')
    189 
    190     parser.add_argument( '--nowait',
    191                          help='Do not wait for debugger to attach (may miss early JNI breakpoints)',
    192                          action='store_true', dest='nowait')
    193 
    194     stdcxx_pypr_versions = [ 'gnustdcxx'+d.replace('gcc','')
    195                              for d in os.listdir(PYPRPR_GNUSTDCXX_BASE)
    196                              if os.path.isdir(os.path.join(PYPRPR_GNUSTDCXX_BASE, d)) ]
    197 
    198     parser.add_argument( '--stdcxx-py-pr',
    199                          help='Specify stdcxx python pretty-printer',
    200                          choices=['auto', 'none', 'gnustdcxx'] + stdcxx_pypr_versions + ['stlport'],
    201                          default='none', dest='stdcxxpypr')
    202 
    203     args = parser.parse_args()
    204 
    205     VERBOSE = args.verbose
    206 
    207     ndk_bin = ndk_bin_path(NDK)
    208     (found_adb,     ADB_CMD)     = find_program('adb',    [ndk_bin])
    209     (found_gnumake, GNUMAKE_CMD) = find_program('make',   [ndk_bin])
    210     (found_jdb,     JDB_CMD)     = find_program('jdb',    [])
    211 
    212     if not found_gnumake:
    213         error('Failed to find GNU make')
    214 
    215     log('Android NDK installation path: %s' % (NDK))
    216 
    217     if args.device:
    218         ADB_FLAGS = '-d'
    219     if args.emulator:
    220         if ADB_FLAGS != '':
    221             parser.print_help()
    222             exit(1)
    223         ADB_FLAGS = '-e'
    224     if args.device_serial != '':
    225         DEVICE_SERIAL = args.device_serial
    226         if ADB_FLAGS != '':
    227             parser.print_help()
    228             exit(1)
    229         ADB_FLAGS = '-s'
    230     if args.adb_cmd != None:
    231         log('Using specific adb command: %s' % (args.adb_cmd))
    232         ADB_CMD = args.adb_cmd
    233     if ADB_CMD is None:
    234         error('''The 'adb' tool is not in your path.
    235        You can change your PATH variable, or use
    236        --adb=<executable> to point to a valid one.''')
    237     if not os.path.isfile(ADB_CMD):
    238         error('Could not run ADB with: %s' % (ADB_CMD))
    239 
    240     if args.project != None:
    241         PROJECT = args.project
    242 
    243     if args.start != None:
    244         OPTION_START = args.start
    245 
    246     if args.launch_name != None:
    247         OPTION_LAUNCH = args.launch_name
    248 
    249     if args.launch_list != None:
    250         OPTION_LAUNCH_LIST = args.launch_list
    251 
    252     if args.force != None:
    253         OPTION_FORCE = args.force
    254 
    255     if args.exec_file != None:
    256         OPTION_EXEC = args.exec_file
    257 
    258     if args.tui != False:
    259         OPTION_TUI = True
    260 
    261     if args.delay != None:
    262         DELAY = args.delay
    263 
    264     if args.gnumake_flags != None:
    265         GNUMAKE_FLAGS = args.gnumake_flags
    266 
    267     if args.nowait == True:
    268         OPTION_WAIT = []
    269     elif not found_jdb:
    270         error('Failed to find jdb.\n..you can use --nowait to disable jdb\n..but may miss early breakpoints.')
    271 
    272     OPTION_STDCXXPYPR = args.stdcxxpypr
    273 
    274 def get_build_var(var):
    275     global GNUMAKE_CMD, GNUMAKE_FLAGS, NDK, PROJECT
    276     text = subprocess.check_output([GNUMAKE_CMD,
    277                                   '--no-print-dir',
    278                                   '-f',
    279                                   NDK+'/build/core/build-local.mk',
    280                                   '-C',
    281                                   PROJECT,
    282                                   'DUMP_'+var] + GNUMAKE_FLAGS
    283                                   )
    284     # replace('\r', '') due to Windows crlf (\r\n)
    285     #  ...universal_newlines=True causes bytes to be returned
    286     #     rather than a str
    287     return text.decode('ascii').replace('\r', '').splitlines()[0]
    288 
    289 def get_build_var_for_abi(var, abi):
    290     global GNUMAKE_CMD, GNUMAKE_FLAGS, NDK, PROJECT
    291     text = subprocess.check_output([GNUMAKE_CMD,
    292                                    '--no-print-dir',
    293                                    '-f',
    294                                    NDK+'/build/core/build-local.mk',
    295                                    '-C',
    296                                    PROJECT,
    297                                    'DUMP_'+var,
    298                                    'APP_ABI='+abi] + GNUMAKE_FLAGS,
    299                                    )
    300     return text.decode('ascii').replace('\r', '').splitlines()[0]
    301 
    302 # Silent if gdb is running in tui mode to keep things tidy.
    303 def output_gdbserver(text):
    304     if not OPTION_TUI or OPTION_TUI != 'running':
    305         print(text)
    306 
    307 # Likewise, silent in tui mode (also prepends 'JDB :: ')
    308 def output_jdb(text):
    309     if not OPTION_TUI or OPTION_TUI != 'running':
    310         print('JDB :: %s' % text)
    311 
    312 def input_jdb(inhandle):
    313     while True:
    314         inhandle.write('\n')
    315         time.sleep(1.0)
    316 
    317 def background_spawn(args, redirect_stderr, output_fn, redirect_stdin = None, input_fn = None):
    318 
    319     def async_stdout(outhandle, queue, output_fn):
    320         for line in iter(outhandle.readline, b''):
    321             output_fn(line.replace('\r', '').replace('\n', ''))
    322         outhandle.close()
    323 
    324     def async_stderr(outhandle, queue, output_fn):
    325         for line in iter(outhandle.readline, b''):
    326             output_fn(line.replace('\r', '').replace('\n', ''))
    327         outhandle.close()
    328 
    329     def async_stdin(inhandle, queue, input_fn):
    330         input_fn(inhandle)
    331         inhandle.close()
    332 
    333     if redirect_stderr:
    334         used_stderr = subprocess.PIPE
    335     else:
    336         used_stderr = subprocess.STDOUT
    337     if redirect_stdin:
    338         used_stdin = subprocess.PIPE
    339     else:
    340         used_stdin = None
    341     p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=used_stderr, stdin=used_stdin,
    342                          bufsize=1, close_fds='posix' in sys.builtin_module_names)
    343     qo = Queue()
    344     to = Thread(target=async_stdout, args=(p.stdout, qo, output_fn))
    345     to.daemon = True
    346     to.start()
    347     if redirect_stderr:
    348         te = Thread(target=async_stderr, args=(p.stderr, qo, output_fn))
    349         te.daemon = True
    350         te.start()
    351     if redirect_stdin:
    352         ti = Thread(target=async_stdin, args=(p.stdin, qo, input_fn))
    353         ti.daemon = True
    354         ti.start()
    355 
    356 def adb_cmd(redirect_stderr, args, log_command=False, adb_trace=False, background=False):
    357     global ADB_CMD, ADB_FLAGS, DEVICE_SERIAL
    358     fullargs = [ADB_CMD]
    359     if ADB_FLAGS != '':
    360         fullargs += [ADB_FLAGS]
    361     if DEVICE_SERIAL != '':
    362         fullargs += [DEVICE_SERIAL]
    363     if isinstance(args, str):
    364         fullargs.append(args)
    365     else:
    366         fullargs += [arg for arg in args]
    367     new_env = os.environ.copy()
    368     retval = 0
    369     if adb_trace:
    370         new_env["ADB_TRACE"] = "1"
    371     if background:
    372         if log_command:
    373             log('## COMMAND: adb_cmd %s [BACKGROUND]' % (' '.join(args)))
    374         background_spawn(fullargs, redirect_stderr, output_gdbserver)
    375         return 0, ''
    376     else:
    377         if log_command:
    378             log('## COMMAND: adb_cmd %s' % (' '.join(args)))
    379         try:
    380             if redirect_stderr:
    381                 text = subprocess.check_output(fullargs,
    382                                                stderr=subprocess.STDOUT,
    383                                                env=new_env
    384                                                )
    385             else:
    386                 text = subprocess.check_output(fullargs,
    387                                                env=new_env
    388                                                )
    389         except subprocess.CalledProcessError as e:
    390             retval = e.returncode
    391             text = e.output
    392         # rstrip() because of final newline.
    393         return retval, text.decode('ascii').replace('\r', '').rstrip()
    394 
    395 def _adb_var_shell(args, redirect_stderr=False, log_command=True):
    396     if log_command:
    397         log('## COMMAND: adb_cmd shell %s' % (' '.join(args)))
    398     arg_str = str(' '.join(args)+' ; echo $?')
    399     adb_ret,output = adb_cmd(redirect_stderr=redirect_stderr,
    400                            args=['shell', arg_str], log_command=False)
    401     output = output.splitlines()
    402     retcode = int(output.pop())
    403     return retcode,'\n'.join(output)
    404 
    405 def adb_var_shell(args, log_command=False):
    406     return _adb_var_shell(args, redirect_stderr=False, log_command=log_command)
    407 
    408 def adb_var_shell2(args, log_command=False):
    409     return _adb_var_shell(args, redirect_stderr=True, log_command=log_command)
    410 
    411 # Return the PID of a given package or program, or 0 if it doesn't run
    412 # $1: Package name ("com.example.hellojni") or program name ("/lib/gdbserver")
    413 # Out: PID number, or 0 if not running
    414 #
    415 def get_pid_of(package_name):
    416     '''
    417     Some custom ROMs use busybox instead of toolbox for ps.
    418     Without -w, busybox truncates the output, and very long
    419     package names like com.exampleisverylongtoolongbyfar.plasma
    420     exceed the limit.
    421     '''
    422     ps_command = 'ps'
    423     retcode,output = adb_cmd(False, ['shell', 'readlink $(which ps)'])
    424     if output:
    425         output = output.replace('\r', '').splitlines()[0]
    426     if output == 'busybox':
    427         ps_command = 'ps -w'
    428     retcode,output = adb_cmd(False,['shell', ps_command])
    429     output = output.replace('\r', '').splitlines()
    430     columns = output.pop(0).split()
    431     try:
    432         PID_column = columns.index('PID')
    433     except:
    434         PID_column = 1
    435     while output:
    436         columns = output.pop().split()
    437         if columns.pop() == package_name:
    438             return 0,int(columns[PID_column])
    439     return 1,0
    440 
    441 def extract_package_name(xmlfile):
    442     '''
    443     The name itself is the value of the 'package' attribute in the
    444     'manifest' element.
    445     '''
    446     tree = ElementTree.ElementTree(file=xmlfile)
    447     root = tree.getroot()
    448     if 'package' in root.attrib:
    449         return root.attrib['package']
    450     return None
    451 
    452 def extract_debuggable(xmlfile):
    453     '''
    454     simply extract the 'android:debuggable' attribute value from
    455     the first <manifest><application> element we find.
    456     '''
    457     tree = ElementTree.ElementTree(file=xmlfile)
    458     root = tree.getroot()
    459     for application in root.iter('application'):
    460         for k in application.attrib.keys():
    461             if str(k).endswith('debuggable'):
    462                 return application.attrib[k] == 'true'
    463     return False
    464 
    465 def extract_launchable(xmlfile):
    466     '''
    467     A given application can have several activities, and each activity
    468     can have several intent filters. We want to only list, in the final
    469     output, the activities which have a intent-filter that contains the
    470     following elements:
    471 
    472       <action android:name="android.intent.action.MAIN" />
    473       <category android:name="android.intent.category.LAUNCHER" />
    474     '''
    475     tree = ElementTree.ElementTree(file=xmlfile)
    476     root = tree.getroot()
    477     launchable_activities = []
    478     for application in root.iter('application'):
    479         for activity in application.iter('activity'):
    480             for intent_filter in activity.iter('intent-filter'):
    481                 found_action_MAIN = False
    482                 found_category_LAUNCHER = False
    483                 for child in intent_filter:
    484                     if child.tag == 'action':
    485                         if True in [str(child.attrib[k]).endswith('MAIN') for k in child.attrib.keys()]:
    486                             found_action_MAIN = True
    487                     if child.tag == 'category':
    488                         if True in [str(child.attrib[k]).endswith('LAUNCHER') for k in child.attrib.keys()]:
    489                             found_category_LAUNCHER = True
    490                 if found_action_MAIN and found_category_LAUNCHER:
    491                     names = [str(activity.attrib[k]) for k in activity.attrib.keys() if str(k).endswith('name')]
    492                     for name in names:
    493                         if name[0] != '.':
    494                             name = '.'+name
    495                         launchable_activities.append(name)
    496     return launchable_activities
    497 
    498 def main():
    499     global ADB_CMD, NDK, PROJECT
    500     global JDB_CMD
    501     global OPTION_START, OPTION_LAUNCH, OPTION_LAUNCH_LIST
    502     global OPTION_FORCE, OPTION_EXEC, OPTION_TUI, OPTION_WAIT
    503     global OPTION_STDCXXPYPR
    504     global PYPRPR_BASE, PYPRPR_GNUSTDCXX_BASE
    505 
    506     if NDK.find(' ')!=-1:
    507         error('NDK path cannot contain space')
    508     handle_args()
    509     if OPTION_EXEC:
    510         if not os.path.isfile(OPTION_EXEC):
    511             error('Invalid initialization file: %s' % (OPTION_EXEC))
    512     ADB_VERSION = subprocess.check_output([ADB_CMD, 'version'],
    513                                         ).decode('ascii').replace('\r', '').splitlines()[0]
    514     log('ADB version found: %s' % (ADB_VERSION))
    515     if DEVICE_SERIAL == '':
    516         log('Using ADB flags: %s' % (ADB_FLAGS))
    517     else:
    518         log('Using ADB flags: %s "%s"' % (ADB_FLAGS,DEVICE_SERIAL))
    519     if PROJECT != None:
    520         log('Using specified project path: %s' % (PROJECT))
    521         if not os.path.isdir(PROJECT):
    522             error('Your --project option does not point to a directory!')
    523         if not os.path.isfile(PROJECT+os.sep+MANIFEST):
    524             error('''Your --project does not point to an Android project path!
    525        It is missing a %s file.''' % (MANIFEST))
    526     else:
    527         # Assume we are in the project directory
    528         if os.path.isfile(MANIFEST):
    529             PROJECT = '.'
    530         else:
    531             PROJECT = ''
    532             CURDIR = os.getcwd()
    533 
    534             while CURDIR != os.path.dirname(CURDIR):
    535                 if os.path.isfile(CURDIR+os.sep+MANIFEST):
    536                     PROJECT=CURDIR
    537                     break
    538                 CURDIR = os.path.dirname(CURDIR)
    539 
    540             if not os.path.isdir(PROJECT):
    541                 error('Launch this script from an application project directory, or use --project=<path>.')
    542         log('Using auto-detected project path: %s' % (PROJECT))
    543 
    544     PACKAGE_NAME = extract_package_name(PROJECT+os.sep+MANIFEST)
    545     if PACKAGE_NAME is None:
    546         PACKAGE_NAME = '<none>'
    547     log('Found package name: %s' % (PACKAGE_NAME))
    548     if PACKAGE_NAME == '<none>':
    549         error('''Could not extract package name from %s.
    550        Please check that the file is well-formed!''' % (PROJECT+os.sep+MANIFEST))
    551     if OPTION_LAUNCH_LIST:
    552         log('Extracting list of launchable activities from manifest:')
    553         print(' '.join(extract_launchable(PROJECT+os.sep+MANIFEST)))
    554         exit(0)
    555     APP_ABIS = get_build_var('APP_ABI').split(' ')
    556     if 'all' in APP_ABIS:
    557         ALL_ABIS = get_build_var('NDK_ALL_ABIS').split(' ')
    558         APP_ABIS = APP_ABIS[:APP_ABIS.index('all')]+ALL_ABIS+APP_ABIS[APP_ABIS.index('all')+1:]
    559     log('ABIs targetted by application: %s' % (' '.join(APP_ABIS)))
    560 
    561     retcode,ADB_TEST = adb_cmd(True,['shell', 'ls'])
    562     if retcode != 0:
    563         print(ADB_TEST)
    564         error('''Could not connect to device or emulator!
    565        Please check that an emulator is running or a device is connected
    566        through USB to this machine. You can use -e, -d and -s <serial>
    567        in case of multiple ones.''')
    568 
    569     retcode,API_LEVEL = adb_var_shell(['getprop', 'ro.build.version.sdk'])
    570     if retcode != 0 or API_LEVEL == '':
    571         error('''Could not find target device's supported API level!
    572 ndk-gdb will only work if your device is running Android 2.2 or higher.''')
    573     API_LEVEL = int(API_LEVEL)
    574     log('Device API Level: %d' % (API_LEVEL))
    575     if API_LEVEL < 8:
    576         error('''ndk-gdb requires a target device running Android 2.2 (API level 8) or higher.
    577 The target device is running API level %d!''' % (API_LEVEL))
    578     COMPAT_ABI = []
    579     _,CPU_ABI1 = adb_var_shell(['getprop', 'ro.product.cpu.abi'])
    580     _,CPU_ABI2 = adb_var_shell(['getprop', 'ro.product.cpu.abi2'])
    581     # Both CPU_ABI1 and CPU_ABI2 may contain multiple comma-delimited abis.
    582     # Concatanate CPU_ABI1 and CPU_ABI2.
    583     CPU_ABIS = CPU_ABI1.split(',')+CPU_ABI2.split(',')
    584     log('Device CPU ABIs: %s' % (' '.join(CPU_ABIS)))
    585     COMPAT_ABI = [ABI for ABI in CPU_ABIS if ABI in APP_ABIS]
    586 
    587     if not len(COMPAT_ABI):
    588         error('''The device does not support the application's targetted CPU ABIs!
    589        Device supports:  %s
    590        Package supports: %s''' % (' '.join(CPU_ABIS),' '.join(APP_ABIS)))
    591     COMPAT_ABI = COMPAT_ABI[0]
    592     log('Compatible device ABI: %s' % (COMPAT_ABI))
    593     GDBSETUP_INIT = get_build_var_for_abi('NDK_APP_GDBSETUP', COMPAT_ABI)
    594     log('Using gdb setup init: %s' % (GDBSETUP_INIT))
    595 
    596     TOOLCHAIN_PREFIX = get_build_var_for_abi('TOOLCHAIN_PREFIX', COMPAT_ABI)
    597     log('Using toolchain prefix: %s' % (TOOLCHAIN_PREFIX))
    598 
    599     APP_OUT = get_build_var_for_abi('TARGET_OUT', COMPAT_ABI)
    600     log('Using app out directory: %s' % (APP_OUT))
    601     DEBUGGABLE = extract_debuggable(PROJECT+os.sep+MANIFEST)
    602     log('Found debuggable flag: %s' % ('true' if DEBUGGABLE==True else 'false'))
    603     # If gdbserver exists, then we built with 'ndk-build NDK_DEBUG=1' and it's
    604     # ok to not have android:debuggable set to true in the original manifest.
    605     # However, if this is not the case, then complain!!
    606     #
    607     gdbserver_path = os.path.join(PROJECT,'libs',COMPAT_ABI,'gdbserver')
    608     if not DEBUGGABLE:
    609         if os.path.isfile(gdbserver_path):
    610             log('Found gdbserver under libs/%s, assuming app was built with NDK_DEBUG=1' % (COMPAT_ABI))
    611         else:
    612             error('''Package %s is not debuggable ! You can fix that in two ways:
    613 
    614   - Rebuilt with the NDK_DEBUG=1 option when calling 'ndk-build'.
    615 
    616   - Modify your manifest to set android:debuggable attribute to "true",
    617     then rebuild normally.
    618 
    619 After one of these, re-install to the device!''' % (PACKAGE_NAME))
    620     elif not os.path.isfile(gdbserver_path):
    621         error('''Could not find gdbserver binary under %s/libs/%s
    622        This usually means you modified your AndroidManifest.xml to set
    623        the android:debuggable flag to 'true' but did not rebuild the
    624        native binaries. Please call 'ndk-build' to do so,
    625        *then* re-install to the device!''' % (PROJECT,COMPAT_ABI))
    626 
    627     # Let's check that 'gdbserver' is properly installed on the device too. If this
    628     # is not the case, the user didn't install the proper package after rebuilding.
    629     #
    630     retcode,DEVICE_GDBSERVER = adb_var_shell2(['ls', '/data/data/%s/lib/gdbserver' % (PACKAGE_NAME)])
    631     if retcode:
    632         error('''Non-debuggable application installed on the target device.
    633        Please re-install the debuggable version!''')
    634     log('Found device gdbserver: %s' % (DEVICE_GDBSERVER))
    635 
    636     # Find the <dataDir> of the package on the device
    637     retcode,DATA_DIR = adb_var_shell2(['run-as', PACKAGE_NAME, '/system/bin/sh', '-c', 'pwd'])
    638     if retcode or DATA_DIR == '':
    639         error('''Could not extract package's data directory. Are you sure that
    640        your installed application is debuggable?''')
    641     log("Found data directory: '%s'" % (DATA_DIR))
    642 
    643     # Launch the activity if needed
    644     if OPTION_START:
    645         if not OPTION_LAUNCH:
    646             OPTION_LAUNCH = extract_launchable(PROJECT+os.sep+MANIFEST)
    647             if not len(OPTION_LAUNCH):
    648                 error('''Could not extract name of launchable activity from manifest!
    649            Try to use --launch=<name> directly instead as a work-around.''')
    650             log('Found first launchable activity: %s' % (OPTION_LAUNCH[0]))
    651         if not len(OPTION_LAUNCH):
    652             error('''It seems that your Application does not have any launchable activity!
    653        Please fix your manifest file and rebuild/re-install your application.''')
    654 
    655     if OPTION_LAUNCH:
    656         log('Launching activity: %s/%s' % (PACKAGE_NAME,OPTION_LAUNCH[0]))
    657         retcode,LAUNCH_OUTPUT=adb_cmd(True,
    658                                       ['shell', 'am', 'start'] + OPTION_WAIT + ['-n', '%s/%s' % (PACKAGE_NAME,OPTION_LAUNCH[0])],
    659                                       log_command=True)
    660         if retcode:
    661             error('''Could not launch specified activity: %s
    662        Use --launch-list to dump a list of valid values.''' % (OPTION_LAUNCH[0]))
    663 
    664         # Sleep a bit, it sometimes take one second to start properly
    665         # Note that we use the 'sleep' command on the device here.
    666         #
    667         adb_cmd(True, ['shell', 'sleep', '%f' % (DELAY)], log_command=True)
    668 
    669     # Find the PID of the application being run
    670     retcode,PID = get_pid_of(PACKAGE_NAME)
    671     log('Found running PID: %d' % (PID))
    672     if retcode or PID == 0:
    673         if OPTION_LAUNCH:
    674             error('''Could not extract PID of application on device/emulator.
    675        Weird, this probably means one of these:
    676 
    677          - The installed package does not match your current manifest.
    678          - The application process was terminated.
    679 
    680        Try using the --verbose option and look at its output for details.''')
    681         else:
    682             error('''Could not extract PID of application on device/emulator.
    683        Are you sure the application is already started?
    684        Consider using --start or --launch=<name> if not.''')
    685 
    686     # Check that there is no other instance of gdbserver running
    687     retcode,GDBSERVER_PID = get_pid_of('lib/gdbserver')
    688     if not retcode and not GDBSERVER_PID == 0:
    689         if not OPTION_FORCE:
    690             error('Another debug session running, Use --force to kill it.')
    691         log('Killing existing debugging session')
    692         adb_cmd(False, ['shell', 'kill -9 %s' % (GDBSERVER_PID)])
    693 
    694     # Launch gdbserver now
    695     DEBUG_SOCKET = 'debug-socket'
    696     adb_cmd(False,
    697             ['shell', 'run-as', PACKAGE_NAME, 'lib/gdbserver', '+%s' % (DEBUG_SOCKET), '--attach', str(PID)],
    698             log_command=True, adb_trace=True, background=True)
    699     log('Launched gdbserver succesfully.')
    700 
    701 # Make sure gdbserver was launched - debug check.
    702 #    adb_var_shell(['sleep', '0.1'], log_command=False)
    703 #    retcode,GDBSERVER_PID = get_pid_of('lib/gdbserver')
    704 #    if retcode or GDBSERVER_PID == 0:
    705 #        error('Could not launch gdbserver on the device?')
    706 #    log('Launched gdbserver succesfully (PID=%s)' % (GDBSERVER_PID))
    707 
    708     # Setup network redirection
    709     log('Setup network redirection')
    710     retcode,_ = adb_cmd(False,
    711                         ['forward', 'tcp:%d' % (DEBUG_PORT), 'localfilesystem:%s/%s' % (DATA_DIR,DEBUG_SOCKET)],
    712                         log_command=True)
    713     if retcode:
    714         error('''Could not setup network redirection to gdbserver?
    715        Maybe using --port=<port> to use a different TCP port might help?''')
    716 
    717     # Get the app_server binary from the device
    718     APP_PROCESS = '%s/app_process' % (APP_OUT)
    719     adb_cmd(False, ['pull', '/system/bin/app_process', APP_PROCESS], log_command=True)
    720     log('Pulled app_process from device/emulator.')
    721 
    722     adb_cmd(False, ['pull', '/system/bin/linker', '%s/linker' % (APP_OUT)], log_command=True)
    723     log('Pulled linker from device/emulator.')
    724 
    725     adb_cmd(False, ['pull', '/system/lib/libc.so', '%s/libc.so' % (APP_OUT)], log_command=True)
    726     log('Pulled libc.so from device/emulator.')
    727 
    728     # Setup JDB connection, for --start or --launch
    729     if (OPTION_START != None or OPTION_LAUNCH != None) and len(OPTION_WAIT):
    730         log('Set up JDB connection, using jdb command: %s' % JDB_CMD)
    731         retcode,_ = adb_cmd(False,
    732                             ['forward', 'tcp:%d' % (JDB_PORT), 'jdwp:%d' % (PID)],
    733                             log_command=True)
    734         time.sleep(1.0)
    735         if retcode:
    736             error('Could not forward JDB port')
    737         background_spawn([JDB_CMD,'-connect','com.sun.jdi.SocketAttach:hostname=localhost,port=%d' % (JDB_PORT)], True, output_jdb, True, input_jdb)
    738         time.sleep(1.0)
    739 
    740     # Work out the python pretty printer details.
    741     pypr_folder = None
    742     pypr_function = None
    743 
    744     # Automatic determination of pypr.
    745     if OPTION_STDCXXPYPR == 'auto':
    746         libdir = os.path.join(PROJECT,'libs',COMPAT_ABI)
    747         libs = [ f for f in os.listdir(libdir)
    748                  if os.path.isfile(os.path.join(libdir, f)) and f.endswith('.so') ]
    749         if 'libstlport_shared.so' in libs:
    750             OPTION_STDCXXPYPR = 'stlport'
    751         elif 'libgnustl_shared.so' in libs:
    752             OPTION_STDCXXPYPR = 'gnustdcxx'
    753 
    754     if OPTION_STDCXXPYPR == 'stlport':
    755         pypr_folder = PYPRPR_BASE + 'stlport/gppfs-0.2/stlport'
    756         pypr_function = 'register_stlport_printers'
    757     elif OPTION_STDCXXPYPR.startswith('gnustdcxx'):
    758         if OPTION_STDCXXPYPR == 'gnustdcxx':
    759             NDK_TOOLCHAIN_VERSION = get_build_var_for_abi('NDK_TOOLCHAIN_VERSION', COMPAT_ABI)
    760             log('Using toolchain version: %s' % (NDK_TOOLCHAIN_VERSION))
    761             pypr_folder = PYPRPR_GNUSTDCXX_BASE + 'gcc-' + NDK_TOOLCHAIN_VERSION
    762         else:
    763             pypr_folder = PYPRPR_GNUSTDCXX_BASE + OPTION_STDCXXPYPR.replace('gnustdcxx-','gcc-')
    764         pypr_function = 'register_libstdcxx_printers'
    765 
    766     # Now launch the appropriate gdb client with the right init commands
    767     #
    768     GDBCLIENT = '%sgdb' % (TOOLCHAIN_PREFIX)
    769     GDBSETUP = '%s/gdb.setup' % (APP_OUT)
    770     shutil.copyfile(GDBSETUP_INIT, GDBSETUP)
    771     with open(GDBSETUP, "a") as gdbsetup:
    772         #uncomment the following to debug the remote connection only
    773         #gdbsetup.write('set debug remote 1\n')
    774         gdbsetup.write('file '+APP_PROCESS+'\n')
    775         gdbsetup.write('target remote :%d\n' % (DEBUG_PORT))
    776         gdbsetup.write('set breakpoint pending on\n')
    777 
    778         if pypr_function:
    779             gdbsetup.write('python\n')
    780             gdbsetup.write('import sys\n')
    781             gdbsetup.write('sys.path.append("%s")\n' % pypr_folder)
    782             gdbsetup.write('from printers import %s\n' % pypr_function)
    783             gdbsetup.write('%s(None)\n' % pypr_function)
    784             gdbsetup.write('end\n')
    785 
    786         if OPTION_EXEC:
    787             with open(OPTION_EXEC, 'r') as execfile:
    788                 for line in execfile:
    789                     gdbsetup.write(line)
    790     gdbsetup.close()
    791 
    792     gdbargs = [GDBCLIENT, '-x', '%s' % (GDBSETUP)]
    793     if OPTION_TUI:
    794         gdbhelp = subprocess.check_output([GDBCLIENT, '--help']).decode('ascii')
    795         try:
    796             gdbhelp.index('--tui')
    797             gdbargs.append('--tui')
    798             OPTION_TUI = 'running'
    799         except:
    800             print('Warning: Disabled tui mode as %s does not support it' % (os.path.basename(GDBCLIENT)))
    801     gdbp = subprocess.Popen(gdbargs)
    802     while gdbp.returncode is None:
    803         try:
    804             gdbp.communicate()
    805         except KeyboardInterrupt:
    806             pass
    807     log("Exited gdb, returncode %d" % gdbp.returncode)
    808 
    809 if __name__ == '__main__':
    810     main()
    811