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