blob: 37a30734f533911f6ff96234e4cebc4a2dc18097 [file] [log] [blame]
Ray Donnelly5ca38752013-06-14 12:56:04 +01001#!/usr/bin/env python
Ray Donnellyc2f40d92012-12-05 01:01:42 +00002
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
Ray Donnelly9855fa02013-08-11 00:41:26 +010031import sys, os, platform, argparse, subprocess, types
Ray Donnellyc2f40d92012-12-05 01:01:42 +000032import xml.etree.cElementTree as ElementTree
Ray Donnellybde0f1f2013-06-04 23:57:36 +010033import shutil, time
Ray Donnellyc2f40d92012-12-05 01:01:42 +000034from 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 '''
Ray Donnelly0a9da1b2013-08-11 00:35:00 +010042 PATHS = extra_paths+os.environ['PATH'].replace('"','').split(os.pathsep)
Ray Donnellyc2f40d92012-12-05 01:01:42 +000043 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
Ray Donnellyc2f40d92012-12-05 01:01:42 +000054def ndk_bin_path(ndk):
Ray Donnelly9855fa02013-08-11 00:41:26 +010055 '''
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'
Ray Donnellyc2f40d92012-12-05 01:01:42 +000087
88VERBOSE = False
89PROJECT = None
Ray Donnellyc2f40d92012-12-05 01:01:42 +000090ADB_CMD = None
91GNUMAKE_CMD = None
Ray Donnellybde0f1f2013-06-04 23:57:36 +010092JDB_CMD = None
Ray Donnelly61bab5f2013-06-04 23:32:27 +010093# Extra arguments passed to the NDK build system when
94# querying it.
95GNUMAKE_FLAGS = []
Ray Donnellyc2f40d92012-12-05 01:01:42 +000096
97OPTION_FORCE = None
98OPTION_EXEC = None
99OPTION_START = None
100OPTION_LAUNCH = None
101OPTION_LAUNCH_LIST = None
102OPTION_TUI = None
Ray Donnellybde0f1f2013-06-04 23:57:36 +0100103OPTION_WAIT = ['-D']
Ray Donnelly5ca38752013-06-14 12:56:04 +0100104OPTION_STDCXXPYPR = None
105
106PYPRPR_BASE = sys.prefix + '/share/pretty-printers/'
107PYPRPR_GNUSTDCXX_BASE = PYPRPR_BASE + 'libstdcxx/'
Ray Donnellyc2f40d92012-12-05 01:01:42 +0000108
109DEBUG_PORT = 5039
Ray Donnellybde0f1f2013-06-04 23:57:36 +0100110JDB_PORT = 65534
Ray Donnellyc2f40d92012-12-05 01:01:42 +0000111
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
Ray Donnelly43aba772013-07-28 20:59:43 +0100120NDK = os.path.abspath(os.path.dirname(sys.argv[0])).replace('\\','/')
Ray Donnellyc2f40d92012-12-05 01:01:42 +0000121DEVICE_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
Ray Donnelly61bab5f2013-06-04 23:32:27 +0100135 global GNUMAKE_CMD, GNUMAKE_FLAGS
136 global ADB_CMD, ADB_FLAGS
Ray Donnellybde0f1f2013-06-04 23:57:36 +0100137 global JDB_CMD
Ray Donnellyc2f40d92012-12-05 01:01:42 +0000138 global PROJECT, NDK
139 global OPTION_START, OPTION_LAUNCH, OPTION_LAUNCH_LIST
Ray Donnellybde0f1f2013-06-04 23:57:36 +0100140 global OPTION_FORCE, OPTION_EXEC, OPTION_TUI, OPTION_WAIT
Ray Donnelly5ca38752013-06-14 12:56:04 +0100141 global OPTION_STDCXXPYPR
142 global PYPRPR_GNUSTDCXX_BASE
Ray Donnellyc2f40d92012-12-05 01:01:42 +0000143
144 parser = argparse.ArgumentParser(description='''
Ray Donnelly43aba772013-07-28 20:59:43 +0100145Setup a gdb debugging session for your Android NDK application.
146Read ''' + NDK + '''/docs/NDK-GDB.html for complete usage instructions.''',
147 formatter_class=argparse.RawTextHelpFormatter)
Ray Donnellyc2f40d92012-12-05 01:01:42 +0000148
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
Ray Donnelly61bab5f2013-06-04 23:32:27 +0100210 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
Ray Donnellybde0f1f2013-06-04 23:57:36 +0100214 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
Todd Fialaf47cd922014-01-06 13:05:45 -0800218 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 = []
Ray Donnelly5ca38752013-06-14 12:56:04 +0100224
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
Ray Donnellyc2f40d92012-12-05 01:01:42 +0000230 args = parser.parse_args()
231
232 VERBOSE = args.verbose
233
234 ndk_bin = ndk_bin_path(NDK)
Ray Donnellyc2f40d92012-12-05 01:01:42 +0000235 (found_adb, ADB_CMD) = find_program('adb', [ndk_bin])
236 (found_gnumake, GNUMAKE_CMD) = find_program('make', [ndk_bin])
Ray Donnellybde0f1f2013-06-04 23:57:36 +0100237 (found_jdb, JDB_CMD) = find_program('jdb', [])
Ray Donnellyc2f40d92012-12-05 01:01:42 +0000238
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
Ray Donnelly61bab5f2013-06-04 23:32:27 +0100291 if args.gnumake_flags != None:
292 GNUMAKE_FLAGS = args.gnumake_flags
293
Ray Donnellybde0f1f2013-06-04 23:57:36 +0100294 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
Ray Donnelly5ca38752013-06-14 12:56:04 +0100299 OPTION_STDCXXPYPR = args.stdcxxpypr
300
Ray Donnellyc2f40d92012-12-05 01:01:42 +0000301def get_build_var(var):
Ray Donnelly61bab5f2013-06-04 23:32:27 +0100302 global GNUMAKE_CMD, GNUMAKE_FLAGS, NDK, PROJECT
Ray Donnellyc2f40d92012-12-05 01:01:42 +0000303 text = subprocess.check_output([GNUMAKE_CMD,
304 '--no-print-dir',
305 '-f',
306 NDK+'/build/core/build-local.mk',
307 '-C',
308 PROJECT,
Ray Donnelly61bab5f2013-06-04 23:32:27 +0100309 'DUMP_'+var] + GNUMAKE_FLAGS
Ray Donnellyc2f40d92012-12-05 01:01:42 +0000310 )
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):
Ray Donnelly61bab5f2013-06-04 23:32:27 +0100317 global GNUMAKE_CMD, GNUMAKE_FLAGS, NDK, PROJECT
Ray Donnellyc2f40d92012-12-05 01:01:42 +0000318 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,
Ray Donnelly61bab5f2013-06-04 23:32:27 +0100325 'APP_ABI='+abi] + GNUMAKE_FLAGS,
Ray Donnellyc2f40d92012-12-05 01:01:42 +0000326 )
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
Ray Donnellybde0f1f2013-06-04 23:57:36 +0100334# 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)
Ray Donnellyc2f40d92012-12-05 01:01:42 +0000338
Ray Donnellybde0f1f2013-06-04 23:57:36 +0100339def input_jdb(inhandle):
340 while True:
341 inhandle.write('\n')
342 time.sleep(1.0)
Ray Donnellyc2f40d92012-12-05 01:01:42 +0000343
Ray Donnellybde0f1f2013-06-04 23:57:36 +0100344def 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''):
Ray Donnellyc2f40d92012-12-05 01:01:42 +0000348 output_fn(line.replace('\r', '').replace('\n', ''))
Ray Donnellybde0f1f2013-06-04 23:57:36 +0100349 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()
Ray Donnellyc2f40d92012-12-05 01:01:42 +0000359
360 if redirect_stderr:
361 used_stderr = subprocess.PIPE
362 else:
363 used_stderr = subprocess.STDOUT
Ray Donnellybde0f1f2013-06-04 23:57:36 +0100364 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,
Ray Donnellyc2f40d92012-12-05 01:01:42 +0000369 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()
Ray Donnellybde0f1f2013-06-04 23:57:36 +0100378 if redirect_stdin:
379 ti = Thread(target=async_stdin, args=(p.stdin, qo, input_fn))
380 ti.daemon = True
381 ti.start()
Ray Donnellyc2f40d92012-12-05 01:01:42 +0000382
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
Ray Donnellybde0f1f2013-06-04 23:57:36 +0100527 global JDB_CMD
Ray Donnellyc2f40d92012-12-05 01:01:42 +0000528 global OPTION_START, OPTION_LAUNCH, OPTION_LAUNCH_LIST
Ray Donnellybde0f1f2013-06-04 23:57:36 +0100529 global OPTION_FORCE, OPTION_EXEC, OPTION_TUI, OPTION_WAIT
Ray Donnelly5ca38752013-06-14 12:56:04 +0100530 global OPTION_STDCXXPYPR
531 global PYPRPR_BASE, PYPRPR_GNUSTDCXX_BASE
Ray Donnellyc2f40d92012-12-05 01:01:42 +0000532
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))
Ray Donnellyc2f40d92012-12-05 01:01:42 +0000546 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))
Ray Donnelly5ca38752013-06-14 12:56:04 +0100575 if PACKAGE_NAME == '<none>':
Ray Donnellyc2f40d92012-12-05 01:01:42 +0000576 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
Andrew Hsieh5f2f1102013-04-08 14:22:19 +0800682 if OPTION_LAUNCH:
Ray Donnellyc2f40d92012-12-05 01:01:42 +0000683 log('Launching activity: %s/%s' % (PACKAGE_NAME,OPTION_LAUNCH[0]))
684 retcode,LAUNCH_OUTPUT=adb_cmd(True,
Ray Donnellybde0f1f2013-06-04 23:57:36 +0100685 ['shell', 'am', 'start'] + OPTION_WAIT + ['-n', '%s/%s' % (PACKAGE_NAME,OPTION_LAUNCH[0])],
Ray Donnellyc2f40d92012-12-05 01:01:42 +0000686 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:
Andrew Hsieh5f2f1102013-04-08 14:22:19 +0800700 if OPTION_LAUNCH:
Ray Donnellyc2f40d92012-12-05 01:01:42 +0000701 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)
Ray Donnellybde0f1f2013-06-04 23:57:36 +0100726 log('Launched gdbserver succesfully.')
Ray Donnellyc2f40d92012-12-05 01:01:42 +0000727
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
Ray Donnellybde0f1f2013-06-04 23:57:36 +0100755 # 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
Ray Donnelly5ca38752013-06-14 12:56:04 +0100767 # 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
Ray Donnellyc2f40d92012-12-05 01:01:42 +0000793 # 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
Ray Donnellybde0f1f2013-06-04 23:57:36 +0100800 #gdbsetup.write('set debug remote 1\n')
Ray Donnellyc2f40d92012-12-05 01:01:42 +0000801 gdbsetup.write('file '+APP_PROCESS+'\n')
802 gdbsetup.write('target remote :%d\n' % (DEBUG_PORT))
Ray Donnellybde0f1f2013-06-04 23:57:36 +0100803 gdbsetup.write('set breakpoint pending on\n')
Ray Donnelly5ca38752013-06-14 12:56:04 +0100804
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
Ray Donnellyc2f40d92012-12-05 01:01:42 +0000813 if OPTION_EXEC:
814 with open(OPTION_EXEC, 'r') as execfile:
Ray Donnellybde0f1f2013-06-04 23:57:36 +0100815 for line in execfile:
Ray Donnellyc2f40d92012-12-05 01:01:42 +0000816 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)))
Ray Donnellybde0f1f2013-06-04 23:57:36 +0100828 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)
Ray Donnellyc2f40d92012-12-05 01:01:42 +0000835
836if __name__ == '__main__':
837 main()