summaryrefslogtreecommitdiff
path: root/java/debug.sh
diff options
context:
space:
mode:
Diffstat (limited to 'java/debug.sh')
-rwxr-xr-xjava/debug.sh371
1 files changed, 371 insertions, 0 deletions
diff --git a/java/debug.sh b/java/debug.sh
new file mode 100755
index 00000000000..c5d40141355
--- /dev/null
+++ b/java/debug.sh
@@ -0,0 +1,371 @@
+#!/bin/bash
+### Run Emacs under GDB or JDB on Android.
+
+## Copyright (C) 2023-2024 Free Software Foundation, Inc.
+
+## This file is part of GNU Emacs.
+
+## GNU Emacs is free software: you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation, either version 3 of the License, or
+## (at your option) any later version.
+
+## GNU Emacs is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+
+## You should have received a copy of the GNU General Public License
+## along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
+
+set -m
+oldpwd=`pwd`
+cd `dirname $0`
+
+devices=`adb devices | grep device | awk -- '/device\y/ { print $1 }' -`
+device=
+progname=$0
+package=org.gnu.emacs
+activity=org.gnu.emacs.EmacsActivity
+gdb_port=5039
+jdb_port=64013
+jdb=no
+attach_existing=no
+gdbserver=
+gdb=gdb
+
+while [ $# -gt 0 ]; do
+ case "$1" in
+ ## This option specifies the serial number of a device to use.
+ "--device" )
+ device="$2"
+ if [ -z device ]; then
+ echo "You must specify an argument to --device"
+ exit 1
+ fi
+ shift
+ ;;
+ "--help" )
+ echo "Usage: $progname [options] -- [gdb options]"
+ echo ""
+ echo " --device DEVICE run Emacs on the specified device"
+ echo " --port PORT run the GDB server on a specific port"
+ echo " --jdb-port PORT run the JDB server on a specific port"
+ echo " --jdb run JDB instead of GDB"
+ echo " --gdb use specified GDB binary"
+ echo " --attach-existing attach to an existing process"
+ echo " --gdbserver BINARY upload and use the specified gdbserver binary"
+ echo " --help print this message"
+ echo ""
+ echo "Available devices:"
+ for device in $devices; do
+ echo " " $device
+ done
+ echo ""
+ exit 0
+ ;;
+ "--jdb" )
+ jdb=yes
+ ;;
+ "--gdb" )
+ shift
+ gdb=$1
+ ;;
+ "--gdbserver" )
+ shift
+ gdbserver=$1
+ ;;
+ "--port" )
+ shift
+ gdb_port=$1
+ ;;
+ "--jdb-port" )
+ shift
+ jdb_port=$1
+ ;;
+ "--attach-existing" )
+ attach_existing=yes
+ ;;
+ "--" )
+ shift
+ gdbargs=$@
+ break;
+ ;;
+ * )
+ echo "$progname: Unrecognized argument $1"
+ exit 1
+ ;;
+ esac
+ shift
+done
+
+if [ -z "$devices" ]; then
+ echo "No devices are available."
+ exit 1
+fi
+
+if [ `wc -w <<< "$devices"` -gt 1 ] && [ -z $device ]; then
+ echo "Multiple devices are available. Please specify one with"
+ echo "the option --device and try again."
+ exit 1
+fi
+
+if [ -z $device ]; then
+ device=$devices
+fi
+
+echo "Looking for $package on device $device"
+
+# Find the application data directory
+app_data_dir=`adb -s $device shell run-as $package sh -c 'pwd 2> /dev/null'`
+
+if [ -z $app_data_dir ]; then
+ echo "The data directory for the package $package was not found."
+ echo "Is it installed?"
+fi
+
+echo "Found application data directory at" "$app_data_dir"
+
+# Generate an awk script to extract PIDs from Android ps output. It
+# is enough to run `ps' as the package user on newer versions of
+# Android, but that doesn't work on Android 2.3.
+cat << EOF > tmp.awk
+BEGIN {
+ pid = 0;
+ pid_column = 2;
+}
+
+{
+ # Remove any trailing carriage return from the input line.
+ gsub ("\r", "", \$NF)
+
+ # If this is line 1, figure out which column contains the PID.
+ if (NR == 1)
+ {
+ for (n = 1; n <= NF; ++n)
+ {
+ if (\$n == "PID")
+ pid_column=n;
+ }
+ }
+ else if (\$NF == "$package")
+ print \$pid_column
+}
+EOF
+
+# Make sure that file disappears once this script exits.
+trap "rm -f $(pwd)/tmp.awk" 0
+
+# First, run ps to fetch the list of process IDs.
+package_pids=`adb -s $device shell ps`
+
+# Next, extract the list of PIDs currently running.
+package_pids=`awk -f tmp.awk <<< $package_pids`
+
+if [ "$attach_existing" != "yes" ]; then
+ # Finally, kill each existing process.
+ for pid in $package_pids; do
+ echo "Killing existing process $pid..."
+ adb -s $device shell run-as $package kill -9 $pid &> /dev/null
+ done
+
+ # Now run the main activity. This must be done as the adb user and
+ # not as the package user.
+ echo "Starting activity $activity and attaching debugger"
+
+ # Exit if the activity could not be started.
+ adb -s $device shell am start -D -n "$package/$activity"
+ if [ ! $? ]; then
+ exit 1;
+ fi
+
+ # Sleep for a bit. Otherwise, the process may not have started
+ # yet.
+ sleep 1
+
+ # Now look for processes matching the package again.
+ package_pids=`adb -s $device shell ps`
+
+ # Next, remove lines matching "ps" itself.
+ package_pids=`awk -f tmp.awk <<< $package_pids`
+fi
+
+rm tmp.awk
+
+pid=$package_pids
+num_pids=`wc -w <<< "$package_pids"`
+
+if [ $num_pids -gt 1 ]; then
+ echo "More than one process was started:"
+ echo ""
+ adb -s $device shell run-as $package ps | awk -- "{
+ if (!match (\$0, /ps/) && match (\$0, /$package/))
+ print \$0
+ }"
+ echo ""
+ printf "Which one do you want to attach to? "
+ read pid
+elif [ -z $package_pids ]; then
+ echo "No processes were found to attach to."
+ exit 1
+fi
+
+# If either --jdb was specified or debug.sh is not connecting to an
+# existing process, then store a suitable JDB invocation in
+# jdb_command. GDB will then run JDB to unblock the application from
+# the wait dialog after startup.
+
+if [ "$jdb" = "yes" ] || [ "$attach_existing" != yes ]; then
+ adb -s $device forward --remove-all
+ adb -s $device forward "tcp:$jdb_port" "jdwp:$pid"
+
+ if [ ! $? ]; then
+ echo "Failed to forward jdwp:$pid to $jdb_port!"
+ echo "Perhaps you need to specify a different port with --port?"
+ exit 1;
+ fi
+
+ jdb_command="jdb -connect \
+ com.sun.jdi.SocketAttach:hostname=localhost,port=$jdb_port"
+
+ if [ $jdb = "yes" ]; then
+ # Just start JDB and then exit
+ $jdb_command
+ exit 1
+ fi
+fi
+
+if [ -n "$jdb_command" ]; then
+ echo "Starting JDB to unblock application."
+
+ # Start JDB to unblock the application.
+ coproc JDB { $jdb_command; }
+
+ # Tell JDB to first suspend all threads.
+ echo "suspend" >&${JDB[1]}
+
+ # Tell JDB to print a magic string once the program is
+ # initialized.
+ echo "print \"__verify_jdb_has_started__\"" >&${JDB[1]}
+
+ # Now wait for JDB to give the string back.
+ line=
+ while :; do
+ read -u ${JDB[0]} line
+ if [ ! $? ]; then
+ echo "Failed to read JDB output"
+ exit 1
+ fi
+
+ case "$line" in
+ *__verify_jdb_has_started__*)
+ # Android only polls for a Java debugger every 200ms, so
+ # the debugger must be connected for at least that long.
+ echo "Pausing 1 second for the program to continue."
+ sleep 1
+ break
+ ;;
+ esac
+ done
+
+ # Note that JDB does not exit until GDB is fully attached!
+fi
+
+# See if gdbserver has to be uploaded
+gdbserver_cmd=
+is_root=
+if [ -z "$gdbserver" ]; then
+ gdbserver_bin=/system/bin/gdbserver64
+else
+ gdbserver_bin=/data/local/tmp/gdbserver
+ gdbserver_cat="cat $gdbserver_bin | run-as $package sh -c \
+ \"tee gdbserver > /dev/null\""
+
+ # Upload the specified gdbserver binary to the device.
+ adb -s $device push "$gdbserver" "$gdbserver_bin"
+
+ if (adb -s $device shell ls /system/bin | grep -G tee); then
+ # Copy it to the user directory.
+ adb -s $device shell "$gdbserver_cat"
+ adb -s $device shell "run-as $package chmod 777 gdbserver"
+ gdbserver_cmd="./gdbserver"
+ else
+ # Hopefully this is an old version of Android which allows
+ # execution from /data/local/tmp. Its `chmod' doesn't support
+ # `+x' either.
+ adb -s $device shell "chmod 777 $gdbserver_bin"
+ gdbserver_cmd="$gdbserver_bin"
+
+ # If the user is root, then there is no need to open any kind
+ # of TCP socket.
+ if (adb -s $device shell id | grep -G root); then
+ gdbserver=
+ is_root=yes
+ fi
+ fi
+fi
+
+# Now start gdbserver on the device asynchronously.
+
+echo "Attaching gdbserver to $pid on $device..."
+exec 5<> /tmp/file-descriptor-stamp
+rm -f /tmp/file-descriptor-stamp
+
+if [ -z "$gdbserver" ]; then
+ if [ "$is_root" = "yes" ]; then
+ adb -s $device shell $gdbserver_bin --multi \
+ "0.0.0.0:7564" --attach $pid >&5 &
+ gdb_socket="tcp:7564"
+ else
+ adb -s $device shell $gdbserver_bin --multi \
+ "0.0.0.0:7564" --attach $pid >&5 &
+ gdb_socket="tcp:7564"
+ fi
+else
+ # Normally the program cannot access $gdbserver_bin when it is
+ # placed in /data/local/tmp.
+ adb -s $device shell run-as $package $gdbserver_cmd --multi \
+ "+debug.$package.socket" --attach $pid >&5 &
+ gdb_socket="localfilesystem:$app_data_dir/debug.$package.socket"
+fi
+
+# In order to allow adb to forward to the gdbserver socket, make the
+# app data directory a+x.
+adb -s $device shell run-as $package chmod a+x $app_data_dir
+
+# Wait until gdbserver successfully runs.
+line=
+while read -u 5 line; do
+ case "$line" in
+ *Attached* )
+ break;
+ ;;
+ *error* | *Error* | failed )
+ echo "GDB error:" $line
+ exit 1
+ ;;
+ * )
+ ;;
+ esac
+done
+
+# Now that GDB is attached, tell the Java debugger to resume execution
+# and then exit.
+
+if [ -n "$jdb_command" ]; then
+ echo "resume" >&${JDB[1]}
+ echo "exit" >&${JDB[1]}
+fi
+
+# Forward the gdb server port here.
+adb -s $device forward "tcp:$gdb_port" $gdb_socket
+if [ ! $? ]; then
+ echo "Failed to forward $app_data_dir/debug.$package.socket"
+ echo "to $gdb_port! Perhaps you need to specify a different port"
+ echo "with --port?"
+ exit 1;
+fi
+
+# Finally, start gdb with any extra arguments needed.
+cd "$oldpwd"
+$gdb --eval-command "target remote localhost:$gdb_port" $gdbargs