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