OLD | NEW |
| (Empty) |
1 #!/bin/bash | |
2 # | |
3 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
4 # Use of this source code is governed by a BSD-style license that can be | |
5 # found in the LICENSE file. | |
6 # | |
7 | |
8 # A generic script used to attach to a running Chromium process and | |
9 # debug it. Most users should not use this directly, but one of the | |
10 # wrapper scripts like adb_gdb_content_shell | |
11 # | |
12 # Use --help to print full usage instructions. | |
13 # | |
14 | |
15 PROGNAME=$(basename "$0") | |
16 PROGDIR=$(dirname "$0") | |
17 | |
18 # Location of Chromium-top-level sources. | |
19 CHROMIUM_SRC=$(cd "$PROGDIR"/../.. >/dev/null && pwd 2>/dev/null) | |
20 | |
21 # Location of Chromium out/ directory. | |
22 if [ -z "$CHROMIUM_OUT_DIR" ]; then | |
23 CHROMIUM_OUT_DIR=out | |
24 fi | |
25 | |
26 TMPDIR= | |
27 GDBSERVER_PIDFILE= | |
28 TARGET_GDBSERVER= | |
29 COMMAND_PREFIX= | |
30 | |
31 clean_exit () { | |
32 if [ "$TMPDIR" ]; then | |
33 GDBSERVER_PID=$(cat $GDBSERVER_PIDFILE 2>/dev/null) | |
34 if [ "$GDBSERVER_PID" ]; then | |
35 log "Killing background gdbserver process: $GDBSERVER_PID" | |
36 kill -9 $GDBSERVER_PID >/dev/null 2>&1 | |
37 fi | |
38 if [ "$TARGET_GDBSERVER" ]; then | |
39 log "Removing target gdbserver binary: $TARGET_GDBSERVER." | |
40 "$ADB" shell "$COMMAND_PREFIX" rm "$TARGET_GDBSERVER" >/dev/null 2>&1 | |
41 fi | |
42 log "Cleaning up: $TMPDIR" | |
43 rm -rf "$TMPDIR" | |
44 fi | |
45 trap "" EXIT | |
46 exit $1 | |
47 } | |
48 | |
49 # Ensure clean exit on Ctrl-C or normal exit. | |
50 trap "clean_exit 1" INT HUP QUIT TERM | |
51 trap "clean_exit \$?" EXIT | |
52 | |
53 panic () { | |
54 echo "ERROR: $@" >&2 | |
55 exit 1 | |
56 } | |
57 | |
58 fail_panic () { | |
59 if [ $? != 0 ]; then panic "$@"; fi | |
60 } | |
61 | |
62 log () { | |
63 if [ "$VERBOSE" -gt 0 ]; then | |
64 echo "$@" | |
65 fi | |
66 } | |
67 | |
68 DEFAULT_PULL_LIBS_DIR=/tmp/$USER-adb-gdb-libs | |
69 | |
70 # NOTE: Allow wrapper scripts to set various default through ADB_GDB_XXX | |
71 # environment variables. This is only for cosmetic reasons, i.e. to | |
72 # display proper | |
73 | |
74 # Allow wrapper scripts to set the default activity through | |
75 # the ADB_GDB_ACTIVITY variable. Users are still able to change the | |
76 # final activity name through --activity=<name> option. | |
77 # | |
78 # This is only for cosmetic reasons, i.e. to display the proper default | |
79 # in the --help output. | |
80 # | |
81 DEFAULT_ACTIVITY=${ADB_GDB_ACTIVITY:-".Main"} | |
82 | |
83 # Allow wrapper scripts to set the program name through ADB_GDB_PROGNAME | |
84 PROGNAME=${ADB_GDB_PROGNAME:-$(basename "$0")} | |
85 | |
86 ACTIVITY=$DEFAULT_ACTIVITY | |
87 ADB= | |
88 ANNOTATE= | |
89 # Note: Ignore BUILDTYPE variable, because the Ninja build doesn't use it. | |
90 BUILDTYPE= | |
91 FORCE= | |
92 GDBEXEPOSTFIX=gdb | |
93 GDBINIT= | |
94 GDBSERVER= | |
95 HELP= | |
96 NDK_DIR= | |
97 NO_PULL_LIBS= | |
98 PACKAGE_NAME= | |
99 PID= | |
100 PORT= | |
101 PRIVILEGED= | |
102 PRIVILEGED_INDEX= | |
103 PROGRAM_NAME="activity" | |
104 PULL_LIBS= | |
105 PULL_LIBS_DIR= | |
106 SANDBOXED= | |
107 SANDBOXED_INDEX= | |
108 START= | |
109 SU_PREFIX= | |
110 SYMBOL_DIR= | |
111 TARGET_ARCH= | |
112 TOOLCHAIN= | |
113 VERBOSE=0 | |
114 | |
115 for opt; do | |
116 optarg=$(expr "x$opt" : 'x[^=]*=\(.*\)') | |
117 case $opt in | |
118 --adb=*) | |
119 ADB=$optarg | |
120 ;; | |
121 --activity=*) | |
122 ACTIVITY=$optarg | |
123 ;; | |
124 --annotate=3) | |
125 ANNOTATE=$optarg | |
126 ;; | |
127 --force) | |
128 FORCE=true | |
129 ;; | |
130 --gdbserver=*) | |
131 GDBSERVER=$optarg | |
132 ;; | |
133 --gdb=*) | |
134 GDB=$optarg | |
135 ;; | |
136 --help|-h|-?) | |
137 HELP=true | |
138 ;; | |
139 --ndk-dir=*) | |
140 NDK_DIR=$optarg | |
141 ;; | |
142 --no-pull-libs) | |
143 NO_PULL_LIBS=true | |
144 ;; | |
145 --package-name=*) | |
146 PACKAGE_NAME=$optarg | |
147 ;; | |
148 --pid=*) | |
149 PID=$optarg | |
150 ;; | |
151 --port=*) | |
152 PORT=$optarg | |
153 ;; | |
154 --privileged) | |
155 PRIVILEGED=true | |
156 ;; | |
157 --privileged=*) | |
158 PRIVILEGED=true | |
159 PRIVILEGED_INDEX=$optarg | |
160 ;; | |
161 --program-name=*) | |
162 PROGRAM_NAME=$optarg | |
163 ;; | |
164 --pull-libs) | |
165 PULL_LIBS=true | |
166 ;; | |
167 --pull-libs-dir=*) | |
168 PULL_LIBS_DIR=$optarg | |
169 ;; | |
170 --sandboxed) | |
171 SANDBOXED=true | |
172 ;; | |
173 --sandboxed=*) | |
174 SANDBOXED=true | |
175 SANDBOXED_INDEX=$optarg | |
176 ;; | |
177 --script=*) | |
178 GDBINIT=$optarg | |
179 ;; | |
180 --start) | |
181 START=true | |
182 ;; | |
183 --su-prefix=*) | |
184 SU_PREFIX=$optarg | |
185 ;; | |
186 --symbol-dir=*) | |
187 SYMBOL_DIR=$optarg | |
188 ;; | |
189 --out-dir=*) | |
190 CHROMIUM_OUT_DIR=$optarg | |
191 ;; | |
192 --target-arch=*) | |
193 TARGET_ARCH=$optarg | |
194 ;; | |
195 --toolchain=*) | |
196 TOOLCHAIN=$optarg | |
197 ;; | |
198 --ui) | |
199 GDBEXEPOSTFIX=gdbtui | |
200 ;; | |
201 --verbose) | |
202 VERBOSE=$(( $VERBOSE + 1 )) | |
203 ;; | |
204 --debug) | |
205 BUILDTYPE=Debug | |
206 ;; | |
207 --release) | |
208 BUILDTYPE=Release | |
209 ;; | |
210 -*) | |
211 panic "Unknown option $OPT, see --help." >&2 | |
212 ;; | |
213 *) | |
214 if [ "$PACKAGE_NAME" ]; then | |
215 panic "You can only provide a single package name as argument!\ | |
216 See --help." | |
217 fi | |
218 PACKAGE_NAME=$opt | |
219 ;; | |
220 esac | |
221 done | |
222 | |
223 print_help_options () { | |
224 cat <<EOF | |
225 EOF | |
226 } | |
227 | |
228 if [ "$HELP" ]; then | |
229 if [ "$ADB_GDB_PROGNAME" ]; then | |
230 # Assume wrapper scripts all provide a default package name. | |
231 cat <<EOF | |
232 Usage: $PROGNAME [options] | |
233 | |
234 Attach gdb to a running Android $PROGRAM_NAME process. | |
235 EOF | |
236 else | |
237 # Assume this is a direct call to adb_gdb | |
238 cat <<EOF | |
239 Usage: $PROGNAME [options] [<package-name>] | |
240 | |
241 Attach gdb to a running Android $PROGRAM_NAME process. | |
242 | |
243 If provided, <package-name> must be the name of the Android application's | |
244 package name to be debugged. You can also use --package-name=<name> to | |
245 specify it. | |
246 EOF | |
247 fi | |
248 | |
249 cat <<EOF | |
250 | |
251 This script is used to debug a running $PROGRAM_NAME process. | |
252 This can be a regular Android application process, sandboxed (if you use the | |
253 --sandboxed or --sandboxed=<num> option) or a privileged (--privileged or | |
254 --privileged=<num>) service. | |
255 | |
256 This script needs several things to work properly. It will try to pick | |
257 them up automatically for you though: | |
258 | |
259 - target gdbserver binary | |
260 - host gdb client (e.g. arm-linux-androideabi-gdb) | |
261 - directory with symbolic version of $PROGRAM_NAME's shared libraries. | |
262 | |
263 You can also use --ndk-dir=<path> to specify an alternative NDK installation | |
264 directory. | |
265 | |
266 The script tries to find the most recent version of the debug version of | |
267 shared libraries under one of the following directories: | |
268 | |
269 \$CHROMIUM_SRC/<out>/Release/lib/ (used by Ninja builds) | |
270 \$CHROMIUM_SRC/<out>/Debug/lib/ (used by Ninja builds) | |
271 \$CHROMIUM_SRC/<out>/Release/lib.target/ (used by Make builds) | |
272 \$CHROMIUM_SRC/<out>/Debug/lib.target/ (used by Make builds) | |
273 | |
274 Where <out> is 'out' by default, unless the --out=<name> option is used or | |
275 the CHROMIUM_OUT_DIR environment variable is defined. | |
276 | |
277 You can restrict this search by using --release or --debug to specify the | |
278 build type, or simply use --symbol-dir=<path> to specify the file manually. | |
279 | |
280 The script tries to extract the target architecture from your target device, | |
281 but if this fails, will default to 'arm'. Use --target-arch=<name> to force | |
282 its value. | |
283 | |
284 Otherwise, the script will complain, but you can use the --gdbserver, | |
285 --gdb and --symbol-lib options to specify everything manually. | |
286 | |
287 An alternative to --gdb=<file> is to use --toollchain=<path> to specify | |
288 the path to the host target-specific cross-toolchain. | |
289 | |
290 You will also need the 'adb' tool in your path. Otherwise, use the --adb | |
291 option. The script will complain if there is more than one device connected | |
292 and ANDROID_SERIAL is not defined. | |
293 | |
294 The first time you use it on a device, the script will pull many system | |
295 libraries required by the process into a temporary directory. This | |
296 is done to strongly improve the debugging experience, like allowing | |
297 readable thread stacks and more. The libraries are copied to the following | |
298 directory by default: | |
299 | |
300 $DEFAULT_PULL_LIBS_DIR/ | |
301 | |
302 But you can use the --pull-libs-dir=<path> option to specify an | |
303 alternative. The script can detect when you change the connected device, | |
304 and will re-pull the libraries only in this case. You can however force it | |
305 with the --pull-libs option. | |
306 | |
307 Any local .gdbinit script will be ignored, but it is possible to pass a | |
308 gdb command script with the --script=<file> option. Note that its commands | |
309 will be passed to gdb after the remote connection and library symbol | |
310 loading have completed. | |
311 | |
312 Valid options: | |
313 --help|-h|-? Print this message. | |
314 --verbose Increase verbosity. | |
315 | |
316 --sandboxed Debug first sandboxed process we find. | |
317 --sandboxed=<num> Debug specific sandboxed process. | |
318 --symbol-dir=<path> Specify directory with symbol shared libraries. | |
319 --out-dir=<path> Specify the out directory. | |
320 --package-name=<name> Specify package name (alternative to 1st argument). | |
321 --privileged Debug first privileged process we find. | |
322 --privileged=<num> Debug specific privileged process. | |
323 --program-name=<name> Specify program name (cosmetic only). | |
324 --pid=<pid> Specify application process pid. | |
325 --force Kill any previous debugging session, if any. | |
326 --start Start package's activity on device. | |
327 --ui Use gdbtui instead of gdb | |
328 --activity=<name> Activity name for --start [$DEFAULT_ACTIVITY]. | |
329 --annotate=<num> Enable gdb annotation. | |
330 --script=<file> Specify extra GDB init script. | |
331 | |
332 --gdbserver=<file> Specify target gdbserver binary. | |
333 --gdb=<file> Specify host gdb client binary. | |
334 --target-arch=<name> Specify NDK target arch. | |
335 --adb=<file> Specify host ADB binary. | |
336 --port=<port> Specify the tcp port to use. | |
337 | |
338 --su-prefix=<prefix> Prepend <prefix> to 'adb shell' commands that are | |
339 run by this script. This can be useful to use | |
340 the 'su' program on rooted production devices. | |
341 e.g. --su-prefix="su -c" | |
342 | |
343 --pull-libs Force system libraries extraction. | |
344 --no-pull-libs Do not extract any system library. | |
345 --libs-dir=<path> Specify system libraries extraction directory. | |
346 | |
347 --debug Use libraries under out/Debug. | |
348 --release Use libraries under out/Release. | |
349 | |
350 EOF | |
351 exit 0 | |
352 fi | |
353 | |
354 if [ -z "$PACKAGE_NAME" ]; then | |
355 panic "Please specify a package name on the command line. See --help." | |
356 fi | |
357 | |
358 if [ -z "$NDK_DIR" ]; then | |
359 ANDROID_NDK_ROOT=$(PYTHONPATH=$CHROMIUM_SRC/build/android python -c \ | |
360 'from pylib.constants import ANDROID_NDK_ROOT; print ANDROID_NDK_ROOT,') | |
361 else | |
362 if [ ! -d "$NDK_DIR" ]; then | |
363 panic "Invalid directory: $NDK_DIR" | |
364 fi | |
365 if [ ! -f "$NDK_DIR/ndk-build" ]; then | |
366 panic "Not a valid NDK directory: $NDK_DIR" | |
367 fi | |
368 ANDROID_NDK_ROOT=$NDK_DIR | |
369 fi | |
370 | |
371 if [ "$GDBINIT" -a ! -f "$GDBINIT" ]; then | |
372 panic "Unknown --script file: $GDBINIT" | |
373 fi | |
374 | |
375 # Check that ADB is in our path | |
376 if [ -z "$ADB" ]; then | |
377 ADB=$(which adb 2>/dev/null) | |
378 if [ -z "$ADB" ]; then | |
379 panic "Can't find 'adb' tool in your path. Install it or use \ | |
380 --adb=<file>" | |
381 fi | |
382 log "Auto-config: --adb=$ADB" | |
383 fi | |
384 | |
385 # Check that it works minimally | |
386 ADB_VERSION=$($ADB version 2>/dev/null) | |
387 echo "$ADB_VERSION" | fgrep -q -e "Android Debug Bridge" | |
388 if [ $? != 0 ]; then | |
389 panic "Your 'adb' tool seems invalid, use --adb=<file> to specify a \ | |
390 different one: $ADB" | |
391 fi | |
392 | |
393 # If there are more than one device connected, and ANDROID_SERIAL is not | |
394 # defined, print an error message. | |
395 NUM_DEVICES_PLUS2=$($ADB devices 2>/dev/null | wc -l) | |
396 if [ "$NUM_DEVICES_PLUS2" -lt 3 -a -z "$ANDROID_SERIAL" ]; then | |
397 echo "ERROR: There is more than one Android device connected to ADB." | |
398 echo "Please define ANDROID_SERIAL to specify which one to use." | |
399 exit 1 | |
400 fi | |
401 | |
402 # Run a command through adb shell, strip the extra \r from the output | |
403 # and return the correct status code to detect failures. This assumes | |
404 # that the adb shell command prints a final \n to stdout. | |
405 # $1+: command to run | |
406 # Out: command's stdout | |
407 # Return: command's status | |
408 # Note: the command's stderr is lost | |
409 adb_shell () { | |
410 local TMPOUT="$(mktemp)" | |
411 local LASTLINE RET | |
412 local ADB=${ADB:-adb} | |
413 | |
414 # The weird sed rule is to strip the final \r on each output line | |
415 # Since 'adb shell' never returns the command's proper exit/status code, | |
416 # we force it to print it as '%%<status>' in the temporary output file, | |
417 # which we will later strip from it. | |
418 $ADB shell $@ ";" echo "%%\$?" 2>/dev/null | \ | |
419 sed -e 's![[:cntrl:]]!!g' > $TMPOUT | |
420 # Get last line in log, which contains the exit code from the command | |
421 LASTLINE=$(sed -e '$!d' $TMPOUT) | |
422 # Extract the status code from the end of the line, which must | |
423 # be '%%<code>'. | |
424 RET=$(echo "$LASTLINE" | \ | |
425 awk '{ if (match($0, "%%[0-9]+$")) { print substr($0,RSTART+2); } }') | |
426 # Remove the status code from the last line. Note that this may result | |
427 # in an empty line. | |
428 LASTLINE=$(echo "$LASTLINE" | \ | |
429 awk '{ if (match($0, "%%[0-9]+$")) { print substr($0,1,RSTART-1); } }') | |
430 # The output itself: all lines except the status code. | |
431 sed -e '$d' $TMPOUT && printf "%s" "$LASTLINE" | |
432 # Remove temp file. | |
433 rm -f $TMPOUT | |
434 # Exit with the appropriate status. | |
435 return $RET | |
436 } | |
437 | |
438 # Find the target architecture from the target device. | |
439 # This returns an NDK-compatible architecture name. | |
440 # out: NDK Architecture name, or empty string. | |
441 get_gyp_target_arch () { | |
442 local ARCH=$(adb_shell getprop ro.product.cpu.abi) | |
443 case $ARCH in | |
444 mips|x86|x86_64) echo "$ARCH";; | |
445 arm64*) echo "arm64";; | |
446 arm*) echo "arm";; | |
447 *) echo ""; | |
448 esac | |
449 } | |
450 | |
451 if [ -z "$TARGET_ARCH" ]; then | |
452 TARGET_ARCH=$(get_gyp_target_arch) | |
453 if [ -z "$TARGET_ARCH" ]; then | |
454 TARGET_ARCH=arm | |
455 fi | |
456 else | |
457 # Nit: accept Chromium's 'ia32' as a valid target architecture. This | |
458 # script prefers the NDK 'x86' name instead because it uses it to find | |
459 # NDK-specific files (host gdb) with it. | |
460 if [ "$TARGET_ARCH" = "ia32" ]; then | |
461 TARGET_ARCH=x86 | |
462 log "Auto-config: --arch=$TARGET_ARCH (equivalent to ia32)" | |
463 fi | |
464 fi | |
465 | |
466 # Detect the NDK system name, i.e. the name used to identify the host. | |
467 # out: NDK system name (e.g. 'linux' or 'darwin') | |
468 get_ndk_host_system () { | |
469 local HOST_OS | |
470 if [ -z "$NDK_HOST_SYSTEM" ]; then | |
471 HOST_OS=$(uname -s) | |
472 case $HOST_OS in | |
473 Linux) NDK_HOST_SYSTEM=linux;; | |
474 Darwin) NDK_HOST_SYSTEM=darwin;; | |
475 *) panic "You can't run this script on this system: $HOST_OS";; | |
476 esac | |
477 fi | |
478 echo "$NDK_HOST_SYSTEM" | |
479 } | |
480 | |
481 # Detect the NDK host architecture name. | |
482 # out: NDK arch name (e.g. 'x86' or 'x86_64') | |
483 get_ndk_host_arch () { | |
484 local HOST_ARCH HOST_OS | |
485 if [ -z "$NDK_HOST_ARCH" ]; then | |
486 HOST_OS=$(get_ndk_host_system) | |
487 HOST_ARCH=$(uname -p) | |
488 case $HOST_ARCH in | |
489 i?86) NDK_HOST_ARCH=x86;; | |
490 x86_64|amd64) NDK_HOST_ARCH=x86_64;; | |
491 *) panic "You can't run this script on this host architecture: $HOST_ARCH"
;; | |
492 esac | |
493 # Darwin trick: "uname -p" always returns i386 on 64-bit installations. | |
494 if [ "$HOST_OS" = darwin -a "$NDK_HOST_ARCH" = "x86" ]; then | |
495 # Use '/usr/bin/file', not just 'file' to avoid buggy MacPorts | |
496 # implementations of the tool. See http://b.android.com/53769 | |
497 HOST_64BITS=$(/usr/bin/file -L "$SHELL" | grep -e "x86[_-]64") | |
498 if [ "$HOST_64BITS" ]; then | |
499 NDK_HOST_ARCH=x86_64 | |
500 fi | |
501 fi | |
502 fi | |
503 echo "$NDK_HOST_ARCH" | |
504 } | |
505 | |
506 # Convert an NDK architecture name into a GNU configure triplet. | |
507 # $1: NDK architecture name (e.g. 'arm') | |
508 # Out: Android GNU configure triplet (e.g. 'arm-linux-androideabi') | |
509 get_arch_gnu_config () { | |
510 case $1 in | |
511 arm) | |
512 echo "arm-linux-androideabi" | |
513 ;; | |
514 arm64) | |
515 echo "aarch64-linux-android" | |
516 ;; | |
517 x86) | |
518 echo "i686-linux-android" | |
519 ;; | |
520 x86_64) | |
521 echo "x86_64-linux-android" | |
522 ;; | |
523 mips) | |
524 echo "mipsel-linux-android" | |
525 ;; | |
526 *) | |
527 echo "$ARCH-linux-android" | |
528 ;; | |
529 esac | |
530 } | |
531 | |
532 # Convert an NDK architecture name into a toolchain name prefix | |
533 # $1: NDK architecture name (e.g. 'arm') | |
534 # Out: NDK toolchain name prefix (e.g. 'arm-linux-androideabi') | |
535 get_arch_toolchain_prefix () { | |
536 # Return the configure triplet, except for x86! | |
537 if [ "$1" = "x86" ]; then | |
538 echo "$1" | |
539 else | |
540 get_arch_gnu_config $1 | |
541 fi | |
542 } | |
543 | |
544 # Find a NDK toolchain prebuilt file or sub-directory. | |
545 # This will probe the various arch-specific toolchain directories | |
546 # in the NDK for the needed file. | |
547 # $1: NDK install path | |
548 # $2: NDK architecture name | |
549 # $3: prebuilt sub-path to look for. | |
550 # Out: file path, or empty if none is found. | |
551 get_ndk_toolchain_prebuilt () { | |
552 local NDK_DIR="${1%/}" | |
553 local ARCH="$2" | |
554 local SUBPATH="$3" | |
555 local NAME="$(get_arch_toolchain_prefix $ARCH)" | |
556 local FILE TARGET | |
557 FILE=$NDK_DIR/toolchains/$NAME-4.9/prebuilt/$SUBPATH | |
558 if [ ! -f "$FILE" ]; then | |
559 FILE=$NDK_DIR/toolchains/$NAME-4.8/prebuilt/$SUBPATH | |
560 if [ ! -f "$FILE" ]; then | |
561 FILE= | |
562 fi | |
563 fi | |
564 echo "$FILE" | |
565 } | |
566 | |
567 # Find the path to an NDK's toolchain full prefix for a given architecture | |
568 # $1: NDK install path | |
569 # $2: NDK target architecture name | |
570 # Out: install path + binary prefix (e.g. | |
571 # ".../path/to/bin/arm-linux-androideabi-") | |
572 get_ndk_toolchain_fullprefix () { | |
573 local NDK_DIR="$1" | |
574 local ARCH="$2" | |
575 local TARGET NAME HOST_OS HOST_ARCH GCC CONFIG | |
576 | |
577 # NOTE: This will need to be updated if the NDK changes the names or moves | |
578 # the location of its prebuilt toolchains. | |
579 # | |
580 GCC= | |
581 HOST_OS=$(get_ndk_host_system) | |
582 HOST_ARCH=$(get_ndk_host_arch) | |
583 CONFIG=$(get_arch_gnu_config $ARCH) | |
584 GCC=$(get_ndk_toolchain_prebuilt \ | |
585 "$NDK_DIR" "$ARCH" "$HOST_OS-$HOST_ARCH/bin/$CONFIG-gcc") | |
586 if [ -z "$GCC" -a "$HOST_ARCH" = "x86_64" ]; then | |
587 GCC=$(get_ndk_toolchain_prebuilt \ | |
588 "$NDK_DIR" "$ARCH" "$HOST_OS-x86/bin/$CONFIG-gcc") | |
589 fi | |
590 if [ ! -f "$GCC" -a "$ARCH" = "x86" ]; then | |
591 # Special case, the x86 toolchain used to be incorrectly | |
592 # named i686-android-linux-gcc! | |
593 GCC=$(get_ndk_toolchain_prebuilt \ | |
594 "$NDK_DIR" "$ARCH" "$HOST_OS-x86/bin/i686-android-linux-gcc") | |
595 fi | |
596 if [ -z "$GCC" ]; then | |
597 panic "Cannot find Android NDK toolchain for '$ARCH' architecture. \ | |
598 Please verify your NDK installation!" | |
599 fi | |
600 echo "${GCC%%gcc}" | |
601 } | |
602 | |
603 # $1: NDK install path | |
604 # $2: target architecture. | |
605 get_ndk_gdbserver () { | |
606 local NDK_DIR="$1" | |
607 local ARCH=$2 | |
608 local BINARY | |
609 | |
610 # The location has moved after NDK r8 | |
611 BINARY=$NDK_DIR/prebuilt/android-$ARCH/gdbserver/gdbserver | |
612 if [ ! -f "$BINARY" ]; then | |
613 BINARY=$(get_ndk_toolchain_prebuilt "$NDK_DIR" "$ARCH" gdbserver) | |
614 fi | |
615 echo "$BINARY" | |
616 } | |
617 | |
618 # Check/probe the path to the Android toolchain installation. Always | |
619 # use the NDK versions of gdb and gdbserver. They must match to avoid | |
620 # issues when both binaries do not speak the same wire protocol. | |
621 # | |
622 if [ -z "$TOOLCHAIN" ]; then | |
623 ANDROID_TOOLCHAIN=$(get_ndk_toolchain_fullprefix \ | |
624 "$ANDROID_NDK_ROOT" "$TARGET_ARCH") | |
625 ANDROID_TOOLCHAIN=$(dirname "$ANDROID_TOOLCHAIN") | |
626 log "Auto-config: --toolchain=$ANDROID_TOOLCHAIN" | |
627 else | |
628 # Be flexible, allow one to specify either the install path or the bin | |
629 # sub-directory in --toolchain: | |
630 # | |
631 if [ -d "$TOOLCHAIN/bin" ]; then | |
632 TOOLCHAIN=$TOOLCHAIN/bin | |
633 fi | |
634 ANDROID_TOOLCHAIN=$TOOLCHAIN | |
635 fi | |
636 | |
637 # Cosmetic: Remove trailing directory separator. | |
638 ANDROID_TOOLCHAIN=${ANDROID_TOOLCHAIN%/} | |
639 | |
640 # Find host GDB client binary | |
641 if [ -z "$GDB" ]; then | |
642 GDB=$(which $ANDROID_TOOLCHAIN/*-$GDBEXEPOSTFIX 2>/dev/null | head -1) | |
643 if [ -z "$GDB" ]; then | |
644 panic "Can't find Android gdb client in your path, check your \ | |
645 --toolchain or --gdb path." | |
646 fi | |
647 log "Host gdb client: $GDB" | |
648 fi | |
649 | |
650 # Find gdbserver binary, we will later push it to /data/local/tmp | |
651 # This ensures that both gdbserver and $GDB talk the same binary protocol, | |
652 # otherwise weird problems will appear. | |
653 # | |
654 if [ -z "$GDBSERVER" ]; then | |
655 GDBSERVER=$(get_ndk_gdbserver "$ANDROID_NDK_ROOT" "$TARGET_ARCH") | |
656 if [ -z "$GDBSERVER" ]; then | |
657 panic "Can't find NDK gdbserver binary. use --gdbserver to specify \ | |
658 valid one!" | |
659 fi | |
660 log "Auto-config: --gdbserver=$GDBSERVER" | |
661 fi | |
662 | |
663 # A unique ID for this script's session. This needs to be the same in all | |
664 # sub-shell commands we're going to launch, so take the PID of the launcher | |
665 # process. | |
666 TMP_ID=$$ | |
667 | |
668 # Temporary directory, will get cleaned up on exit. | |
669 TMPDIR=/tmp/$USER-adb-gdb-tmp-$TMP_ID | |
670 mkdir -p "$TMPDIR" && rm -rf "$TMPDIR"/* | |
671 | |
672 GDBSERVER_PIDFILE="$TMPDIR"/gdbserver-$TMP_ID.pid | |
673 | |
674 # If --force is specified, try to kill any gdbserver process started by the | |
675 # same user on the device. Normally, these are killed automatically by the | |
676 # script on exit, but there are a few corner cases where this would still | |
677 # be needed. | |
678 if [ "$FORCE" ]; then | |
679 GDBSERVER_PIDS=$(adb_shell ps | awk '$9 ~ /gdbserver/ { print $2; }') | |
680 for GDB_PID in $GDBSERVER_PIDS; do | |
681 log "Killing previous gdbserver (PID=$GDB_PID)" | |
682 adb_shell kill -9 $GDB_PID | |
683 done | |
684 fi | |
685 | |
686 if [ "$START" ]; then | |
687 log "Starting $PROGRAM_NAME on device." | |
688 adb_shell am start -n $PACKAGE_NAME/$ACTIVITY 2>/dev/null | |
689 adb_shell ps | grep -q $PACKAGE_NAME | |
690 fail_panic "Could not start $PROGRAM_NAME on device. Are you sure the \ | |
691 package is installed?" | |
692 fi | |
693 | |
694 # Return the timestamp of a given time, as number of seconds since epoch. | |
695 # $1: file path | |
696 # Out: file timestamp | |
697 get_file_timestamp () { | |
698 stat -c %Y "$1" 2>/dev/null | |
699 } | |
700 | |
701 # Detect the build type and symbol directory. This is done by finding | |
702 # the most recent sub-directory containing debug shared libraries under | |
703 # $CHROMIUM_SRC/$CHROMIUM_OUT_DIR/ | |
704 # | |
705 # $1: $BUILDTYPE value, can be empty | |
706 # Out: nothing, but this sets SYMBOL_DIR | |
707 # | |
708 detect_symbol_dir () { | |
709 local SUBDIRS SUBDIR LIST DIR DIR_LIBS TSTAMP | |
710 # Note: Ninja places debug libraries under out/$BUILDTYPE/lib/, while | |
711 # Make places then under out/$BUILDTYPE/lib.target. | |
712 if [ "$1" ]; then | |
713 SUBDIRS="$1/lib $1/lib.target" | |
714 else | |
715 SUBDIRS="Release/lib Debug/lib Release/lib.target Debug/lib.target" | |
716 fi | |
717 LIST=$TMPDIR/scan-subdirs-$$.txt | |
718 printf "" > "$LIST" | |
719 for SUBDIR in $SUBDIRS; do | |
720 DIR=$CHROMIUM_SRC/$CHROMIUM_OUT_DIR/$SUBDIR | |
721 if [ -d "$DIR" ]; then | |
722 # Ignore build directories that don't contain symbol versions | |
723 # of the shared libraries. | |
724 DIR_LIBS=$(ls "$DIR"/lib*.so 2>/dev/null) | |
725 if [ -z "$DIR_LIBS" ]; then | |
726 echo "No shared libs: $DIR" | |
727 continue | |
728 fi | |
729 TSTAMP=$(get_file_timestamp "$DIR") | |
730 printf "%s %s\n" "$TSTAMP" "$SUBDIR" >> "$LIST" | |
731 fi | |
732 done | |
733 SUBDIR=$(cat $LIST | sort -r | head -1 | cut -d" " -f2) | |
734 rm -f "$LIST" | |
735 | |
736 if [ -z "$SUBDIR" ]; then | |
737 if [ -z "$1" ]; then | |
738 panic "Could not find any build directory under \ | |
739 $CHROMIUM_SRC/$CHROMIUM_OUT_DIR. Please build the program first!" | |
740 else | |
741 panic "Could not find any $1 directory under \ | |
742 $CHROMIUM_SRC/$CHROMIUM_OUT_DIR. Check your build type!" | |
743 fi | |
744 fi | |
745 | |
746 SYMBOL_DIR=$CHROMIUM_SRC/$CHROMIUM_OUT_DIR/$SUBDIR | |
747 log "Auto-config: --symbol-dir=$SYMBOL_DIR" | |
748 } | |
749 | |
750 if [ -z "$SYMBOL_DIR" ]; then | |
751 detect_symbol_dir "$BUILDTYPE" | |
752 fi | |
753 | |
754 # Allow several concurrent debugging sessions | |
755 TARGET_GDBSERVER=/data/data/$PACKAGE_NAME/gdbserver-adb-gdb-$TMP_ID | |
756 TMP_TARGET_GDBSERVER=/data/local/tmp/gdbserver-adb-gdb-$TMP_ID | |
757 | |
758 # Return the build fingerprint contained in a build.prop file. | |
759 # $1: path to build.prop file | |
760 get_build_fingerprint_from () { | |
761 cat "$1" | grep -e '^ro.build.fingerprint=' | cut -d= -f2 | |
762 } | |
763 | |
764 | |
765 ORG_PULL_LIBS_DIR=$PULL_LIBS_DIR | |
766 PULL_LIBS_DIR=${PULL_LIBS_DIR:-$DEFAULT_PULL_LIBS_DIR} | |
767 | |
768 HOST_FINGERPRINT= | |
769 DEVICE_FINGERPRINT=$(adb_shell getprop ro.build.fingerprint) | |
770 log "Device build fingerprint: $DEVICE_FINGERPRINT" | |
771 | |
772 # If --pull-libs-dir is not specified, and this is a platform build, look | |
773 # if we can use the symbolic libraries under $ANDROID_PRODUCT_OUT/symbols/ | |
774 # directly, if the build fingerprint matches the device. | |
775 if [ -z "$ORG_PULL_LIBS_DIR" -a \ | |
776 "$ANDROID_PRODUCT_OUT" -a \ | |
777 -f "$ANDROID_PRODUCT_OUT/system/build.prop" ]; then | |
778 ANDROID_FINGERPRINT=$(get_build_fingerprint_from \ | |
779 "$ANDROID_PRODUCT_OUT"/system/build.prop) | |
780 log "Android build fingerprint: $ANDROID_FINGERPRINT" | |
781 if [ "$ANDROID_FINGERPRINT" = "$DEVICE_FINGERPRINT" ]; then | |
782 log "Perfect match!" | |
783 PULL_LIBS_DIR=$ANDROID_PRODUCT_OUT/symbols | |
784 HOST_FINGERPRINT=$ANDROID_FINGERPRINT | |
785 if [ "$PULL_LIBS" ]; then | |
786 log "Ignoring --pull-libs since the device and platform build \ | |
787 fingerprints match." | |
788 NO_PULL_LIBS=true | |
789 fi | |
790 fi | |
791 fi | |
792 | |
793 # If neither --pull-libs an --no-pull-libs were specified, check the build | |
794 # fingerprints of the device, and the cached system libraries on the host. | |
795 # | |
796 if [ -z "$NO_PULL_LIBS" -a -z "$PULL_LIBS" ]; then | |
797 if [ ! -f "$PULL_LIBS_DIR/build.prop" ]; then | |
798 log "Auto-config: --pull-libs (no cached libraries)" | |
799 PULL_LIBS=true | |
800 else | |
801 HOST_FINGERPRINT=$(get_build_fingerprint_from "$PULL_LIBS_DIR/build.prop") | |
802 log "Host build fingerprint: $HOST_FINGERPRINT" | |
803 if [ "$HOST_FINGERPRINT" == "$DEVICE_FINGERPRINT" ]; then | |
804 log "Auto-config: --no-pull-libs (fingerprint match)" | |
805 NO_PULL_LIBS=true | |
806 else | |
807 log "Auto-config: --pull-libs (fingerprint mismatch)" | |
808 PULL_LIBS=true | |
809 fi | |
810 fi | |
811 fi | |
812 | |
813 # Extract the system libraries from the device if necessary. | |
814 if [ "$PULL_LIBS" -a -z "$NO_PULL_LIBS" ]; then | |
815 echo "Extracting system libraries into: $PULL_LIBS_DIR" | |
816 fi | |
817 | |
818 mkdir -p "$PULL_LIBS_DIR" | |
819 fail_panic "Can't create --libs-dir directory: $PULL_LIBS_DIR" | |
820 | |
821 # If requested, work for M-x gdb. The gdb indirections make it | |
822 # difficult to pass --annotate=3 to the gdb binary itself. | |
823 GDB_ARGS= | |
824 if [ "$ANNOTATE" ]; then | |
825 GDB_ARGS=$GDB_ARGS" --annotate=$ANNOTATE" | |
826 fi | |
827 | |
828 # Get the PID from the first argument or else find the PID of the | |
829 # browser process. | |
830 if [ -z "$PID" ]; then | |
831 PROCESSNAME=$PACKAGE_NAME | |
832 if [ "$SANDBOXED_INDEX" ]; then | |
833 PROCESSNAME=$PROCESSNAME:sandboxed_process$SANDBOXED_INDEX | |
834 elif [ "$SANDBOXED" ]; then | |
835 PROCESSNAME=$PROCESSNAME:sandboxed_process | |
836 PID=$(adb_shell ps | \ | |
837 awk '$9 ~ /^'$PROCESSNAME'/ { print $2; }' | head -1) | |
838 elif [ "$PRIVILEGED_INDEX" ]; then | |
839 PROCESSNAME=$PROCESSNAME:privileged_process$PRIVILEGED_INDEX | |
840 elif [ "$PRIVILEGED" ]; then | |
841 PROCESSNAME=$PROCESSNAME:privileged_process | |
842 PID=$(adb_shell ps | \ | |
843 awk '$9 ~ /^'$PROCESSNAME'/ { print $2; }' | head -1) | |
844 fi | |
845 if [ -z "$PID" ]; then | |
846 PID=$(adb_shell ps | \ | |
847 awk '$9 == "'$PROCESSNAME'" { print $2; }' | head -1) | |
848 fi | |
849 if [ -z "$PID" ]; then | |
850 if [ "$START" ]; then | |
851 panic "Can't find application process PID, did it crash?" | |
852 else | |
853 panic "Can't find application process PID, are you sure it is \ | |
854 running? Try using --start." | |
855 fi | |
856 fi | |
857 log "Found process PID: $PID" | |
858 elif [ "$SANDBOXED" ]; then | |
859 echo "WARNING: --sandboxed option ignored due to use of --pid." | |
860 elif [ "$PRIVILEGED" ]; then | |
861 echo "WARNING: --privileged option ignored due to use of --pid." | |
862 fi | |
863 | |
864 # Determine if 'adb shell' runs as root or not. | |
865 # If so, we can launch gdbserver directly, otherwise, we have to | |
866 # use run-as $PACKAGE_NAME ..., which requires the package to be debuggable. | |
867 # | |
868 if [ "$SU_PREFIX" ]; then | |
869 # Need to check that this works properly. | |
870 SU_PREFIX_TEST_LOG=$TMPDIR/su-prefix.log | |
871 adb_shell $SU_PREFIX \"echo "foo"\" > $SU_PREFIX_TEST_LOG 2>&1 | |
872 if [ $? != 0 -o "$(cat $SU_PREFIX_TEST_LOG)" != "foo" ]; then | |
873 echo "ERROR: Cannot use '$SU_PREFIX' as a valid su prefix:" | |
874 echo "$ adb shell $SU_PREFIX \"echo foo\"" | |
875 cat $SU_PREFIX_TEST_LOG | |
876 exit 1 | |
877 fi | |
878 COMMAND_PREFIX="$SU_PREFIX \"" | |
879 COMMAND_SUFFIX="\"" | |
880 else | |
881 SHELL_UID=$(adb shell cat /proc/self/status | \ | |
882 awk '$1 == "Uid:" { print $2; }') | |
883 log "Shell UID: $SHELL_UID" | |
884 if [ "$SHELL_UID" != 0 -o -n "$NO_ROOT" ]; then | |
885 COMMAND_PREFIX="run-as $PACKAGE_NAME" | |
886 COMMAND_SUFFIX= | |
887 else | |
888 COMMAND_PREFIX= | |
889 COMMAND_SUFFIX= | |
890 fi | |
891 fi | |
892 log "Command prefix: '$COMMAND_PREFIX'" | |
893 log "Command suffix: '$COMMAND_SUFFIX'" | |
894 | |
895 # Pull device's system libraries that are mapped by our process. | |
896 # Pulling all system libraries is too long, so determine which ones | |
897 # we need by looking at /proc/$PID/maps instead | |
898 if [ "$PULL_LIBS" -a -z "$NO_PULL_LIBS" ]; then | |
899 echo "Extracting system libraries into: $PULL_LIBS_DIR" | |
900 rm -f $PULL_LIBS_DIR/build.prop | |
901 MAPPINGS=$(adb_shell $COMMAND_PREFIX cat /proc/$PID/maps $COMMAND_SUFFIX) | |
902 if [ $? != 0 ]; then | |
903 echo "ERROR: Could not list process's memory mappings." | |
904 if [ "$SU_PREFIX" ]; then | |
905 panic "Are you sure your --su-prefix is correct?" | |
906 else | |
907 panic "Use --su-prefix if the application is not debuggable." | |
908 fi | |
909 fi | |
910 SYSTEM_LIBS=$(echo "$MAPPINGS" | \ | |
911 awk '$6 ~ /\/system\/.*\.so$/ { print $6; }' | sort -u) | |
912 for SYSLIB in /system/bin/linker $SYSTEM_LIBS; do | |
913 echo "Pulling from device: $SYSLIB" | |
914 DST_FILE=$PULL_LIBS_DIR$SYSLIB | |
915 DST_DIR=$(dirname "$DST_FILE") | |
916 mkdir -p "$DST_DIR" && adb pull $SYSLIB "$DST_FILE" 2>/dev/null | |
917 fail_panic "Could not pull $SYSLIB from device !?" | |
918 done | |
919 echo "Pulling device build.prop" | |
920 adb pull /system/build.prop $PULL_LIBS_DIR/build.prop | |
921 fail_panic "Could not pull device build.prop !?" | |
922 fi | |
923 | |
924 # Find all the sub-directories of $PULL_LIBS_DIR, up to depth 4 | |
925 # so we can add them to solib-search-path later. | |
926 SOLIB_DIRS=$(find $PULL_LIBS_DIR -mindepth 1 -maxdepth 4 -type d | \ | |
927 grep -v "^$" | tr '\n' ':') | |
928 | |
929 # This is a re-implementation of gdbclient, where we use compatible | |
930 # versions of gdbserver and $GDBNAME to ensure that everything works | |
931 # properly. | |
932 # | |
933 | |
934 # Push gdbserver to the device | |
935 log "Pushing gdbserver $GDBSERVER to $TARGET_GDBSERVER" | |
936 adb push $GDBSERVER $TMP_TARGET_GDBSERVER &>/dev/null | |
937 adb shell $COMMAND_PREFIX cp $TMP_TARGET_GDBSERVER $TARGET_GDBSERVER | |
938 adb shell rm $TMP_TARGET_GDBSERVER | |
939 fail_panic "Could not copy gdbserver to the device!" | |
940 | |
941 if [ -z "$PORT" ]; then | |
942 PORT=5039 | |
943 fi | |
944 HOST_PORT=$PORT | |
945 TARGET_PORT=$PORT | |
946 | |
947 # Select correct app_process for architecture. | |
948 case $TARGET_ARCH in | |
949 arm|x86|mips) GDBEXEC=app_process;; | |
950 arm64|x86_64) GDBEXEC=app_process64;; | |
951 *) fail_panic "Unknown app_process for architecture!";; | |
952 esac | |
953 | |
954 # Detect AddressSanitizer setup on the device. In that case app_process is a | |
955 # script, and the real executable is app_process.real. | |
956 GDBEXEC_ASAN=app_process.real | |
957 adb_shell ls /system/bin/$GDBEXEC_ASAN | |
958 if [ $? == 0 ]; then | |
959 GDBEXEC=$GDBEXEC_ASAN | |
960 fi | |
961 | |
962 # Pull the app_process binary from the device. | |
963 log "Pulling $GDBEXEC from device" | |
964 adb pull /system/bin/$GDBEXEC "$TMPDIR"/$GDBEXEC &>/dev/null | |
965 fail_panic "Could not retrieve $GDBEXEC from the device!" | |
966 | |
967 # Setup network redirection | |
968 log "Setting network redirection (host:$HOST_PORT -> device:$TARGET_PORT)" | |
969 adb forward tcp:$HOST_PORT tcp:$TARGET_PORT | |
970 fail_panic "Could not setup network redirection from \ | |
971 host:localhost:$HOST_PORT to device:localhost:$TARGET_PORT!" | |
972 | |
973 # Start gdbserver in the background | |
974 # Note that using run-as requires the package to be debuggable. | |
975 # | |
976 # If not, this will fail horribly. The alternative is to run the | |
977 # program as root, which requires of course root privileges. | |
978 # Maybe we should add a --root option to enable this? | |
979 # | |
980 log "Starting gdbserver in the background:" | |
981 GDBSERVER_LOG=$TMPDIR/gdbserver-$TMP_ID.log | |
982 log "adb shell $COMMAND_PREFIX $TARGET_GDBSERVER :$TARGET_PORT \ | |
983 --attach $PID $COMMAND_SUFFIX" | |
984 ("$ADB" shell $COMMAND_PREFIX $TARGET_GDBSERVER :$TARGET_PORT \ | |
985 --attach $PID $COMMAND_SUFFIX > $GDBSERVER_LOG 2>&1) & | |
986 GDBSERVER_PID=$! | |
987 echo "$GDBSERVER_PID" > $GDBSERVER_PIDFILE | |
988 log "background job pid: $GDBSERVER_PID" | |
989 | |
990 # Check that it is still running after a few seconds. If not, this means we | |
991 # could not properly attach to it | |
992 sleep 2 | |
993 log "Job control: $(jobs -l)" | |
994 STATE=$(jobs -l | awk '$2 == "'$GDBSERVER_PID'" { print $3; }') | |
995 if [ "$STATE" != "Running" ]; then | |
996 echo "ERROR: GDBServer could not attach to PID $PID!" | |
997 if [ $(adb_shell su -c getenforce) != "Permissive" ]; then | |
998 echo "Device mode is Enforcing. Changing Device mode to Permissive " | |
999 $(adb_shell su -c setenforce 0) | |
1000 if [ $(adb_shell su -c getenforce) != "Permissive" ]; then | |
1001 echo "ERROR: Failed to Change Device mode to Permissive" | |
1002 echo "Failure log (use --verbose for more information):" | |
1003 cat $GDBSERVER_LOG | |
1004 exit 1 | |
1005 fi | |
1006 else | |
1007 echo "Failure log (use --verbose for more information):" | |
1008 cat $GDBSERVER_LOG | |
1009 exit 1 | |
1010 fi | |
1011 fi | |
1012 | |
1013 # Generate a file containing useful GDB initialization commands | |
1014 readonly COMMANDS=$TMPDIR/gdb.init | |
1015 log "Generating GDB initialization commands file: $COMMANDS" | |
1016 echo -n "" > $COMMANDS | |
1017 echo "set print pretty 1" >> $COMMANDS | |
1018 echo "python" >> $COMMANDS | |
1019 echo "import sys" >> $COMMANDS | |
1020 echo "sys.path.insert(0, '$CHROMIUM_SRC/tools/gdb/')" >> $COMMANDS | |
1021 echo "try:" >> $COMMANDS | |
1022 echo " import gdb_chrome" >> $COMMANDS | |
1023 echo "finally:" >> $COMMANDS | |
1024 echo " sys.path.pop(0)" >> $COMMANDS | |
1025 echo "end" >> $COMMANDS | |
1026 echo "file $TMPDIR/$GDBEXEC" >> $COMMANDS | |
1027 echo "directory $CHROMIUM_SRC" >> $COMMANDS | |
1028 echo "set solib-absolute-prefix $PULL_LIBS_DIR" >> $COMMANDS | |
1029 echo "set solib-search-path $SOLIB_DIRS:$PULL_LIBS_DIR:$SYMBOL_DIR" \ | |
1030 >> $COMMANDS | |
1031 echo "echo Attaching and reading symbols, this may take a while.." \ | |
1032 >> $COMMANDS | |
1033 echo "target remote :$HOST_PORT" >> $COMMANDS | |
1034 | |
1035 if [ "$GDBINIT" ]; then | |
1036 cat "$GDBINIT" >> $COMMANDS | |
1037 fi | |
1038 | |
1039 if [ "$VERBOSE" -gt 0 ]; then | |
1040 echo "### START $COMMANDS" | |
1041 cat $COMMANDS | |
1042 echo "### END $COMMANDS" | |
1043 fi | |
1044 | |
1045 log "Launching gdb client: $GDB $GDB_ARGS -x $COMMANDS" | |
1046 $GDB $GDB_ARGS -x $COMMANDS && | |
1047 rm -f "$GDBSERVER_PIDFILE" | |
OLD | NEW |