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