| Index: build/android/adb_gdb
|
| diff --git a/build/android/adb_gdb b/build/android/adb_gdb
|
| new file mode 100755
|
| index 0000000000000000000000000000000000000000..65ec7b20b87df53f1de71e9ac4d31e3cad6c1ee4
|
| --- /dev/null
|
| +++ b/build/android/adb_gdb
|
| @@ -0,0 +1,1047 @@
|
| +#!/bin/bash
|
| +#
|
| +# Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
| +# Use of this source code is governed by a BSD-style license that can be
|
| +# found in the LICENSE file.
|
| +#
|
| +
|
| +# A generic script used to attach to a running Chromium process and
|
| +# debug it. Most users should not use this directly, but one of the
|
| +# wrapper scripts like adb_gdb_content_shell
|
| +#
|
| +# Use --help to print full usage instructions.
|
| +#
|
| +
|
| +PROGNAME=$(basename "$0")
|
| +PROGDIR=$(dirname "$0")
|
| +
|
| +# Location of Chromium-top-level sources.
|
| +CHROMIUM_SRC=$(cd "$PROGDIR"/../.. >/dev/null && pwd 2>/dev/null)
|
| +
|
| +# Location of Chromium out/ directory.
|
| +if [ -z "$CHROMIUM_OUT_DIR" ]; then
|
| + CHROMIUM_OUT_DIR=out
|
| +fi
|
| +
|
| +TMPDIR=
|
| +GDBSERVER_PIDFILE=
|
| +TARGET_GDBSERVER=
|
| +COMMAND_PREFIX=
|
| +
|
| +clean_exit () {
|
| + if [ "$TMPDIR" ]; then
|
| + GDBSERVER_PID=$(cat $GDBSERVER_PIDFILE 2>/dev/null)
|
| + if [ "$GDBSERVER_PID" ]; then
|
| + log "Killing background gdbserver process: $GDBSERVER_PID"
|
| + kill -9 $GDBSERVER_PID >/dev/null 2>&1
|
| + fi
|
| + if [ "$TARGET_GDBSERVER" ]; then
|
| + log "Removing target gdbserver binary: $TARGET_GDBSERVER."
|
| + "$ADB" shell "$COMMAND_PREFIX" rm "$TARGET_GDBSERVER" >/dev/null 2>&1
|
| + fi
|
| + log "Cleaning up: $TMPDIR"
|
| + rm -rf "$TMPDIR"
|
| + fi
|
| + trap "" EXIT
|
| + exit $1
|
| +}
|
| +
|
| +# Ensure clean exit on Ctrl-C or normal exit.
|
| +trap "clean_exit 1" INT HUP QUIT TERM
|
| +trap "clean_exit \$?" EXIT
|
| +
|
| +panic () {
|
| + echo "ERROR: $@" >&2
|
| + exit 1
|
| +}
|
| +
|
| +fail_panic () {
|
| + if [ $? != 0 ]; then panic "$@"; fi
|
| +}
|
| +
|
| +log () {
|
| + if [ "$VERBOSE" -gt 0 ]; then
|
| + echo "$@"
|
| + fi
|
| +}
|
| +
|
| +DEFAULT_PULL_LIBS_DIR=/tmp/$USER-adb-gdb-libs
|
| +
|
| +# NOTE: Allow wrapper scripts to set various default through ADB_GDB_XXX
|
| +# environment variables. This is only for cosmetic reasons, i.e. to
|
| +# display proper
|
| +
|
| +# Allow wrapper scripts to set the default activity through
|
| +# the ADB_GDB_ACTIVITY variable. Users are still able to change the
|
| +# final activity name through --activity=<name> option.
|
| +#
|
| +# This is only for cosmetic reasons, i.e. to display the proper default
|
| +# in the --help output.
|
| +#
|
| +DEFAULT_ACTIVITY=${ADB_GDB_ACTIVITY:-".Main"}
|
| +
|
| +# Allow wrapper scripts to set the program name through ADB_GDB_PROGNAME
|
| +PROGNAME=${ADB_GDB_PROGNAME:-$(basename "$0")}
|
| +
|
| +ACTIVITY=$DEFAULT_ACTIVITY
|
| +ADB=
|
| +ANNOTATE=
|
| +# Note: Ignore BUILDTYPE variable, because the Ninja build doesn't use it.
|
| +BUILDTYPE=
|
| +FORCE=
|
| +GDBEXEPOSTFIX=gdb
|
| +GDBINIT=
|
| +GDBSERVER=
|
| +HELP=
|
| +NDK_DIR=
|
| +NO_PULL_LIBS=
|
| +PACKAGE_NAME=
|
| +PID=
|
| +PORT=
|
| +PRIVILEGED=
|
| +PRIVILEGED_INDEX=
|
| +PROGRAM_NAME="activity"
|
| +PULL_LIBS=
|
| +PULL_LIBS_DIR=
|
| +SANDBOXED=
|
| +SANDBOXED_INDEX=
|
| +START=
|
| +SU_PREFIX=
|
| +SYMBOL_DIR=
|
| +TARGET_ARCH=
|
| +TOOLCHAIN=
|
| +VERBOSE=0
|
| +
|
| +for opt; do
|
| + optarg=$(expr "x$opt" : 'x[^=]*=\(.*\)')
|
| + case $opt in
|
| + --adb=*)
|
| + ADB=$optarg
|
| + ;;
|
| + --activity=*)
|
| + ACTIVITY=$optarg
|
| + ;;
|
| + --annotate=3)
|
| + ANNOTATE=$optarg
|
| + ;;
|
| + --force)
|
| + FORCE=true
|
| + ;;
|
| + --gdbserver=*)
|
| + GDBSERVER=$optarg
|
| + ;;
|
| + --gdb=*)
|
| + GDB=$optarg
|
| + ;;
|
| + --help|-h|-?)
|
| + HELP=true
|
| + ;;
|
| + --ndk-dir=*)
|
| + NDK_DIR=$optarg
|
| + ;;
|
| + --no-pull-libs)
|
| + NO_PULL_LIBS=true
|
| + ;;
|
| + --package-name=*)
|
| + PACKAGE_NAME=$optarg
|
| + ;;
|
| + --pid=*)
|
| + PID=$optarg
|
| + ;;
|
| + --port=*)
|
| + PORT=$optarg
|
| + ;;
|
| + --privileged)
|
| + PRIVILEGED=true
|
| + ;;
|
| + --privileged=*)
|
| + PRIVILEGED=true
|
| + PRIVILEGED_INDEX=$optarg
|
| + ;;
|
| + --program-name=*)
|
| + PROGRAM_NAME=$optarg
|
| + ;;
|
| + --pull-libs)
|
| + PULL_LIBS=true
|
| + ;;
|
| + --pull-libs-dir=*)
|
| + PULL_LIBS_DIR=$optarg
|
| + ;;
|
| + --sandboxed)
|
| + SANDBOXED=true
|
| + ;;
|
| + --sandboxed=*)
|
| + SANDBOXED=true
|
| + SANDBOXED_INDEX=$optarg
|
| + ;;
|
| + --script=*)
|
| + GDBINIT=$optarg
|
| + ;;
|
| + --start)
|
| + START=true
|
| + ;;
|
| + --su-prefix=*)
|
| + SU_PREFIX=$optarg
|
| + ;;
|
| + --symbol-dir=*)
|
| + SYMBOL_DIR=$optarg
|
| + ;;
|
| + --out-dir=*)
|
| + CHROMIUM_OUT_DIR=$optarg
|
| + ;;
|
| + --target-arch=*)
|
| + TARGET_ARCH=$optarg
|
| + ;;
|
| + --toolchain=*)
|
| + TOOLCHAIN=$optarg
|
| + ;;
|
| + --ui)
|
| + GDBEXEPOSTFIX=gdbtui
|
| + ;;
|
| + --verbose)
|
| + VERBOSE=$(( $VERBOSE + 1 ))
|
| + ;;
|
| + --debug)
|
| + BUILDTYPE=Debug
|
| + ;;
|
| + --release)
|
| + BUILDTYPE=Release
|
| + ;;
|
| + -*)
|
| + panic "Unknown option $OPT, see --help." >&2
|
| + ;;
|
| + *)
|
| + if [ "$PACKAGE_NAME" ]; then
|
| + panic "You can only provide a single package name as argument!\
|
| + See --help."
|
| + fi
|
| + PACKAGE_NAME=$opt
|
| + ;;
|
| + esac
|
| +done
|
| +
|
| +print_help_options () {
|
| + cat <<EOF
|
| +EOF
|
| +}
|
| +
|
| +if [ "$HELP" ]; then
|
| + if [ "$ADB_GDB_PROGNAME" ]; then
|
| + # Assume wrapper scripts all provide a default package name.
|
| + cat <<EOF
|
| +Usage: $PROGNAME [options]
|
| +
|
| +Attach gdb to a running Android $PROGRAM_NAME process.
|
| +EOF
|
| + else
|
| + # Assume this is a direct call to adb_gdb
|
| + cat <<EOF
|
| +Usage: $PROGNAME [options] [<package-name>]
|
| +
|
| +Attach gdb to a running Android $PROGRAM_NAME process.
|
| +
|
| +If provided, <package-name> must be the name of the Android application's
|
| +package name to be debugged. You can also use --package-name=<name> to
|
| +specify it.
|
| +EOF
|
| + fi
|
| +
|
| + cat <<EOF
|
| +
|
| +This script is used to debug a running $PROGRAM_NAME process.
|
| +This can be a regular Android application process, sandboxed (if you use the
|
| +--sandboxed or --sandboxed=<num> option) or a privileged (--privileged or
|
| +--privileged=<num>) service.
|
| +
|
| +This script needs several things to work properly. It will try to pick
|
| +them up automatically for you though:
|
| +
|
| + - target gdbserver binary
|
| + - host gdb client (e.g. arm-linux-androideabi-gdb)
|
| + - directory with symbolic version of $PROGRAM_NAME's shared libraries.
|
| +
|
| +You can also use --ndk-dir=<path> to specify an alternative NDK installation
|
| +directory.
|
| +
|
| +The script tries to find the most recent version of the debug version of
|
| +shared libraries under one of the following directories:
|
| +
|
| + \$CHROMIUM_SRC/<out>/Release/lib/ (used by Ninja builds)
|
| + \$CHROMIUM_SRC/<out>/Debug/lib/ (used by Ninja builds)
|
| + \$CHROMIUM_SRC/<out>/Release/lib.target/ (used by Make builds)
|
| + \$CHROMIUM_SRC/<out>/Debug/lib.target/ (used by Make builds)
|
| +
|
| +Where <out> is 'out' by default, unless the --out=<name> option is used or
|
| +the CHROMIUM_OUT_DIR environment variable is defined.
|
| +
|
| +You can restrict this search by using --release or --debug to specify the
|
| +build type, or simply use --symbol-dir=<path> to specify the file manually.
|
| +
|
| +The script tries to extract the target architecture from your target device,
|
| +but if this fails, will default to 'arm'. Use --target-arch=<name> to force
|
| +its value.
|
| +
|
| +Otherwise, the script will complain, but you can use the --gdbserver,
|
| +--gdb and --symbol-lib options to specify everything manually.
|
| +
|
| +An alternative to --gdb=<file> is to use --toollchain=<path> to specify
|
| +the path to the host target-specific cross-toolchain.
|
| +
|
| +You will also need the 'adb' tool in your path. Otherwise, use the --adb
|
| +option. The script will complain if there is more than one device connected
|
| +and ANDROID_SERIAL is not defined.
|
| +
|
| +The first time you use it on a device, the script will pull many system
|
| +libraries required by the process into a temporary directory. This
|
| +is done to strongly improve the debugging experience, like allowing
|
| +readable thread stacks and more. The libraries are copied to the following
|
| +directory by default:
|
| +
|
| + $DEFAULT_PULL_LIBS_DIR/
|
| +
|
| +But you can use the --pull-libs-dir=<path> option to specify an
|
| +alternative. The script can detect when you change the connected device,
|
| +and will re-pull the libraries only in this case. You can however force it
|
| +with the --pull-libs option.
|
| +
|
| +Any local .gdbinit script will be ignored, but it is possible to pass a
|
| +gdb command script with the --script=<file> option. Note that its commands
|
| +will be passed to gdb after the remote connection and library symbol
|
| +loading have completed.
|
| +
|
| +Valid options:
|
| + --help|-h|-? Print this message.
|
| + --verbose Increase verbosity.
|
| +
|
| + --sandboxed Debug first sandboxed process we find.
|
| + --sandboxed=<num> Debug specific sandboxed process.
|
| + --symbol-dir=<path> Specify directory with symbol shared libraries.
|
| + --out-dir=<path> Specify the out directory.
|
| + --package-name=<name> Specify package name (alternative to 1st argument).
|
| + --privileged Debug first privileged process we find.
|
| + --privileged=<num> Debug specific privileged process.
|
| + --program-name=<name> Specify program name (cosmetic only).
|
| + --pid=<pid> Specify application process pid.
|
| + --force Kill any previous debugging session, if any.
|
| + --start Start package's activity on device.
|
| + --ui Use gdbtui instead of gdb
|
| + --activity=<name> Activity name for --start [$DEFAULT_ACTIVITY].
|
| + --annotate=<num> Enable gdb annotation.
|
| + --script=<file> Specify extra GDB init script.
|
| +
|
| + --gdbserver=<file> Specify target gdbserver binary.
|
| + --gdb=<file> Specify host gdb client binary.
|
| + --target-arch=<name> Specify NDK target arch.
|
| + --adb=<file> Specify host ADB binary.
|
| + --port=<port> Specify the tcp port to use.
|
| +
|
| + --su-prefix=<prefix> Prepend <prefix> to 'adb shell' commands that are
|
| + run by this script. This can be useful to use
|
| + the 'su' program on rooted production devices.
|
| + e.g. --su-prefix="su -c"
|
| +
|
| + --pull-libs Force system libraries extraction.
|
| + --no-pull-libs Do not extract any system library.
|
| + --libs-dir=<path> Specify system libraries extraction directory.
|
| +
|
| + --debug Use libraries under out/Debug.
|
| + --release Use libraries under out/Release.
|
| +
|
| +EOF
|
| + exit 0
|
| +fi
|
| +
|
| +if [ -z "$PACKAGE_NAME" ]; then
|
| + panic "Please specify a package name on the command line. See --help."
|
| +fi
|
| +
|
| +if [ -z "$NDK_DIR" ]; then
|
| + ANDROID_NDK_ROOT=$(PYTHONPATH=$CHROMIUM_SRC/build/android python -c \
|
| +'from pylib.constants import ANDROID_NDK_ROOT; print ANDROID_NDK_ROOT,')
|
| +else
|
| + if [ ! -d "$NDK_DIR" ]; then
|
| + panic "Invalid directory: $NDK_DIR"
|
| + fi
|
| + if [ ! -f "$NDK_DIR/ndk-build" ]; then
|
| + panic "Not a valid NDK directory: $NDK_DIR"
|
| + fi
|
| + ANDROID_NDK_ROOT=$NDK_DIR
|
| +fi
|
| +
|
| +if [ "$GDBINIT" -a ! -f "$GDBINIT" ]; then
|
| + panic "Unknown --script file: $GDBINIT"
|
| +fi
|
| +
|
| +# Check that ADB is in our path
|
| +if [ -z "$ADB" ]; then
|
| + ADB=$(which adb 2>/dev/null)
|
| + if [ -z "$ADB" ]; then
|
| + panic "Can't find 'adb' tool in your path. Install it or use \
|
| +--adb=<file>"
|
| + fi
|
| + log "Auto-config: --adb=$ADB"
|
| +fi
|
| +
|
| +# Check that it works minimally
|
| +ADB_VERSION=$($ADB version 2>/dev/null)
|
| +echo "$ADB_VERSION" | fgrep -q -e "Android Debug Bridge"
|
| +if [ $? != 0 ]; then
|
| + panic "Your 'adb' tool seems invalid, use --adb=<file> to specify a \
|
| +different one: $ADB"
|
| +fi
|
| +
|
| +# If there are more than one device connected, and ANDROID_SERIAL is not
|
| +# defined, print an error message.
|
| +NUM_DEVICES_PLUS2=$($ADB devices 2>/dev/null | wc -l)
|
| +if [ "$NUM_DEVICES_PLUS2" -lt 3 -a -z "$ANDROID_SERIAL" ]; then
|
| + echo "ERROR: There is more than one Android device connected to ADB."
|
| + echo "Please define ANDROID_SERIAL to specify which one to use."
|
| + exit 1
|
| +fi
|
| +
|
| +# Run a command through adb shell, strip the extra \r from the output
|
| +# and return the correct status code to detect failures. This assumes
|
| +# that the adb shell command prints a final \n to stdout.
|
| +# $1+: command to run
|
| +# Out: command's stdout
|
| +# Return: command's status
|
| +# Note: the command's stderr is lost
|
| +adb_shell () {
|
| + local TMPOUT="$(mktemp)"
|
| + local LASTLINE RET
|
| + local ADB=${ADB:-adb}
|
| +
|
| + # The weird sed rule is to strip the final \r on each output line
|
| + # Since 'adb shell' never returns the command's proper exit/status code,
|
| + # we force it to print it as '%%<status>' in the temporary output file,
|
| + # which we will later strip from it.
|
| + $ADB shell $@ ";" echo "%%\$?" 2>/dev/null | \
|
| + sed -e 's![[:cntrl:]]!!g' > $TMPOUT
|
| + # Get last line in log, which contains the exit code from the command
|
| + LASTLINE=$(sed -e '$!d' $TMPOUT)
|
| + # Extract the status code from the end of the line, which must
|
| + # be '%%<code>'.
|
| + RET=$(echo "$LASTLINE" | \
|
| + awk '{ if (match($0, "%%[0-9]+$")) { print substr($0,RSTART+2); } }')
|
| + # Remove the status code from the last line. Note that this may result
|
| + # in an empty line.
|
| + LASTLINE=$(echo "$LASTLINE" | \
|
| + awk '{ if (match($0, "%%[0-9]+$")) { print substr($0,1,RSTART-1); } }')
|
| + # The output itself: all lines except the status code.
|
| + sed -e '$d' $TMPOUT && printf "%s" "$LASTLINE"
|
| + # Remove temp file.
|
| + rm -f $TMPOUT
|
| + # Exit with the appropriate status.
|
| + return $RET
|
| +}
|
| +
|
| +# Find the target architecture from the target device.
|
| +# This returns an NDK-compatible architecture name.
|
| +# out: NDK Architecture name, or empty string.
|
| +get_gyp_target_arch () {
|
| + local ARCH=$(adb_shell getprop ro.product.cpu.abi)
|
| + case $ARCH in
|
| + mips|x86|x86_64) echo "$ARCH";;
|
| + arm64*) echo "arm64";;
|
| + arm*) echo "arm";;
|
| + *) echo "";
|
| + esac
|
| +}
|
| +
|
| +if [ -z "$TARGET_ARCH" ]; then
|
| + TARGET_ARCH=$(get_gyp_target_arch)
|
| + if [ -z "$TARGET_ARCH" ]; then
|
| + TARGET_ARCH=arm
|
| + fi
|
| +else
|
| + # Nit: accept Chromium's 'ia32' as a valid target architecture. This
|
| + # script prefers the NDK 'x86' name instead because it uses it to find
|
| + # NDK-specific files (host gdb) with it.
|
| + if [ "$TARGET_ARCH" = "ia32" ]; then
|
| + TARGET_ARCH=x86
|
| + log "Auto-config: --arch=$TARGET_ARCH (equivalent to ia32)"
|
| + fi
|
| +fi
|
| +
|
| +# Detect the NDK system name, i.e. the name used to identify the host.
|
| +# out: NDK system name (e.g. 'linux' or 'darwin')
|
| +get_ndk_host_system () {
|
| + local HOST_OS
|
| + if [ -z "$NDK_HOST_SYSTEM" ]; then
|
| + HOST_OS=$(uname -s)
|
| + case $HOST_OS in
|
| + Linux) NDK_HOST_SYSTEM=linux;;
|
| + Darwin) NDK_HOST_SYSTEM=darwin;;
|
| + *) panic "You can't run this script on this system: $HOST_OS";;
|
| + esac
|
| + fi
|
| + echo "$NDK_HOST_SYSTEM"
|
| +}
|
| +
|
| +# Detect the NDK host architecture name.
|
| +# out: NDK arch name (e.g. 'x86' or 'x86_64')
|
| +get_ndk_host_arch () {
|
| + local HOST_ARCH HOST_OS
|
| + if [ -z "$NDK_HOST_ARCH" ]; then
|
| + HOST_OS=$(get_ndk_host_system)
|
| + HOST_ARCH=$(uname -p)
|
| + case $HOST_ARCH in
|
| + i?86) NDK_HOST_ARCH=x86;;
|
| + x86_64|amd64) NDK_HOST_ARCH=x86_64;;
|
| + *) panic "You can't run this script on this host architecture: $HOST_ARCH";;
|
| + esac
|
| + # Darwin trick: "uname -p" always returns i386 on 64-bit installations.
|
| + if [ "$HOST_OS" = darwin -a "$NDK_HOST_ARCH" = "x86" ]; then
|
| + # Use '/usr/bin/file', not just 'file' to avoid buggy MacPorts
|
| + # implementations of the tool. See http://b.android.com/53769
|
| + HOST_64BITS=$(/usr/bin/file -L "$SHELL" | grep -e "x86[_-]64")
|
| + if [ "$HOST_64BITS" ]; then
|
| + NDK_HOST_ARCH=x86_64
|
| + fi
|
| + fi
|
| + fi
|
| + echo "$NDK_HOST_ARCH"
|
| +}
|
| +
|
| +# Convert an NDK architecture name into a GNU configure triplet.
|
| +# $1: NDK architecture name (e.g. 'arm')
|
| +# Out: Android GNU configure triplet (e.g. 'arm-linux-androideabi')
|
| +get_arch_gnu_config () {
|
| + case $1 in
|
| + arm)
|
| + echo "arm-linux-androideabi"
|
| + ;;
|
| + arm64)
|
| + echo "aarch64-linux-android"
|
| + ;;
|
| + x86)
|
| + echo "i686-linux-android"
|
| + ;;
|
| + x86_64)
|
| + echo "x86_64-linux-android"
|
| + ;;
|
| + mips)
|
| + echo "mipsel-linux-android"
|
| + ;;
|
| + *)
|
| + echo "$ARCH-linux-android"
|
| + ;;
|
| + esac
|
| +}
|
| +
|
| +# Convert an NDK architecture name into a toolchain name prefix
|
| +# $1: NDK architecture name (e.g. 'arm')
|
| +# Out: NDK toolchain name prefix (e.g. 'arm-linux-androideabi')
|
| +get_arch_toolchain_prefix () {
|
| + # Return the configure triplet, except for x86!
|
| + if [ "$1" = "x86" ]; then
|
| + echo "$1"
|
| + else
|
| + get_arch_gnu_config $1
|
| + fi
|
| +}
|
| +
|
| +# Find a NDK toolchain prebuilt file or sub-directory.
|
| +# This will probe the various arch-specific toolchain directories
|
| +# in the NDK for the needed file.
|
| +# $1: NDK install path
|
| +# $2: NDK architecture name
|
| +# $3: prebuilt sub-path to look for.
|
| +# Out: file path, or empty if none is found.
|
| +get_ndk_toolchain_prebuilt () {
|
| + local NDK_DIR="${1%/}"
|
| + local ARCH="$2"
|
| + local SUBPATH="$3"
|
| + local NAME="$(get_arch_toolchain_prefix $ARCH)"
|
| + local FILE TARGET
|
| + FILE=$NDK_DIR/toolchains/$NAME-4.9/prebuilt/$SUBPATH
|
| + if [ ! -f "$FILE" ]; then
|
| + FILE=$NDK_DIR/toolchains/$NAME-4.8/prebuilt/$SUBPATH
|
| + if [ ! -f "$FILE" ]; then
|
| + FILE=
|
| + fi
|
| + fi
|
| + echo "$FILE"
|
| +}
|
| +
|
| +# Find the path to an NDK's toolchain full prefix for a given architecture
|
| +# $1: NDK install path
|
| +# $2: NDK target architecture name
|
| +# Out: install path + binary prefix (e.g.
|
| +# ".../path/to/bin/arm-linux-androideabi-")
|
| +get_ndk_toolchain_fullprefix () {
|
| + local NDK_DIR="$1"
|
| + local ARCH="$2"
|
| + local TARGET NAME HOST_OS HOST_ARCH GCC CONFIG
|
| +
|
| + # NOTE: This will need to be updated if the NDK changes the names or moves
|
| + # the location of its prebuilt toolchains.
|
| + #
|
| + GCC=
|
| + HOST_OS=$(get_ndk_host_system)
|
| + HOST_ARCH=$(get_ndk_host_arch)
|
| + CONFIG=$(get_arch_gnu_config $ARCH)
|
| + GCC=$(get_ndk_toolchain_prebuilt \
|
| + "$NDK_DIR" "$ARCH" "$HOST_OS-$HOST_ARCH/bin/$CONFIG-gcc")
|
| + if [ -z "$GCC" -a "$HOST_ARCH" = "x86_64" ]; then
|
| + GCC=$(get_ndk_toolchain_prebuilt \
|
| + "$NDK_DIR" "$ARCH" "$HOST_OS-x86/bin/$CONFIG-gcc")
|
| + fi
|
| + if [ ! -f "$GCC" -a "$ARCH" = "x86" ]; then
|
| + # Special case, the x86 toolchain used to be incorrectly
|
| + # named i686-android-linux-gcc!
|
| + GCC=$(get_ndk_toolchain_prebuilt \
|
| + "$NDK_DIR" "$ARCH" "$HOST_OS-x86/bin/i686-android-linux-gcc")
|
| + fi
|
| + if [ -z "$GCC" ]; then
|
| + panic "Cannot find Android NDK toolchain for '$ARCH' architecture. \
|
| +Please verify your NDK installation!"
|
| + fi
|
| + echo "${GCC%%gcc}"
|
| +}
|
| +
|
| +# $1: NDK install path
|
| +# $2: target architecture.
|
| +get_ndk_gdbserver () {
|
| + local NDK_DIR="$1"
|
| + local ARCH=$2
|
| + local BINARY
|
| +
|
| + # The location has moved after NDK r8
|
| + BINARY=$NDK_DIR/prebuilt/android-$ARCH/gdbserver/gdbserver
|
| + if [ ! -f "$BINARY" ]; then
|
| + BINARY=$(get_ndk_toolchain_prebuilt "$NDK_DIR" "$ARCH" gdbserver)
|
| + fi
|
| + echo "$BINARY"
|
| +}
|
| +
|
| +# Check/probe the path to the Android toolchain installation. Always
|
| +# use the NDK versions of gdb and gdbserver. They must match to avoid
|
| +# issues when both binaries do not speak the same wire protocol.
|
| +#
|
| +if [ -z "$TOOLCHAIN" ]; then
|
| + ANDROID_TOOLCHAIN=$(get_ndk_toolchain_fullprefix \
|
| + "$ANDROID_NDK_ROOT" "$TARGET_ARCH")
|
| + ANDROID_TOOLCHAIN=$(dirname "$ANDROID_TOOLCHAIN")
|
| + log "Auto-config: --toolchain=$ANDROID_TOOLCHAIN"
|
| +else
|
| + # Be flexible, allow one to specify either the install path or the bin
|
| + # sub-directory in --toolchain:
|
| + #
|
| + if [ -d "$TOOLCHAIN/bin" ]; then
|
| + TOOLCHAIN=$TOOLCHAIN/bin
|
| + fi
|
| + ANDROID_TOOLCHAIN=$TOOLCHAIN
|
| +fi
|
| +
|
| +# Cosmetic: Remove trailing directory separator.
|
| +ANDROID_TOOLCHAIN=${ANDROID_TOOLCHAIN%/}
|
| +
|
| +# Find host GDB client binary
|
| +if [ -z "$GDB" ]; then
|
| + GDB=$(which $ANDROID_TOOLCHAIN/*-$GDBEXEPOSTFIX 2>/dev/null | head -1)
|
| + if [ -z "$GDB" ]; then
|
| + panic "Can't find Android gdb client in your path, check your \
|
| +--toolchain or --gdb path."
|
| + fi
|
| + log "Host gdb client: $GDB"
|
| +fi
|
| +
|
| +# Find gdbserver binary, we will later push it to /data/local/tmp
|
| +# This ensures that both gdbserver and $GDB talk the same binary protocol,
|
| +# otherwise weird problems will appear.
|
| +#
|
| +if [ -z "$GDBSERVER" ]; then
|
| + GDBSERVER=$(get_ndk_gdbserver "$ANDROID_NDK_ROOT" "$TARGET_ARCH")
|
| + if [ -z "$GDBSERVER" ]; then
|
| + panic "Can't find NDK gdbserver binary. use --gdbserver to specify \
|
| +valid one!"
|
| + fi
|
| + log "Auto-config: --gdbserver=$GDBSERVER"
|
| +fi
|
| +
|
| +# A unique ID for this script's session. This needs to be the same in all
|
| +# sub-shell commands we're going to launch, so take the PID of the launcher
|
| +# process.
|
| +TMP_ID=$$
|
| +
|
| +# Temporary directory, will get cleaned up on exit.
|
| +TMPDIR=/tmp/$USER-adb-gdb-tmp-$TMP_ID
|
| +mkdir -p "$TMPDIR" && rm -rf "$TMPDIR"/*
|
| +
|
| +GDBSERVER_PIDFILE="$TMPDIR"/gdbserver-$TMP_ID.pid
|
| +
|
| +# If --force is specified, try to kill any gdbserver process started by the
|
| +# same user on the device. Normally, these are killed automatically by the
|
| +# script on exit, but there are a few corner cases where this would still
|
| +# be needed.
|
| +if [ "$FORCE" ]; then
|
| + GDBSERVER_PIDS=$(adb_shell ps | awk '$9 ~ /gdbserver/ { print $2; }')
|
| + for GDB_PID in $GDBSERVER_PIDS; do
|
| + log "Killing previous gdbserver (PID=$GDB_PID)"
|
| + adb_shell kill -9 $GDB_PID
|
| + done
|
| +fi
|
| +
|
| +if [ "$START" ]; then
|
| + log "Starting $PROGRAM_NAME on device."
|
| + adb_shell am start -n $PACKAGE_NAME/$ACTIVITY 2>/dev/null
|
| + adb_shell ps | grep -q $PACKAGE_NAME
|
| + fail_panic "Could not start $PROGRAM_NAME on device. Are you sure the \
|
| +package is installed?"
|
| +fi
|
| +
|
| +# Return the timestamp of a given time, as number of seconds since epoch.
|
| +# $1: file path
|
| +# Out: file timestamp
|
| +get_file_timestamp () {
|
| + stat -c %Y "$1" 2>/dev/null
|
| +}
|
| +
|
| +# Detect the build type and symbol directory. This is done by finding
|
| +# the most recent sub-directory containing debug shared libraries under
|
| +# $CHROMIUM_SRC/$CHROMIUM_OUT_DIR/
|
| +#
|
| +# $1: $BUILDTYPE value, can be empty
|
| +# Out: nothing, but this sets SYMBOL_DIR
|
| +#
|
| +detect_symbol_dir () {
|
| + local SUBDIRS SUBDIR LIST DIR DIR_LIBS TSTAMP
|
| + # Note: Ninja places debug libraries under out/$BUILDTYPE/lib/, while
|
| + # Make places then under out/$BUILDTYPE/lib.target.
|
| + if [ "$1" ]; then
|
| + SUBDIRS="$1/lib $1/lib.target"
|
| + else
|
| + SUBDIRS="Release/lib Debug/lib Release/lib.target Debug/lib.target"
|
| + fi
|
| + LIST=$TMPDIR/scan-subdirs-$$.txt
|
| + printf "" > "$LIST"
|
| + for SUBDIR in $SUBDIRS; do
|
| + DIR=$CHROMIUM_SRC/$CHROMIUM_OUT_DIR/$SUBDIR
|
| + if [ -d "$DIR" ]; then
|
| + # Ignore build directories that don't contain symbol versions
|
| + # of the shared libraries.
|
| + DIR_LIBS=$(ls "$DIR"/lib*.so 2>/dev/null)
|
| + if [ -z "$DIR_LIBS" ]; then
|
| + echo "No shared libs: $DIR"
|
| + continue
|
| + fi
|
| + TSTAMP=$(get_file_timestamp "$DIR")
|
| + printf "%s %s\n" "$TSTAMP" "$SUBDIR" >> "$LIST"
|
| + fi
|
| + done
|
| + SUBDIR=$(cat $LIST | sort -r | head -1 | cut -d" " -f2)
|
| + rm -f "$LIST"
|
| +
|
| + if [ -z "$SUBDIR" ]; then
|
| + if [ -z "$1" ]; then
|
| + panic "Could not find any build directory under \
|
| +$CHROMIUM_SRC/$CHROMIUM_OUT_DIR. Please build the program first!"
|
| + else
|
| + panic "Could not find any $1 directory under \
|
| +$CHROMIUM_SRC/$CHROMIUM_OUT_DIR. Check your build type!"
|
| + fi
|
| + fi
|
| +
|
| + SYMBOL_DIR=$CHROMIUM_SRC/$CHROMIUM_OUT_DIR/$SUBDIR
|
| + log "Auto-config: --symbol-dir=$SYMBOL_DIR"
|
| +}
|
| +
|
| +if [ -z "$SYMBOL_DIR" ]; then
|
| + detect_symbol_dir "$BUILDTYPE"
|
| +fi
|
| +
|
| +# Allow several concurrent debugging sessions
|
| +TARGET_GDBSERVER=/data/data/$PACKAGE_NAME/gdbserver-adb-gdb-$TMP_ID
|
| +TMP_TARGET_GDBSERVER=/data/local/tmp/gdbserver-adb-gdb-$TMP_ID
|
| +
|
| +# Return the build fingerprint contained in a build.prop file.
|
| +# $1: path to build.prop file
|
| +get_build_fingerprint_from () {
|
| + cat "$1" | grep -e '^ro.build.fingerprint=' | cut -d= -f2
|
| +}
|
| +
|
| +
|
| +ORG_PULL_LIBS_DIR=$PULL_LIBS_DIR
|
| +PULL_LIBS_DIR=${PULL_LIBS_DIR:-$DEFAULT_PULL_LIBS_DIR}
|
| +
|
| +HOST_FINGERPRINT=
|
| +DEVICE_FINGERPRINT=$(adb_shell getprop ro.build.fingerprint)
|
| +log "Device build fingerprint: $DEVICE_FINGERPRINT"
|
| +
|
| +# If --pull-libs-dir is not specified, and this is a platform build, look
|
| +# if we can use the symbolic libraries under $ANDROID_PRODUCT_OUT/symbols/
|
| +# directly, if the build fingerprint matches the device.
|
| +if [ -z "$ORG_PULL_LIBS_DIR" -a \
|
| + "$ANDROID_PRODUCT_OUT" -a \
|
| + -f "$ANDROID_PRODUCT_OUT/system/build.prop" ]; then
|
| + ANDROID_FINGERPRINT=$(get_build_fingerprint_from \
|
| + "$ANDROID_PRODUCT_OUT"/system/build.prop)
|
| + log "Android build fingerprint: $ANDROID_FINGERPRINT"
|
| + if [ "$ANDROID_FINGERPRINT" = "$DEVICE_FINGERPRINT" ]; then
|
| + log "Perfect match!"
|
| + PULL_LIBS_DIR=$ANDROID_PRODUCT_OUT/symbols
|
| + HOST_FINGERPRINT=$ANDROID_FINGERPRINT
|
| + if [ "$PULL_LIBS" ]; then
|
| + log "Ignoring --pull-libs since the device and platform build \
|
| +fingerprints match."
|
| + NO_PULL_LIBS=true
|
| + fi
|
| + fi
|
| +fi
|
| +
|
| +# If neither --pull-libs an --no-pull-libs were specified, check the build
|
| +# fingerprints of the device, and the cached system libraries on the host.
|
| +#
|
| +if [ -z "$NO_PULL_LIBS" -a -z "$PULL_LIBS" ]; then
|
| + if [ ! -f "$PULL_LIBS_DIR/build.prop" ]; then
|
| + log "Auto-config: --pull-libs (no cached libraries)"
|
| + PULL_LIBS=true
|
| + else
|
| + HOST_FINGERPRINT=$(get_build_fingerprint_from "$PULL_LIBS_DIR/build.prop")
|
| + log "Host build fingerprint: $HOST_FINGERPRINT"
|
| + if [ "$HOST_FINGERPRINT" == "$DEVICE_FINGERPRINT" ]; then
|
| + log "Auto-config: --no-pull-libs (fingerprint match)"
|
| + NO_PULL_LIBS=true
|
| + else
|
| + log "Auto-config: --pull-libs (fingerprint mismatch)"
|
| + PULL_LIBS=true
|
| + fi
|
| + fi
|
| +fi
|
| +
|
| +# Extract the system libraries from the device if necessary.
|
| +if [ "$PULL_LIBS" -a -z "$NO_PULL_LIBS" ]; then
|
| + echo "Extracting system libraries into: $PULL_LIBS_DIR"
|
| +fi
|
| +
|
| +mkdir -p "$PULL_LIBS_DIR"
|
| +fail_panic "Can't create --libs-dir directory: $PULL_LIBS_DIR"
|
| +
|
| +# If requested, work for M-x gdb. The gdb indirections make it
|
| +# difficult to pass --annotate=3 to the gdb binary itself.
|
| +GDB_ARGS=
|
| +if [ "$ANNOTATE" ]; then
|
| + GDB_ARGS=$GDB_ARGS" --annotate=$ANNOTATE"
|
| +fi
|
| +
|
| +# Get the PID from the first argument or else find the PID of the
|
| +# browser process.
|
| +if [ -z "$PID" ]; then
|
| + PROCESSNAME=$PACKAGE_NAME
|
| + if [ "$SANDBOXED_INDEX" ]; then
|
| + PROCESSNAME=$PROCESSNAME:sandboxed_process$SANDBOXED_INDEX
|
| + elif [ "$SANDBOXED" ]; then
|
| + PROCESSNAME=$PROCESSNAME:sandboxed_process
|
| + PID=$(adb_shell ps | \
|
| + awk '$9 ~ /^'$PROCESSNAME'/ { print $2; }' | head -1)
|
| + elif [ "$PRIVILEGED_INDEX" ]; then
|
| + PROCESSNAME=$PROCESSNAME:privileged_process$PRIVILEGED_INDEX
|
| + elif [ "$PRIVILEGED" ]; then
|
| + PROCESSNAME=$PROCESSNAME:privileged_process
|
| + PID=$(adb_shell ps | \
|
| + awk '$9 ~ /^'$PROCESSNAME'/ { print $2; }' | head -1)
|
| + fi
|
| + if [ -z "$PID" ]; then
|
| + PID=$(adb_shell ps | \
|
| + awk '$9 == "'$PROCESSNAME'" { print $2; }' | head -1)
|
| + fi
|
| + if [ -z "$PID" ]; then
|
| + if [ "$START" ]; then
|
| + panic "Can't find application process PID, did it crash?"
|
| + else
|
| + panic "Can't find application process PID, are you sure it is \
|
| +running? Try using --start."
|
| + fi
|
| + fi
|
| + log "Found process PID: $PID"
|
| +elif [ "$SANDBOXED" ]; then
|
| + echo "WARNING: --sandboxed option ignored due to use of --pid."
|
| +elif [ "$PRIVILEGED" ]; then
|
| + echo "WARNING: --privileged option ignored due to use of --pid."
|
| +fi
|
| +
|
| +# Determine if 'adb shell' runs as root or not.
|
| +# If so, we can launch gdbserver directly, otherwise, we have to
|
| +# use run-as $PACKAGE_NAME ..., which requires the package to be debuggable.
|
| +#
|
| +if [ "$SU_PREFIX" ]; then
|
| + # Need to check that this works properly.
|
| + SU_PREFIX_TEST_LOG=$TMPDIR/su-prefix.log
|
| + adb_shell $SU_PREFIX \"echo "foo"\" > $SU_PREFIX_TEST_LOG 2>&1
|
| + if [ $? != 0 -o "$(cat $SU_PREFIX_TEST_LOG)" != "foo" ]; then
|
| + echo "ERROR: Cannot use '$SU_PREFIX' as a valid su prefix:"
|
| + echo "$ adb shell $SU_PREFIX \"echo foo\""
|
| + cat $SU_PREFIX_TEST_LOG
|
| + exit 1
|
| + fi
|
| + COMMAND_PREFIX="$SU_PREFIX \""
|
| + COMMAND_SUFFIX="\""
|
| +else
|
| + SHELL_UID=$(adb shell cat /proc/self/status | \
|
| + awk '$1 == "Uid:" { print $2; }')
|
| + log "Shell UID: $SHELL_UID"
|
| + if [ "$SHELL_UID" != 0 -o -n "$NO_ROOT" ]; then
|
| + COMMAND_PREFIX="run-as $PACKAGE_NAME"
|
| + COMMAND_SUFFIX=
|
| + else
|
| + COMMAND_PREFIX=
|
| + COMMAND_SUFFIX=
|
| + fi
|
| +fi
|
| +log "Command prefix: '$COMMAND_PREFIX'"
|
| +log "Command suffix: '$COMMAND_SUFFIX'"
|
| +
|
| +# Pull device's system libraries that are mapped by our process.
|
| +# Pulling all system libraries is too long, so determine which ones
|
| +# we need by looking at /proc/$PID/maps instead
|
| +if [ "$PULL_LIBS" -a -z "$NO_PULL_LIBS" ]; then
|
| + echo "Extracting system libraries into: $PULL_LIBS_DIR"
|
| + rm -f $PULL_LIBS_DIR/build.prop
|
| + MAPPINGS=$(adb_shell $COMMAND_PREFIX cat /proc/$PID/maps $COMMAND_SUFFIX)
|
| + if [ $? != 0 ]; then
|
| + echo "ERROR: Could not list process's memory mappings."
|
| + if [ "$SU_PREFIX" ]; then
|
| + panic "Are you sure your --su-prefix is correct?"
|
| + else
|
| + panic "Use --su-prefix if the application is not debuggable."
|
| + fi
|
| + fi
|
| + SYSTEM_LIBS=$(echo "$MAPPINGS" | \
|
| + awk '$6 ~ /\/system\/.*\.so$/ { print $6; }' | sort -u)
|
| + for SYSLIB in /system/bin/linker $SYSTEM_LIBS; do
|
| + echo "Pulling from device: $SYSLIB"
|
| + DST_FILE=$PULL_LIBS_DIR$SYSLIB
|
| + DST_DIR=$(dirname "$DST_FILE")
|
| + mkdir -p "$DST_DIR" && adb pull $SYSLIB "$DST_FILE" 2>/dev/null
|
| + fail_panic "Could not pull $SYSLIB from device !?"
|
| + done
|
| + echo "Pulling device build.prop"
|
| + adb pull /system/build.prop $PULL_LIBS_DIR/build.prop
|
| + fail_panic "Could not pull device build.prop !?"
|
| +fi
|
| +
|
| +# Find all the sub-directories of $PULL_LIBS_DIR, up to depth 4
|
| +# so we can add them to solib-search-path later.
|
| +SOLIB_DIRS=$(find $PULL_LIBS_DIR -mindepth 1 -maxdepth 4 -type d | \
|
| + grep -v "^$" | tr '\n' ':')
|
| +
|
| +# This is a re-implementation of gdbclient, where we use compatible
|
| +# versions of gdbserver and $GDBNAME to ensure that everything works
|
| +# properly.
|
| +#
|
| +
|
| +# Push gdbserver to the device
|
| +log "Pushing gdbserver $GDBSERVER to $TARGET_GDBSERVER"
|
| +adb push $GDBSERVER $TMP_TARGET_GDBSERVER &>/dev/null
|
| +adb shell $COMMAND_PREFIX cp $TMP_TARGET_GDBSERVER $TARGET_GDBSERVER
|
| +adb shell rm $TMP_TARGET_GDBSERVER
|
| +fail_panic "Could not copy gdbserver to the device!"
|
| +
|
| +if [ -z "$PORT" ]; then
|
| + PORT=5039
|
| +fi
|
| +HOST_PORT=$PORT
|
| +TARGET_PORT=$PORT
|
| +
|
| +# Select correct app_process for architecture.
|
| +case $TARGET_ARCH in
|
| + arm|x86|mips) GDBEXEC=app_process;;
|
| + arm64|x86_64) GDBEXEC=app_process64;;
|
| + *) fail_panic "Unknown app_process for architecture!";;
|
| +esac
|
| +
|
| +# Detect AddressSanitizer setup on the device. In that case app_process is a
|
| +# script, and the real executable is app_process.real.
|
| +GDBEXEC_ASAN=app_process.real
|
| +adb_shell ls /system/bin/$GDBEXEC_ASAN
|
| +if [ $? == 0 ]; then
|
| + GDBEXEC=$GDBEXEC_ASAN
|
| +fi
|
| +
|
| +# Pull the app_process binary from the device.
|
| +log "Pulling $GDBEXEC from device"
|
| +adb pull /system/bin/$GDBEXEC "$TMPDIR"/$GDBEXEC &>/dev/null
|
| +fail_panic "Could not retrieve $GDBEXEC from the device!"
|
| +
|
| +# Setup network redirection
|
| +log "Setting network redirection (host:$HOST_PORT -> device:$TARGET_PORT)"
|
| +adb forward tcp:$HOST_PORT tcp:$TARGET_PORT
|
| +fail_panic "Could not setup network redirection from \
|
| +host:localhost:$HOST_PORT to device:localhost:$TARGET_PORT!"
|
| +
|
| +# Start gdbserver in the background
|
| +# Note that using run-as requires the package to be debuggable.
|
| +#
|
| +# If not, this will fail horribly. The alternative is to run the
|
| +# program as root, which requires of course root privileges.
|
| +# Maybe we should add a --root option to enable this?
|
| +#
|
| +log "Starting gdbserver in the background:"
|
| +GDBSERVER_LOG=$TMPDIR/gdbserver-$TMP_ID.log
|
| +log "adb shell $COMMAND_PREFIX $TARGET_GDBSERVER :$TARGET_PORT \
|
| +--attach $PID $COMMAND_SUFFIX"
|
| +("$ADB" shell $COMMAND_PREFIX $TARGET_GDBSERVER :$TARGET_PORT \
|
| + --attach $PID $COMMAND_SUFFIX > $GDBSERVER_LOG 2>&1) &
|
| +GDBSERVER_PID=$!
|
| +echo "$GDBSERVER_PID" > $GDBSERVER_PIDFILE
|
| +log "background job pid: $GDBSERVER_PID"
|
| +
|
| +# Check that it is still running after a few seconds. If not, this means we
|
| +# could not properly attach to it
|
| +sleep 2
|
| +log "Job control: $(jobs -l)"
|
| +STATE=$(jobs -l | awk '$2 == "'$GDBSERVER_PID'" { print $3; }')
|
| +if [ "$STATE" != "Running" ]; then
|
| + echo "ERROR: GDBServer could not attach to PID $PID!"
|
| + if [ $(adb_shell su -c getenforce) != "Permissive" ]; then
|
| + echo "Device mode is Enforcing. Changing Device mode to Permissive "
|
| + $(adb_shell su -c setenforce 0)
|
| + if [ $(adb_shell su -c getenforce) != "Permissive" ]; then
|
| + echo "ERROR: Failed to Change Device mode to Permissive"
|
| + echo "Failure log (use --verbose for more information):"
|
| + cat $GDBSERVER_LOG
|
| + exit 1
|
| + fi
|
| + else
|
| + echo "Failure log (use --verbose for more information):"
|
| + cat $GDBSERVER_LOG
|
| + exit 1
|
| + fi
|
| +fi
|
| +
|
| +# Generate a file containing useful GDB initialization commands
|
| +readonly COMMANDS=$TMPDIR/gdb.init
|
| +log "Generating GDB initialization commands file: $COMMANDS"
|
| +echo -n "" > $COMMANDS
|
| +echo "set print pretty 1" >> $COMMANDS
|
| +echo "python" >> $COMMANDS
|
| +echo "import sys" >> $COMMANDS
|
| +echo "sys.path.insert(0, '$CHROMIUM_SRC/tools/gdb/')" >> $COMMANDS
|
| +echo "try:" >> $COMMANDS
|
| +echo " import gdb_chrome" >> $COMMANDS
|
| +echo "finally:" >> $COMMANDS
|
| +echo " sys.path.pop(0)" >> $COMMANDS
|
| +echo "end" >> $COMMANDS
|
| +echo "file $TMPDIR/$GDBEXEC" >> $COMMANDS
|
| +echo "directory $CHROMIUM_SRC" >> $COMMANDS
|
| +echo "set solib-absolute-prefix $PULL_LIBS_DIR" >> $COMMANDS
|
| +echo "set solib-search-path $SOLIB_DIRS:$PULL_LIBS_DIR:$SYMBOL_DIR" \
|
| + >> $COMMANDS
|
| +echo "echo Attaching and reading symbols, this may take a while.." \
|
| + >> $COMMANDS
|
| +echo "target remote :$HOST_PORT" >> $COMMANDS
|
| +
|
| +if [ "$GDBINIT" ]; then
|
| + cat "$GDBINIT" >> $COMMANDS
|
| +fi
|
| +
|
| +if [ "$VERBOSE" -gt 0 ]; then
|
| + echo "### START $COMMANDS"
|
| + cat $COMMANDS
|
| + echo "### END $COMMANDS"
|
| +fi
|
| +
|
| +log "Launching gdb client: $GDB $GDB_ARGS -x $COMMANDS"
|
| +$GDB $GDB_ARGS -x $COMMANDS &&
|
| +rm -f "$GDBSERVER_PIDFILE"
|
|
|