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