1#!/usr/bin/env python 2 3r''' 4 Copyright (C) 2010 The Android Open Source Project 5 Copyright (C) 2012 Ray Donnelly <mingw.android@gmail.com> 6 7 Licensed under the Apache License, Version 2.0 (the "License"); 8 you may not use this file except in compliance with the License. 9 You may obtain a copy of the License at 10 11 http://www.apache.org/licenses/LICENSE-2.0 12 13 Unless required by applicable law or agreed to in writing, software 14 distributed under the License is distributed on an "AS IS" BASIS, 15 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 See the License for the specific language governing permissions and 17 limitations under the License. 18 19 20 This wrapper script is used to launch a native debugging session 21 on a given NDK application. The application must be debuggable, i.e. 22 its android:debuggable attribute must be set to 'true' in the 23 <application> element of its manifest. 24 25 See docs/NDK-GDB.TXT for usage description. Essentially, you just 26 need to launch ndk-gdb-py from your application project directory 27 after doing ndk-build && ant debug && \ 28 adb install && <start-application-on-device> 29''' 30 31import sys, os, platform, argparse, subprocess, types 32import xml.etree.cElementTree as ElementTree 33import shutil, time 34from threading import Thread 35try: 36 from Queue import Queue, Empty 37except ImportError: 38 from queue import Queue, Empty # python 3.x 39 40def find_program(program, extra_paths = []): 41 ''' extra_paths are searched before PATH ''' 42 PATHS = extra_paths+os.environ['PATH'].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 54def 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 88VERBOSE = False 89PROJECT = None 90ADB_CMD = None 91GNUMAKE_CMD = None 92JDB_CMD = None 93# Extra arguments passed to the NDK build system when 94# querying it. 95GNUMAKE_FLAGS = [] 96 97OPTION_FORCE = None 98OPTION_EXEC = None 99OPTION_START = None 100OPTION_LAUNCH = None 101OPTION_LAUNCH_LIST = None 102OPTION_TUI = None 103OPTION_WAIT = ['-D'] 104OPTION_STDCXXPYPR = None 105 106PYPRPR_BASE = sys.prefix + '/share/pretty-printers/' 107PYPRPR_GNUSTDCXX_BASE = PYPRPR_BASE + 'libstdcxx/' 108 109DEBUG_PORT = 5039 110JDB_PORT = 65534 111 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 120NDK = os.path.abspath(os.path.dirname(sys.argv[0])).replace('\\','/') 121DEVICE_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 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=''' 145Setup a gdb debugging session for your Android NDK application. 146Read ''' + 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 301def 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 316def 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. 330def 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 :: ') 335def output_jdb(text): 336 if not OPTION_TUI or OPTION_TUI != 'running': 337 print('JDB :: %s' % text) 338 339def input_jdb(inhandle): 340 while True: 341 inhandle.write('\n') 342 time.sleep(1.0) 343 344def 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 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 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! 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 607 _,CPU_ABILIST32 = adb_var_shell(['getprop', 'ro.product.cpu.abilist32']) 608 _,CPU_ABILIST64 = adb_var_shell(['getprop', 'ro.product.cpu.abilist64']) 609 # Both CPU_ABILIST32 and CPU_ABILIST64 may contain multiple comma-delimited abis. 610 # Concatanate CPU_ABILIST32 and CPU_ABILIST64. 611 CPU_ABIS = CPU_ABILIST64.split(',')+CPU_ABILIST32.split(',') 612 if not CPU_ABILIST64 and not CPU_ABILIST32: 613 _,CPU_ABI1 = adb_var_shell(['getprop', 'ro.product.cpu.abi']) 614 _,CPU_ABI2 = adb_var_shell(['getprop', 'ro.product.cpu.abi2']) 615 CPU_ABIS = CPU_ABI1.split(',')+CPU_ABI2.split(',') 616 617 log('Device CPU ABIs: %s' % (' '.join(CPU_ABIS))) 618 619 device_bits = 32 620 if len(CPU_ABILIST64): 621 device_bits = 64 622 623 app_bits = 32 624 # First look compatible ABI in the list of 64-bit ABIs. 625 COMPAT_ABI = [ABI for ABI in CPU_ABILIST64.split(',') if ABI in APP_ABIS] 626 if len(COMPAT_ABI): 627 # We found compatible ABI and it is 64-bit. 628 app_bits = 64 629 else: 630 # If we found nothing - look among 32-bit ABIs. 631 COMPAT_ABI = [ABI for ABI in CPU_ABILIST32.split(',') if ABI in APP_ABIS] 632 if not len(COMPAT_ABI): 633 # Lastly, lets check ro.product.cpu.abi and ro.product.cpu.abi2 634 COMPAT_ABI = [ABI for ABI in CPU_ABIS if ABI in APP_ABIS] 635 636 if not len(COMPAT_ABI): 637 error('''The device does not support the application's targetted CPU ABIs! 638 Device supports: %s 639 Package supports: %s''' % (' '.join(CPU_ABIS),' '.join(APP_ABIS))) 640 COMPAT_ABI = COMPAT_ABI[0] 641 log('Compatible device ABI: %s' % (COMPAT_ABI)) 642 GDBSETUP_INIT = get_build_var_for_abi('NDK_APP_GDBSETUP', COMPAT_ABI) 643 log('Using gdb setup init: %s' % (GDBSETUP_INIT)) 644 645 TOOLCHAIN_PREFIX = get_build_var_for_abi('TOOLCHAIN_PREFIX', COMPAT_ABI) 646 log('Using toolchain prefix: %s' % (TOOLCHAIN_PREFIX)) 647 648 APP_OUT = get_build_var_for_abi('TARGET_OUT', COMPAT_ABI) 649 log('Using app out directory: %s' % (APP_OUT)) 650 DEBUGGABLE = extract_debuggable(PROJECT+os.sep+MANIFEST) 651 log('Found debuggable flag: %s' % ('true' if DEBUGGABLE==True else 'false')) 652 # If gdbserver exists, then we built with 'ndk-build NDK_DEBUG=1' and it's 653 # ok to not have android:debuggable set to true in the original manifest. 654 # However, if this is not the case, then complain!! 655 # 656 gdbserver_path = os.path.join(PROJECT,'libs',COMPAT_ABI,'gdbserver') 657 if not DEBUGGABLE: 658 if os.path.isfile(gdbserver_path): 659 log('Found gdbserver under libs/%s, assuming app was built with NDK_DEBUG=1' % (COMPAT_ABI)) 660 else: 661 error('''Package %s is not debuggable ! You can fix that in two ways: 662 663 - Rebuilt with the NDK_DEBUG=1 option when calling 'ndk-build'. 664 665 - Modify your manifest to set android:debuggable attribute to "true", 666 then rebuild normally. 667 668After one of these, re-install to the device!''' % (PACKAGE_NAME)) 669 elif not os.path.isfile(gdbserver_path): 670 error('''Could not find gdbserver binary under %s/libs/%s 671 This usually means you modified your AndroidManifest.xml to set 672 the android:debuggable flag to 'true' but did not rebuild the 673 native binaries. Please call 'ndk-build' to do so, 674 *then* re-install to the device!''' % (PROJECT,COMPAT_ABI)) 675 676 # Let's check that 'gdbserver' is properly installed on the device too. If this 677 # is not the case, push 'gdbserver' found in prebuilt. 678 # 679 device_gdbserver = '/data/data/%s/lib/gdbserver' % (PACKAGE_NAME) 680 retcode,LS_GDBSERVER = adb_var_shell2(['ls', device_gdbserver]) 681 if not retcode: 682 log('Found device gdbserver: %s' % (device_gdbserver)) 683 else: 684 gdbserver_prebuilt_path = ('%s/prebuilt/android-%s/gdbserver/gdbserver' % (NDK, COMPAT_ABI)) 685 if os.path.isfile(gdbserver_prebuilt_path): 686 log('Found prebuilt gdbserver. Copying it on device') 687 retcode,PUSH_OUTPUT=adb_cmd(True, 688 ['shell', 'push', '%s' % gdbserver_prebuilt_path, '/data/local/tmp/gdbserver']) 689 if retcode: 690 error('''Could not copy prebuilt gdberver to the device''') 691 device_gdbserver = '/data/local/tmp/gdbserver' 692 else: 693 error('''Non-debuggable application installed on the target device. 694 Please re-install the debuggable version!''') 695 696 # Find the <dataDir> of the package on the device 697 retcode,DATA_DIR = adb_var_shell2(['run-as', PACKAGE_NAME, '/system/bin/sh', '-c', 'pwd']) 698 if retcode or DATA_DIR == '': 699 error('''Could not extract package's data directory. Are you sure that 700 your installed application is debuggable?''') 701 log("Found data directory: '%s'" % (DATA_DIR)) 702 703 # Launch the activity if needed 704 if OPTION_START: 705 if not OPTION_LAUNCH: 706 OPTION_LAUNCH = extract_launchable(PROJECT+os.sep+MANIFEST) 707 if not len(OPTION_LAUNCH): 708 error('''Could not extract name of launchable activity from manifest! 709 Try to use --launch=<name> directly instead as a work-around.''') 710 log('Found first launchable activity: %s' % (OPTION_LAUNCH[0])) 711 if not len(OPTION_LAUNCH): 712 error('''It seems that your Application does not have any launchable activity! 713 Please fix your manifest file and rebuild/re-install your application.''') 714 715 if OPTION_LAUNCH: 716 log('Launching activity: %s/%s' % (PACKAGE_NAME,OPTION_LAUNCH[0])) 717 retcode,LAUNCH_OUTPUT=adb_cmd(True, 718 ['shell', 'am', 'start'] + OPTION_WAIT + ['-n', '%s/%s' % (PACKAGE_NAME,OPTION_LAUNCH[0])], 719 log_command=True) 720 if retcode: 721 error('''Could not launch specified activity: %s 722 Use --launch-list to dump a list of valid values.''' % (OPTION_LAUNCH[0])) 723 724 # Sleep a bit, it sometimes take one second to start properly 725 # Note that we use the 'sleep' command on the device here. 726 # 727 adb_cmd(True, ['shell', 'sleep', '%f' % (DELAY)], log_command=True) 728 729 # Find the PID of the application being run 730 retcode,PID = get_pid_of(PACKAGE_NAME) 731 log('Found running PID: %d' % (PID)) 732 if retcode or PID == 0: 733 if OPTION_LAUNCH: 734 error('''Could not extract PID of application on device/emulator. 735 Weird, this probably means one of these: 736 737 - The installed package does not match your current manifest. 738 - The application process was terminated. 739 740 Try using the --verbose option and look at its output for details.''') 741 else: 742 error('''Could not extract PID of application on device/emulator. 743 Are you sure the application is already started? 744 Consider using --start or --launch=<name> if not.''') 745 746 # Check that there is no other instance of gdbserver running 747 retcode,GDBSERVER_PID = get_pid_of('lib/gdbserver') 748 if not retcode and not GDBSERVER_PID == 0: 749 if not OPTION_FORCE: 750 error('Another debug session running, Use --force to kill it.') 751 log('Killing existing debugging session') 752 adb_cmd(False, ['shell', 'kill -9 %s' % (GDBSERVER_PID)]) 753 754 # Launch gdbserver now 755 DEBUG_SOCKET = 'debug-socket' 756 adb_cmd(False, 757 ['shell', 'run-as', PACKAGE_NAME, device_gdbserver, '+%s' % (DEBUG_SOCKET), '--attach', str(PID)], 758 log_command=True, adb_trace=True, background=True) 759 log('Launched gdbserver succesfully.') 760 761# Make sure gdbserver was launched - debug check. 762# adb_var_shell(['sleep', '0.1'], log_command=False) 763# retcode,GDBSERVER_PID = get_pid_of('lib/gdbserver') 764# if retcode or GDBSERVER_PID == 0: 765# error('Could not launch gdbserver on the device?') 766# log('Launched gdbserver succesfully (PID=%s)' % (GDBSERVER_PID)) 767 768 # Setup network redirection 769 log('Setup network redirection') 770 retcode,_ = adb_cmd(False, 771 ['forward', 'tcp:%d' % (DEBUG_PORT), 'localfilesystem:%s/%s' % (DATA_DIR,DEBUG_SOCKET)], 772 log_command=True) 773 if retcode: 774 error('''Could not setup network redirection to gdbserver? 775 Maybe using --port=<port> to use a different TCP port might help?''') 776 777 # If we are debugging 64-bit app, then we need to pull linker64, 778 # app_process64 and libc.so from lib64 directory. 779 linker_name = 'linker' 780 libdir_name = 'lib' 781 app_process_name = 'app_process32' 782 if (app_bits == 64): 783 linker_name = 'linker64' 784 libdir_name = 'lib64' 785 app_process_name = 'app_process64' 786 else: 787 retcode,_ = adb_cmd(False, ['shell', 'test -e /system/bin/%s' % (app_process_name)]) 788 if retcode: 789 # Old 32-bit devices do not have app_process32. Pull 790 # app_process in this case 791 app_process_name = 'app_process' 792 793 # Get the app_server binary from the device 794 pulled_app_process = '%s/app_process' % (APP_OUT) 795 adb_cmd(False, ['pull', '/system/bin/%s' % (app_process_name), pulled_app_process], log_command=True) 796 log('Pulled %s from device/emulator.' % (app_process_name)) 797 798 pulled_linker = '%s/%s' % (APP_OUT, linker_name) 799 adb_cmd(False, ['pull', '/system/bin/%s' % (linker_name), pulled_linker], log_command=True) 800 log('Pulled %s from device/emulator.' % (linker_name)) 801 802 pulled_libc = '%s/libc.so' % (APP_OUT) 803 adb_cmd(False, ['pull', '/system/%s/libc.so' % libdir_name, pulled_libc], log_command=True) 804 log('Pulled /system/%s/libc.so from device/emulator.' % libdir_name) 805 806 # Setup JDB connection, for --start or --launch 807 if (OPTION_START != None or OPTION_LAUNCH != None) and len(OPTION_WAIT): 808 log('Set up JDB connection, using jdb command: %s' % JDB_CMD) 809 retcode,_ = adb_cmd(False, 810 ['forward', 'tcp:%d' % (JDB_PORT), 'jdwp:%d' % (PID)], 811 log_command=True) 812 time.sleep(1.0) 813 if retcode: 814 error('Could not forward JDB port') 815 background_spawn([JDB_CMD,'-connect','com.sun.jdi.SocketAttach:hostname=localhost,port=%d' % (JDB_PORT)], True, output_jdb, True, input_jdb) 816 time.sleep(1.0) 817 818 # Work out the python pretty printer details. 819 pypr_folder = None 820 pypr_function = None 821 822 # Automatic determination of pypr. 823 if OPTION_STDCXXPYPR == 'auto': 824 libdir = os.path.join(PROJECT,'libs',COMPAT_ABI) 825 libs = [ f for f in os.listdir(libdir) 826 if os.path.isfile(os.path.join(libdir, f)) and f.endswith('.so') ] 827 if 'libstlport_shared.so' in libs: 828 OPTION_STDCXXPYPR = 'stlport' 829 elif 'libgnustl_shared.so' in libs: 830 OPTION_STDCXXPYPR = 'gnustdcxx' 831 832 if OPTION_STDCXXPYPR == 'stlport': 833 pypr_folder = PYPRPR_BASE + 'stlport/gppfs-0.2/stlport' 834 pypr_function = 'register_stlport_printers' 835 elif OPTION_STDCXXPYPR.startswith('gnustdcxx'): 836 if OPTION_STDCXXPYPR == 'gnustdcxx': 837 NDK_TOOLCHAIN_VERSION = get_build_var_for_abi('NDK_TOOLCHAIN_VERSION', COMPAT_ABI) 838 log('Using toolchain version: %s' % (NDK_TOOLCHAIN_VERSION)) 839 pypr_folder = PYPRPR_GNUSTDCXX_BASE + 'gcc-' + NDK_TOOLCHAIN_VERSION 840 else: 841 pypr_folder = PYPRPR_GNUSTDCXX_BASE + OPTION_STDCXXPYPR.replace('gnustdcxx-','gcc-') 842 pypr_function = 'register_libstdcxx_printers' 843 844 # Now launch the appropriate gdb client with the right init commands 845 # 846 GDBCLIENT = '%sgdb' % (TOOLCHAIN_PREFIX) 847 GDBSETUP = '%s/gdb.setup' % (APP_OUT) 848 shutil.copyfile(GDBSETUP_INIT, GDBSETUP) 849 with open(GDBSETUP, "a") as gdbsetup: 850 #uncomment the following to debug the remote connection only 851 #gdbsetup.write('set debug remote 1\n') 852 gdbsetup.write('file '+pulled_app_process+'\n') 853 gdbsetup.write('target remote :%d\n' % (DEBUG_PORT)) 854 gdbsetup.write('set breakpoint pending on\n') 855 856 if pypr_function: 857 gdbsetup.write('python\n') 858 gdbsetup.write('import sys\n') 859 gdbsetup.write('sys.path.append("%s")\n' % pypr_folder) 860 gdbsetup.write('from printers import %s\n' % pypr_function) 861 gdbsetup.write('%s(None)\n' % pypr_function) 862 gdbsetup.write('end\n') 863 864 if OPTION_EXEC: 865 with open(OPTION_EXEC, 'r') as execfile: 866 for line in execfile: 867 gdbsetup.write(line) 868 gdbsetup.close() 869 870 gdbargs = [GDBCLIENT, '-x', '%s' % (GDBSETUP)] 871 if OPTION_TUI: 872 gdbhelp = subprocess.check_output([GDBCLIENT, '--help']).decode('ascii') 873 try: 874 gdbhelp.index('--tui') 875 gdbargs.append('--tui') 876 OPTION_TUI = 'running' 877 except: 878 print('Warning: Disabled tui mode as %s does not support it' % (os.path.basename(GDBCLIENT))) 879 gdbp = subprocess.Popen(gdbargs) 880 while gdbp.returncode is None: 881 try: 882 gdbp.communicate() 883 except KeyboardInterrupt: 884 pass 885 log("Exited gdb, returncode %d" % gdbp.returncode) 886 887if __name__ == '__main__': 888 main() 889