1#!/usr/bin/env python
2
3r'''
4 Copyright (C) 2010 The Android Open Source Project
5 Copyright (C) 2012 Ray Donnelly <mingw.android@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
31import sys, os, argparse, subprocess, types
32import xml.etree.cElementTree as ElementTree
33import shutil, time
34from threading import Thread
35try:
36    from Queue import Queue, Empty
37except ImportError:
38    from queue import Queue, Empty  # python 3.x
39
40def 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.
55def 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
64VERBOSE = False
65PROJECT = None
66ADB_CMD = None
67GNUMAKE_CMD = None
68JDB_CMD = None
69# Extra arguments passed to the NDK build system when
70# querying it.
71GNUMAKE_FLAGS = []
72
73OPTION_FORCE = None
74OPTION_EXEC = None
75OPTION_START = None
76OPTION_LAUNCH = None
77OPTION_LAUNCH_LIST = None
78OPTION_TUI = None
79OPTION_WAIT = ['-D']
80OPTION_STDCXXPYPR = None
81
82PYPRPR_BASE = sys.prefix + '/share/pretty-printers/'
83PYPRPR_GNUSTDCXX_BASE = PYPRPR_BASE + 'libstdcxx/'
84
85DEBUG_PORT = 5039
86JDB_PORT = 65534
87
88# Name of the manifest file
89MANIFEST = '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#
95DELAY = 2.0
96NDK = os.path.abspath(os.path.dirname(sys.argv[0])).replace('\\','/')
97DEVICE_SERIAL = ''
98ADB_FLAGS = ''
99
100def log(string):
101    global VERBOSE
102    if VERBOSE:
103        print(string)
104
105def error(string, errcode=1):
106    print('ERROR: %s' % (string))
107    exit(errcode)
108
109def 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='''
121Setup a gdb debugging session for your Android NDK application.
122Read ''' + 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
274def 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
289def 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.
303def 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 :: ')
308def output_jdb(text):
309    if not OPTION_TUI or OPTION_TUI != 'running':
310        print('JDB :: %s' % text)
311
312def input_jdb(inhandle):
313    while True:
314        inhandle.write('\n')
315        time.sleep(1.0)
316
317def 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
356def 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
395def _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
405def adb_var_shell(args, log_command=False):
406    return _adb_var_shell(args, redirect_stderr=False, log_command=log_command)
407
408def 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#
415def 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
441def 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
452def 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
465def 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
498def 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!
572ndk-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.
577The 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
619After 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
809if __name__ == '__main__':
810    main()
811