diff options
Diffstat (limited to 'java/debug.sh')
| -rwxr-xr-x | java/debug.sh | 368 |
1 files changed, 368 insertions, 0 deletions
diff --git a/java/debug.sh b/java/debug.sh new file mode 100755 index 00000000000..d6e439bec90 --- /dev/null +++ b/java/debug.sh | |||
| @@ -0,0 +1,368 @@ | |||
| 1 | #!/bin/bash | ||
| 2 | ### Run Emacs under GDB or JDB on Android. | ||
| 3 | |||
| 4 | ## Copyright (C) 2023 Free Software Foundation, Inc. | ||
| 5 | |||
| 6 | ## This file is part of GNU Emacs. | ||
| 7 | |||
| 8 | ## GNU Emacs is free software: you can redistribute it and/or modify | ||
| 9 | ## it under the terms of the GNU General Public License as published by | ||
| 10 | ## the Free Software Foundation, either version 3 of the License, or | ||
| 11 | ## (at your option) any later version. | ||
| 12 | |||
| 13 | ## GNU Emacs is distributed in the hope that it will be useful, | ||
| 14 | ## but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 15 | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 16 | ## GNU General Public License for more details. | ||
| 17 | |||
| 18 | ## You should have received a copy of the GNU General Public License | ||
| 19 | ## along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. | ||
| 20 | |||
| 21 | set -m | ||
| 22 | oldpwd=`pwd` | ||
| 23 | cd `dirname $0` | ||
| 24 | |||
| 25 | devices=`adb devices | grep device | awk -- '/device\y/ { print $1 }' -` | ||
| 26 | device= | ||
| 27 | progname=$0 | ||
| 28 | package=org.gnu.emacs | ||
| 29 | activity=org.gnu.emacs.EmacsActivity | ||
| 30 | gdb_port=5039 | ||
| 31 | jdb_port=64013 | ||
| 32 | jdb=no | ||
| 33 | attach_existing=no | ||
| 34 | gdbserver= | ||
| 35 | gdb=gdb | ||
| 36 | |||
| 37 | while [ $# -gt 0 ]; do | ||
| 38 | case "$1" in | ||
| 39 | ## This option specifies the serial number of a device to use. | ||
| 40 | "--device" ) | ||
| 41 | device="$2" | ||
| 42 | if [ -z device ]; then | ||
| 43 | echo "You must specify an argument to --device" | ||
| 44 | exit 1 | ||
| 45 | fi | ||
| 46 | shift | ||
| 47 | ;; | ||
| 48 | "--help" ) | ||
| 49 | echo "Usage: $progname [options] -- [gdb options]" | ||
| 50 | echo "" | ||
| 51 | echo " --device DEVICE run Emacs on the specified device" | ||
| 52 | echo " --port PORT run the GDB server on a specific port" | ||
| 53 | echo " --jdb-port PORT run the JDB server on a specific port" | ||
| 54 | echo " --jdb run JDB instead of GDB" | ||
| 55 | echo " --gdb use specified GDB binary" | ||
| 56 | echo " --attach-existing attach to an existing process" | ||
| 57 | echo " --gdbserver BINARY upload and use the specified gdbserver binary" | ||
| 58 | echo " --help print this message" | ||
| 59 | echo "" | ||
| 60 | echo "Available devices:" | ||
| 61 | for device in $devices; do | ||
| 62 | echo " " $device | ||
| 63 | done | ||
| 64 | echo "" | ||
| 65 | exit 0 | ||
| 66 | ;; | ||
| 67 | "--jdb" ) | ||
| 68 | jdb=yes | ||
| 69 | ;; | ||
| 70 | "--gdb" ) | ||
| 71 | shift | ||
| 72 | gdb=$1 | ||
| 73 | ;; | ||
| 74 | "--gdbserver" ) | ||
| 75 | shift | ||
| 76 | gdbserver=$1 | ||
| 77 | ;; | ||
| 78 | "--port" ) | ||
| 79 | shift | ||
| 80 | gdb_port=$1 | ||
| 81 | ;; | ||
| 82 | "--jdb-port" ) | ||
| 83 | shift | ||
| 84 | jdb_port=$1 | ||
| 85 | ;; | ||
| 86 | "--attach-existing" ) | ||
| 87 | attach_existing=yes | ||
| 88 | ;; | ||
| 89 | "--" ) | ||
| 90 | shift | ||
| 91 | gdbargs=$@ | ||
| 92 | break; | ||
| 93 | ;; | ||
| 94 | * ) | ||
| 95 | echo "$progname: Unrecognized argument $1" | ||
| 96 | exit 1 | ||
| 97 | ;; | ||
| 98 | esac | ||
| 99 | shift | ||
| 100 | done | ||
| 101 | |||
| 102 | if [ -z "$devices" ]; then | ||
| 103 | echo "No devices are available." | ||
| 104 | exit 1 | ||
| 105 | fi | ||
| 106 | |||
| 107 | if [ -z $device ]; then | ||
| 108 | device=$devices | ||
| 109 | fi | ||
| 110 | |||
| 111 | if [ `wc -w <<< "$devices"` -gt 1 ] && [ -z device ]; then | ||
| 112 | echo "Multiple devices are available. Please pick one using" | ||
| 113 | echo "--device and try again." | ||
| 114 | fi | ||
| 115 | |||
| 116 | echo "Looking for $package on device $device" | ||
| 117 | |||
| 118 | # Find the application data directory | ||
| 119 | app_data_dir=`adb -s $device shell run-as $package sh -c 'pwd 2> /dev/null'` | ||
| 120 | |||
| 121 | if [ -z $app_data_dir ]; then | ||
| 122 | echo "The data directory for the package $package was not found." | ||
| 123 | echo "Is it installed?" | ||
| 124 | fi | ||
| 125 | |||
| 126 | echo "Found application data directory at" "$app_data_dir" | ||
| 127 | |||
| 128 | # Generate an awk script to extract PIDs from Android ps output. It | ||
| 129 | # is enough to run `ps' as the package user on newer versions of | ||
| 130 | # Android, but that doesn't work on Android 2.3. | ||
| 131 | cat << EOF > tmp.awk | ||
| 132 | BEGIN { | ||
| 133 | pid = 0; | ||
| 134 | pid_column = 2; | ||
| 135 | } | ||
| 136 | |||
| 137 | { | ||
| 138 | # Remove any trailing carriage return from the input line. | ||
| 139 | gsub ("\r", "", \$NF) | ||
| 140 | |||
| 141 | # If this is line 1, figure out which column contains the PID. | ||
| 142 | if (NR == 1) | ||
| 143 | { | ||
| 144 | for (n = 1; n <= NF; ++n) | ||
| 145 | { | ||
| 146 | if (\$n == "PID") | ||
| 147 | pid_column=n; | ||
| 148 | } | ||
| 149 | } | ||
| 150 | else if (\$NF == "$package") | ||
| 151 | print \$pid_column | ||
| 152 | } | ||
| 153 | EOF | ||
| 154 | |||
| 155 | # Make sure that file disappears once this script exits. | ||
| 156 | trap "rm -f $(pwd)/tmp.awk" 0 | ||
| 157 | |||
| 158 | # First, run ps to fetch the list of process IDs. | ||
| 159 | package_pids=`adb -s $device shell ps` | ||
| 160 | |||
| 161 | # Next, extract the list of PIDs currently running. | ||
| 162 | package_pids=`awk -f tmp.awk <<< $package_pids` | ||
| 163 | |||
| 164 | if [ "$attach_existing" != "yes" ]; then | ||
| 165 | # Finally, kill each existing process. | ||
| 166 | for pid in $package_pids; do | ||
| 167 | echo "Killing existing process $pid..." | ||
| 168 | adb -s $device shell run-as $package kill -9 $pid &> /dev/null | ||
| 169 | done | ||
| 170 | |||
| 171 | # Now run the main activity. This must be done as the adb user and | ||
| 172 | # not as the package user. | ||
| 173 | echo "Starting activity $activity and attaching debugger" | ||
| 174 | |||
| 175 | # Exit if the activity could not be started. | ||
| 176 | adb -s $device shell am start -D -n "$package/$activity" | ||
| 177 | if [ ! $? ]; then | ||
| 178 | exit 1; | ||
| 179 | fi | ||
| 180 | |||
| 181 | # Sleep for a bit. Otherwise, the process may not have started | ||
| 182 | # yet. | ||
| 183 | sleep 1 | ||
| 184 | |||
| 185 | # Now look for processes matching the package again. | ||
| 186 | package_pids=`adb -s $device shell ps` | ||
| 187 | |||
| 188 | # Next, remove lines matching "ps" itself. | ||
| 189 | package_pids=`awk -f tmp.awk <<< $package_pids` | ||
| 190 | fi | ||
| 191 | |||
| 192 | pid=$package_pids | ||
| 193 | num_pids=`wc -w <<< "$package_pids"` | ||
| 194 | |||
| 195 | if [ $num_pids -gt 1 ]; then | ||
| 196 | echo "More than one process was started:" | ||
| 197 | echo "" | ||
| 198 | adb -s $device shell run-as $package ps | awk -- "{ | ||
| 199 | if (!match (\$0, /ps/) && match (\$0, /$package/)) | ||
| 200 | print \$0 | ||
| 201 | }" | ||
| 202 | echo "" | ||
| 203 | printf "Which one do you want to attach to? " | ||
| 204 | read pid | ||
| 205 | elif [ -z $package_pids ]; then | ||
| 206 | echo "No processes were found to attach to." | ||
| 207 | exit 1 | ||
| 208 | fi | ||
| 209 | |||
| 210 | # If either --jdb was specified or debug.sh is not connecting to an | ||
| 211 | # existing process, then store a suitable JDB invocation in | ||
| 212 | # jdb_command. GDB will then run JDB to unblock the application from | ||
| 213 | # the wait dialog after startup. | ||
| 214 | |||
| 215 | if [ "$jdb" = "yes" ] || [ "$attach_existing" != yes ]; then | ||
| 216 | adb -s $device forward --remove-all | ||
| 217 | adb -s $device forward "tcp:$jdb_port" "jdwp:$pid" | ||
| 218 | |||
| 219 | if [ ! $? ]; then | ||
| 220 | echo "Failed to forward jdwp:$pid to $jdb_port!" | ||
| 221 | echo "Perhaps you need to specify a different port with --port?" | ||
| 222 | exit 1; | ||
| 223 | fi | ||
| 224 | |||
| 225 | jdb_command="jdb -connect \ | ||
| 226 | com.sun.jdi.SocketAttach:hostname=localhost,port=$jdb_port" | ||
| 227 | |||
| 228 | if [ $jdb = "yes" ]; then | ||
| 229 | # Just start JDB and then exit | ||
| 230 | $jdb_command | ||
| 231 | exit 1 | ||
| 232 | fi | ||
| 233 | fi | ||
| 234 | |||
| 235 | if [ -n "$jdb_command" ]; then | ||
| 236 | echo "Starting JDB to unblock application." | ||
| 237 | |||
| 238 | # Start JDB to unblock the application. | ||
| 239 | coproc JDB { $jdb_command; } | ||
| 240 | |||
| 241 | # Tell JDB to first suspend all threads. | ||
| 242 | echo "suspend" >&${JDB[1]} | ||
| 243 | |||
| 244 | # Tell JDB to print a magic string once the program is | ||
| 245 | # initialized. | ||
| 246 | echo "print \"__verify_jdb_has_started__\"" >&${JDB[1]} | ||
| 247 | |||
| 248 | # Now wait for JDB to give the string back. | ||
| 249 | line= | ||
| 250 | while :; do | ||
| 251 | read -u ${JDB[0]} line | ||
| 252 | if [ ! $? ]; then | ||
| 253 | echo "Failed to read JDB output" | ||
| 254 | exit 1 | ||
| 255 | fi | ||
| 256 | |||
| 257 | case "$line" in | ||
| 258 | *__verify_jdb_has_started__*) | ||
| 259 | # Android only polls for a Java debugger every 200ms, so | ||
| 260 | # the debugger must be connected for at least that long. | ||
| 261 | echo "Pausing 1 second for the program to continue." | ||
| 262 | sleep 1 | ||
| 263 | break | ||
| 264 | ;; | ||
| 265 | esac | ||
| 266 | done | ||
| 267 | |||
| 268 | # Note that JDB does not exit until GDB is fully attached! | ||
| 269 | fi | ||
| 270 | |||
| 271 | # See if gdbserver has to be uploaded | ||
| 272 | gdbserver_cmd= | ||
| 273 | is_root= | ||
| 274 | if [ -z "$gdbserver" ]; then | ||
| 275 | gdbserver_bin=/system/bin/gdbserver64 | ||
| 276 | else | ||
| 277 | gdbserver_bin=/data/local/tmp/gdbserver | ||
| 278 | gdbserver_cat="cat $gdbserver_bin | run-as $package sh -c \ | ||
| 279 | \"tee gdbserver > /dev/null\"" | ||
| 280 | |||
| 281 | # Upload the specified gdbserver binary to the device. | ||
| 282 | adb -s $device push "$gdbserver" "$gdbserver_bin" | ||
| 283 | |||
| 284 | if (adb -s $device shell ls /system/bin | grep -G tee); then | ||
| 285 | # Copy it to the user directory. | ||
| 286 | adb -s $device shell "$gdbserver_cat" | ||
| 287 | adb -s $device shell "run-as $package chmod 777 gdbserver" | ||
| 288 | gdbserver_cmd="./gdbserver" | ||
| 289 | else | ||
| 290 | # Hopefully this is an old version of Android which allows | ||
| 291 | # execution from /data/local/tmp. Its `chmod' doesn't support | ||
| 292 | # `+x' either. | ||
| 293 | adb -s $device shell "chmod 777 $gdbserver_bin" | ||
| 294 | gdbserver_cmd="$gdbserver_bin" | ||
| 295 | |||
| 296 | # If the user is root, then there is no need to open any kind | ||
| 297 | # of TCP socket. | ||
| 298 | if (adb -s $device shell id | grep -G root); then | ||
| 299 | gdbserver= | ||
| 300 | is_root=yes | ||
| 301 | fi | ||
| 302 | fi | ||
| 303 | fi | ||
| 304 | |||
| 305 | # Now start gdbserver on the device asynchronously. | ||
| 306 | |||
| 307 | echo "Attaching gdbserver to $pid on $device..." | ||
| 308 | exec 5<> /tmp/file-descriptor-stamp | ||
| 309 | rm -f /tmp/file-descriptor-stamp | ||
| 310 | |||
| 311 | if [ -z "$gdbserver" ]; then | ||
| 312 | if [ "$is_root" = "yes" ]; then | ||
| 313 | adb -s $device shell $gdbserver_bin --multi \ | ||
| 314 | "0.0.0.0:7564" --attach $pid >&5 & | ||
| 315 | gdb_socket="tcp:7564" | ||
| 316 | else | ||
| 317 | adb -s $device shell $gdbserver_bin --multi \ | ||
| 318 | "0.0.0.0:7564" --attach $pid >&5 & | ||
| 319 | gdb_socket="tcp:7564" | ||
| 320 | fi | ||
| 321 | else | ||
| 322 | # Normally the program cannot access $gdbserver_bin when it is | ||
| 323 | # placed in /data/local/tmp. | ||
| 324 | adb -s $device shell run-as $package $gdbserver_cmd --multi \ | ||
| 325 | "+debug.$package.socket" --attach $pid >&5 & | ||
| 326 | gdb_socket="localfilesystem:$app_data_dir/debug.$package.socket" | ||
| 327 | fi | ||
| 328 | |||
| 329 | # In order to allow adb to forward to the gdbserver socket, make the | ||
| 330 | # app data directory a+x. | ||
| 331 | adb -s $device shell run-as $package chmod a+x $app_data_dir | ||
| 332 | |||
| 333 | # Wait until gdbserver successfully runs. | ||
| 334 | line= | ||
| 335 | while read -u 5 line; do | ||
| 336 | case "$line" in | ||
| 337 | *Attached* ) | ||
| 338 | break; | ||
| 339 | ;; | ||
| 340 | *error* | *Error* | failed ) | ||
| 341 | echo "GDB error:" $line | ||
| 342 | exit 1 | ||
| 343 | ;; | ||
| 344 | * ) | ||
| 345 | ;; | ||
| 346 | esac | ||
| 347 | done | ||
| 348 | |||
| 349 | # Now that GDB is attached, tell the Java debugger to resume execution | ||
| 350 | # and then exit. | ||
| 351 | |||
| 352 | if [ -n "$jdb_command" ]; then | ||
| 353 | echo "resume" >&${JDB[1]} | ||
| 354 | echo "exit" >&${JDB[1]} | ||
| 355 | fi | ||
| 356 | |||
| 357 | # Forward the gdb server port here. | ||
| 358 | adb -s $device forward "tcp:$gdb_port" $gdb_socket | ||
| 359 | if [ ! $? ]; then | ||
| 360 | echo "Failed to forward $app_data_dir/debug.$package.socket" | ||
| 361 | echo "to $gdb_port! Perhaps you need to specify a different port" | ||
| 362 | echo "with --port?" | ||
| 363 | exit 1; | ||
| 364 | fi | ||
| 365 | |||
| 366 | # Finally, start gdb with any extra arguments needed. | ||
| 367 | cd "$oldpwd" | ||
| 368 | $gdb --eval-command "target remote localhost:$gdb_port" $gdbargs | ||