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, 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 _,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 646 After one of these, re-install to the device!''' % (PACKAGE_NAME)) 647 elif not os.path.isfile(gdbserver_path): 648 error('''Could not find gdbserver binary under %s/libs/%s 649 This usually means you modified your AndroidManifest.xml to set 650 the android:debuggable flag to 'true' but did not rebuild the 651 native binaries. Please call 'ndk-build' to do so, 652 *then* re-install to the device!''' % (PROJECT,COMPAT_ABI)) 653 654 # Let's check that 'gdbserver' is properly installed on the device too. If this 655 # is not the case, the user didn't install the proper package after rebuilding. 656 # 657 retcode,DEVICE_GDBSERVER = adb_var_shell2(['ls', '/data/data/%s/lib/gdbserver' % (PACKAGE_NAME)]) 658 if retcode: 659 error('''Non-debuggable application installed on the target device. 660 Please re-install the debuggable version!''') 661 log('Found device gdbserver: %s' % (DEVICE_GDBSERVER)) 662 663 # Find the <dataDir> of the package on the device 664 retcode,DATA_DIR = adb_var_shell2(['run-as', PACKAGE_NAME, '/system/bin/sh', '-c', 'pwd']) 665 if retcode or DATA_DIR == '': 666 error('''Could not extract package's data directory. Are you sure that 667 your installed application is debuggable?''') 668 log("Found data directory: '%s'" % (DATA_DIR)) 669 670 # Launch the activity if needed 671 if OPTION_START: 672 if not OPTION_LAUNCH: 673 OPTION_LAUNCH = extract_launchable(PROJECT+os.sep+MANIFEST) 674 if not len(OPTION_LAUNCH): 675 error('''Could not extract name of launchable activity from manifest! 676 Try to use --launch=<name> directly instead as a work-around.''') 677 log('Found first launchable activity: %s' % (OPTION_LAUNCH[0])) 678 if not len(OPTION_LAUNCH): 679 error('''It seems that your Application does not have any launchable activity! 680 Please fix your manifest file and rebuild/re-install your application.''') 681 682 if OPTION_LAUNCH: 683 log('Launching activity: %s/%s' % (PACKAGE_NAME,OPTION_LAUNCH[0])) 684 retcode,LAUNCH_OUTPUT=adb_cmd(True, 685 ['shell', 'am', 'start'] + OPTION_WAIT + ['-n', '%s/%s' % (PACKAGE_NAME,OPTION_LAUNCH[0])], 686 log_command=True) 687 if retcode: 688 error('''Could not launch specified activity: %s 689 Use --launch-list to dump a list of valid values.''' % (OPTION_LAUNCH[0])) 690 691 # Sleep a bit, it sometimes take one second to start properly 692 # Note that we use the 'sleep' command on the device here. 693 # 694 adb_cmd(True, ['shell', 'sleep', '%f' % (DELAY)], log_command=True) 695 696 # Find the PID of the application being run 697 retcode,PID = get_pid_of(PACKAGE_NAME) 698 log('Found running PID: %d' % (PID)) 699 if retcode or PID == 0: 700 if OPTION_LAUNCH: 701 error('''Could not extract PID of application on device/emulator. 702 Weird, this probably means one of these: 703 704 - The installed package does not match your current manifest. 705 - The application process was terminated. 706 707 Try using the --verbose option and look at its output for details.''') 708 else: 709 error('''Could not extract PID of application on device/emulator. 710 Are you sure the application is already started? 711 Consider using --start or --launch=<name> if not.''') 712 713 # Check that there is no other instance of gdbserver running 714 retcode,GDBSERVER_PID = get_pid_of('lib/gdbserver') 715 if not retcode and not GDBSERVER_PID == 0: 716 if not OPTION_FORCE: 717 error('Another debug session running, Use --force to kill it.') 718 log('Killing existing debugging session') 719 adb_cmd(False, ['shell', 'kill -9 %s' % (GDBSERVER_PID)]) 720 721 # Launch gdbserver now 722 DEBUG_SOCKET = 'debug-socket' 723 adb_cmd(False, 724 ['shell', 'run-as', PACKAGE_NAME, 'lib/gdbserver', '+%s' % (DEBUG_SOCKET), '--attach', str(PID)], 725 log_command=True, adb_trace=True, background=True) 726 log('Launched gdbserver succesfully.') 727 728 # Make sure gdbserver was launched - debug check. 729 # adb_var_shell(['sleep', '0.1'], log_command=False) 730 # retcode,GDBSERVER_PID = get_pid_of('lib/gdbserver') 731 # if retcode or GDBSERVER_PID == 0: 732 # error('Could not launch gdbserver on the device?') 733 # log('Launched gdbserver succesfully (PID=%s)' % (GDBSERVER_PID)) 734 735 # Setup network redirection 736 log('Setup network redirection') 737 retcode,_ = adb_cmd(False, 738 ['forward', 'tcp:%d' % (DEBUG_PORT), 'localfilesystem:%s/%s' % (DATA_DIR,DEBUG_SOCKET)], 739 log_command=True) 740 if retcode: 741 error('''Could not setup network redirection to gdbserver? 742 Maybe using --port=<port> to use a different TCP port might help?''') 743 744 # Get the app_server binary from the device 745 APP_PROCESS = '%s/app_process' % (APP_OUT) 746 adb_cmd(False, ['pull', '/system/bin/app_process', APP_PROCESS], log_command=True) 747 log('Pulled app_process from device/emulator.') 748 749 adb_cmd(False, ['pull', '/system/bin/linker', '%s/linker' % (APP_OUT)], log_command=True) 750 log('Pulled linker from device/emulator.') 751 752 adb_cmd(False, ['pull', '/system/lib/libc.so', '%s/libc.so' % (APP_OUT)], log_command=True) 753 log('Pulled libc.so from device/emulator.') 754 755 # Setup JDB connection, for --start or --launch 756 if (OPTION_START != None or OPTION_LAUNCH != None) and len(OPTION_WAIT): 757 log('Set up JDB connection, using jdb command: %s' % JDB_CMD) 758 retcode,_ = adb_cmd(False, 759 ['forward', 'tcp:%d' % (JDB_PORT), 'jdwp:%d' % (PID)], 760 log_command=True) 761 time.sleep(1.0) 762 if retcode: 763 error('Could not forward JDB port') 764 background_spawn([JDB_CMD,'-connect','com.sun.jdi.SocketAttach:hostname=localhost,port=%d' % (JDB_PORT)], True, output_jdb, True, input_jdb) 765 time.sleep(1.0) 766 767 # Work out the python pretty printer details. 768 pypr_folder = None 769 pypr_function = None 770 771 # Automatic determination of pypr. 772 if OPTION_STDCXXPYPR == 'auto': 773 libdir = os.path.join(PROJECT,'libs',COMPAT_ABI) 774 libs = [ f for f in os.listdir(libdir) 775 if os.path.isfile(os.path.join(libdir, f)) and f.endswith('.so') ] 776 if 'libstlport_shared.so' in libs: 777 OPTION_STDCXXPYPR = 'stlport' 778 elif 'libgnustl_shared.so' in libs: 779 OPTION_STDCXXPYPR = 'gnustdcxx' 780 781 if OPTION_STDCXXPYPR == 'stlport': 782 pypr_folder = PYPRPR_BASE + 'stlport/gppfs-0.2/stlport' 783 pypr_function = 'register_stlport_printers' 784 elif OPTION_STDCXXPYPR.startswith('gnustdcxx'): 785 if OPTION_STDCXXPYPR == 'gnustdcxx': 786 NDK_TOOLCHAIN_VERSION = get_build_var_for_abi('NDK_TOOLCHAIN_VERSION', COMPAT_ABI) 787 log('Using toolchain version: %s' % (NDK_TOOLCHAIN_VERSION)) 788 pypr_folder = PYPRPR_GNUSTDCXX_BASE + 'gcc-' + NDK_TOOLCHAIN_VERSION 789 else: 790 pypr_folder = PYPRPR_GNUSTDCXX_BASE + OPTION_STDCXXPYPR.replace('gnustdcxx-','gcc-') 791 pypr_function = 'register_libstdcxx_printers' 792 793 # Now launch the appropriate gdb client with the right init commands 794 # 795 GDBCLIENT = '%sgdb' % (TOOLCHAIN_PREFIX) 796 GDBSETUP = '%s/gdb.setup' % (APP_OUT) 797 shutil.copyfile(GDBSETUP_INIT, GDBSETUP) 798 with open(GDBSETUP, "a") as gdbsetup: 799 #uncomment the following to debug the remote connection only 800 #gdbsetup.write('set debug remote 1\n') 801 gdbsetup.write('file '+APP_PROCESS+'\n') 802 gdbsetup.write('target remote :%d\n' % (DEBUG_PORT)) 803 gdbsetup.write('set breakpoint pending on\n') 804 805 if pypr_function: 806 gdbsetup.write('python\n') 807 gdbsetup.write('import sys\n') 808 gdbsetup.write('sys.path.append("%s")\n' % pypr_folder) 809 gdbsetup.write('from printers import %s\n' % pypr_function) 810 gdbsetup.write('%s(None)\n' % pypr_function) 811 gdbsetup.write('end\n') 812 813 if OPTION_EXEC: 814 with open(OPTION_EXEC, 'r') as execfile: 815 for line in execfile: 816 gdbsetup.write(line) 817 gdbsetup.close() 818 819 gdbargs = [GDBCLIENT, '-x', '%s' % (GDBSETUP)] 820 if OPTION_TUI: 821 gdbhelp = subprocess.check_output([GDBCLIENT, '--help']).decode('ascii') 822 try: 823 gdbhelp.index('--tui') 824 gdbargs.append('--tui') 825 OPTION_TUI = 'running' 826 except: 827 print('Warning: Disabled tui mode as %s does not support it' % (os.path.basename(GDBCLIENT))) 828 gdbp = subprocess.Popen(gdbargs) 829 while gdbp.returncode is None: 830 try: 831 gdbp.communicate() 832 except KeyboardInterrupt: 833 pass 834 log("Exited gdb, returncode %d" % gdbp.returncode) 835 836 if __name__ == '__main__': 837 main() 838