aboutsummaryrefslogtreecommitdiff
path: root/ndk-gdb.py
diff options
context:
space:
mode:
authorRay Donnelly <mingw.android@gmail.com>2013-06-04 23:57:36 +0100
committerRay Donnelly <mingw.android@gmail.com>2013-06-05 20:51:58 +0100
commitbde0f1fe42a470fbf1261ab2afef51fdffea843b (patch)
tree6475fa8845d970c6d369aa0ffd2137483fe9d513 /ndk-gdb.py
parent61bab5f67a6b587b87c01915d36fd2915d6ff244 (diff)
downloadndk-bde0f1fe42a470fbf1261ab2afef51fdffea843b.tar.gz
Implement java-wait feature in ndk-gdb.py
Prevents early breakpoints from being missed. Original bug report from: https://code.google.com/p/android/issues/detail?id=41278 Original shell script version from: https://android-review.googlesource.com/#/c/48029/ Change-Id: I62809e73ee06ce962f4de3fc8ddc652744384c5b
Diffstat (limited to 'ndk-gdb.py')
-rwxr-xr-xndk-gdb.py90
1 files changed, 72 insertions, 18 deletions
diff --git a/ndk-gdb.py b/ndk-gdb.py
index a4bd043a7..b3b873887 100755
--- a/ndk-gdb.py
+++ b/ndk-gdb.py
@@ -30,7 +30,7 @@ r'''
import sys, os, argparse, subprocess, types
import xml.etree.cElementTree as ElementTree
-import shutil
+import shutil, time
from threading import Thread
try:
from Queue import Queue, Empty
@@ -65,6 +65,7 @@ VERBOSE = False
PROJECT = None
ADB_CMD = None
GNUMAKE_CMD = None
+JDB_CMD = None
# Extra arguments passed to the NDK build system when
# querying it.
GNUMAKE_FLAGS = []
@@ -75,9 +76,10 @@ OPTION_START = None
OPTION_LAUNCH = None
OPTION_LAUNCH_LIST = None
OPTION_TUI = None
-
+OPTION_WAIT = ['-D']
DEBUG_PORT = 5039
+JDB_PORT = 65534
# Name of the manifest file
MANIFEST = 'AndroidManifest.xml'
@@ -104,9 +106,10 @@ def handle_args():
global VERBOSE, DEBUG_PORT, DELAY, DEVICE_SERIAL
global GNUMAKE_CMD, GNUMAKE_FLAGS
global ADB_CMD, ADB_FLAGS
+ global JDB_CMD
global PROJECT, NDK
global OPTION_START, OPTION_LAUNCH, OPTION_LAUNCH_LIST
- global OPTION_FORCE, OPTION_EXEC, OPTION_TUI
+ global OPTION_FORCE, OPTION_EXEC, OPTION_TUI, OPTION_WAIT
parser = argparse.ArgumentParser(description='''
Setup a gdb debugging session for your Android NDK application.
@@ -177,6 +180,10 @@ def handle_args():
help='Flag to pass to gnumake, e.g. NDK_TOOLCHAIN_VERSION=4.8',
action='append', dest='gnumake_flags')
+ parser.add_argument( '--nowait',
+ help='Do not wait for debugger to attach (may miss early JNI breakpoints)',
+ action='store_true', dest='nowait')
+
args = parser.parse_args()
VERBOSE = args.verbose
@@ -184,6 +191,7 @@ def handle_args():
ndk_bin = ndk_bin_path(NDK)
(found_adb, ADB_CMD) = find_program('adb', [ndk_bin])
(found_gnumake, GNUMAKE_CMD) = find_program('make', [ndk_bin])
+ (found_jdb, JDB_CMD) = find_program('jdb', [])
if not found_gnumake:
error('Failed to find GNU make')
@@ -240,6 +248,11 @@ def handle_args():
if args.gnumake_flags != None:
GNUMAKE_FLAGS = args.gnumake_flags
+ if args.nowait == True:
+ OPTION_WAIT = []
+ elif not found_jdb:
+ error('Failed to find jdb.\n..you can use --nowait to disable jdb\n..but may miss early breakpoints.')
+
def get_build_var(var):
global GNUMAKE_CMD, GNUMAKE_FLAGS, NDK, PROJECT
text = subprocess.check_output([GNUMAKE_CMD,
@@ -273,23 +286,41 @@ def output_gdbserver(text):
if not OPTION_TUI or OPTION_TUI != 'running':
print(text)
-def background_spawn(args, redirect_stderr, output_fn):
+# Likewise, silent in tui mode (also prepends 'JDB :: ')
+def output_jdb(text):
+ if not OPTION_TUI or OPTION_TUI != 'running':
+ print('JDB :: %s' % text)
+
+def input_jdb(inhandle):
+ while True:
+ inhandle.write('\n')
+ time.sleep(1.0)
+
+def background_spawn(args, redirect_stderr, output_fn, redirect_stdin = None, input_fn = None):
- def async_stdout(out, queue, output_fn):
- for line in iter(out.readline, b''):
+ def async_stdout(outhandle, queue, output_fn):
+ for line in iter(outhandle.readline, b''):
output_fn(line.replace('\r', '').replace('\n', ''))
- out.close()
+ outhandle.close()
- def async_stderr(out, queue, output_fn):
- for line in iter(out.readline, b''):
+ def async_stderr(outhandle, queue, output_fn):
+ for line in iter(outhandle.readline, b''):
output_fn(line.replace('\r', '').replace('\n', ''))
- out.close()
+ outhandle.close()
+
+ def async_stdin(inhandle, queue, input_fn):
+ input_fn(inhandle)
+ inhandle.close()
if redirect_stderr:
used_stderr = subprocess.PIPE
else:
used_stderr = subprocess.STDOUT
- p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=used_stderr,
+ if redirect_stdin:
+ used_stdin = subprocess.PIPE
+ else:
+ used_stdin = None
+ p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=used_stderr, stdin=used_stdin,
bufsize=1, close_fds='posix' in sys.builtin_module_names)
qo = Queue()
to = Thread(target=async_stdout, args=(p.stdout, qo, output_fn))
@@ -299,6 +330,10 @@ def background_spawn(args, redirect_stderr, output_fn):
te = Thread(target=async_stderr, args=(p.stderr, qo, output_fn))
te.daemon = True
te.start()
+ if redirect_stdin:
+ ti = Thread(target=async_stdin, args=(p.stdin, qo, input_fn))
+ ti.daemon = True
+ ti.start()
def adb_cmd(redirect_stderr, args, log_command=False, adb_trace=False, background=False):
global ADB_CMD, ADB_FLAGS, DEVICE_SERIAL
@@ -444,8 +479,9 @@ def extract_launchable(xmlfile):
def main():
global ADB_CMD, NDK, PROJECT
+ global JDB_CMD
global OPTION_START, OPTION_LAUNCH, OPTION_LAUNCH_LIST
- global OPTION_FORCE, OPTION_EXEC, OPTION_TUI
+ global OPTION_FORCE, OPTION_EXEC, OPTION_TUI, OPTION_WAIT
if NDK.find(' ')!=-1:
error('NDK path cannot contain space')
@@ -460,7 +496,6 @@ def main():
log('Using ADB flags: %s' % (ADB_FLAGS))
else:
log('Using ADB flags: %s "%s"' % (ADB_FLAGS,DEVICE_SERIAL))
-
if PROJECT != None:
log('Using specified project path: %s' % (PROJECT))
if not os.path.isdir(PROJECT):
@@ -600,7 +635,7 @@ After one of these, re-install to the device!''' % (PACKAGE_NAME))
if OPTION_LAUNCH:
log('Launching activity: %s/%s' % (PACKAGE_NAME,OPTION_LAUNCH[0]))
retcode,LAUNCH_OUTPUT=adb_cmd(True,
- ['shell', 'am', 'start', '-n', '%s/%s' % (PACKAGE_NAME,OPTION_LAUNCH[0])],
+ ['shell', 'am', 'start'] + OPTION_WAIT + ['-n', '%s/%s' % (PACKAGE_NAME,OPTION_LAUNCH[0])],
log_command=True)
if retcode:
error('''Could not launch specified activity: %s
@@ -641,7 +676,7 @@ After one of these, re-install to the device!''' % (PACKAGE_NAME))
adb_cmd(False,
['shell', 'run-as', PACKAGE_NAME, 'lib/gdbserver', '+%s' % (DEBUG_SOCKET), '--attach', str(PID)],
log_command=True, adb_trace=True, background=True)
- log('Launched gdbserver succesfully')
+ log('Launched gdbserver succesfully.')
# Make sure gdbserver was launched - debug check.
# adb_var_shell(['sleep', '0.1'], log_command=False)
@@ -670,6 +705,18 @@ After one of these, re-install to the device!''' % (PACKAGE_NAME))
adb_cmd(False, ['pull', '/system/lib/libc.so', '%s/libc.so' % (APP_OUT)], log_command=True)
log('Pulled libc.so from device/emulator.')
+ # Setup JDB connection, for --start or --launch
+ if (OPTION_START != None or OPTION_LAUNCH != None) and len(OPTION_WAIT):
+ log('Set up JDB connection, using jdb command: %s' % JDB_CMD)
+ retcode,_ = adb_cmd(False,
+ ['forward', 'tcp:%d' % (JDB_PORT), 'jdwp:%d' % (PID)],
+ log_command=True)
+ time.sleep(1.0)
+ if retcode:
+ error('Could not forward JDB port')
+ background_spawn([JDB_CMD,'-connect','com.sun.jdi.SocketAttach:hostname=localhost,port=%d' % (JDB_PORT)], True, output_jdb, True, input_jdb)
+ time.sleep(1.0)
+
# Now launch the appropriate gdb client with the right init commands
#
GDBCLIENT = '%sgdb' % (TOOLCHAIN_PREFIX)
@@ -677,12 +724,13 @@ After one of these, re-install to the device!''' % (PACKAGE_NAME))
shutil.copyfile(GDBSETUP_INIT, GDBSETUP)
with open(GDBSETUP, "a") as gdbsetup:
#uncomment the following to debug the remote connection only
- #echo "set debug remote 1" >> $GDBSETUP
+ #gdbsetup.write('set debug remote 1\n')
gdbsetup.write('file '+APP_PROCESS+'\n')
gdbsetup.write('target remote :%d\n' % (DEBUG_PORT))
+ gdbsetup.write('set breakpoint pending on\n')
if OPTION_EXEC:
with open(OPTION_EXEC, 'r') as execfile:
- for line in execfile.readline():
+ for line in execfile:
gdbsetup.write(line)
gdbsetup.close()
@@ -695,7 +743,13 @@ After one of these, re-install to the device!''' % (PACKAGE_NAME))
OPTION_TUI = 'running'
except:
print('Warning: Disabled tui mode as %s does not support it' % (os.path.basename(GDBCLIENT)))
- subprocess.call(gdbargs)
+ gdbp = subprocess.Popen(gdbargs)
+ while gdbp.returncode is None:
+ try:
+ gdbp.communicate()
+ except KeyboardInterrupt:
+ pass
+ log("Exited gdb, returncode %d" % gdbp.returncode)
if __name__ == '__main__':
main()