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, platform, 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'].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
54def 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
88VERBOSE = False
89PROJECT = None
90ADB_CMD = None
91GNUMAKE_CMD = None
92JDB_CMD = None
93# Extra arguments passed to the NDK build system when
94# querying it.
95GNUMAKE_FLAGS = []
96
97OPTION_FORCE = None
98OPTION_EXEC = None
99OPTION_START = None
100OPTION_LAUNCH = None
101OPTION_LAUNCH_LIST = None
102OPTION_TUI = None
103OPTION_WAIT = ['-D']
104OPTION_STDCXXPYPR = None
105
106PYPRPR_BASE = sys.prefix + '/share/pretty-printers/'
107PYPRPR_GNUSTDCXX_BASE = PYPRPR_BASE + 'libstdcxx/'
108
109DEBUG_PORT = 5039
110JDB_PORT = 65534
111
112# Name of the manifest file
113MANIFEST = '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#
119DELAY = 2.0
120NDK = os.path.abspath(os.path.dirname(sys.argv[0])).replace('\\','/')
121DEVICE_SERIAL = ''
122ADB_FLAGS = ''
123
124def log(string):
125    global VERBOSE
126    if VERBOSE:
127        print(string)
128
129def error(string, errcode=1):
130    print('ERROR: %s' % (string))
131    exit(errcode)
132
133def 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='''
145Setup a gdb debugging session for your Android NDK application.
146Read ''' + 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
301def 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
316def 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.
330def 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 :: ')
335def output_jdb(text):
336    if not OPTION_TUI or OPTION_TUI != 'running':
337        print('JDB :: %s' % text)
338
339def input_jdb(inhandle):
340    while True:
341        inhandle.write('\n')
342        time.sleep(1.0)
343
344def 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
383def 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
422def _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
432def adb_var_shell(args, log_command=False):
433    return _adb_var_shell(args, redirect_stderr=False, log_command=log_command)
434
435def 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#
442def 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
468def 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
479def 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
492def 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
525def 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!
599ndk-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.
604The 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
646After 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
836if __name__ == '__main__':
837    main()
838