summaryrefslogtreecommitdiff
path: root/java/debug.sh
blob: c5d4014135560609878a1be4bfbb85d337ee7179 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
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