|  | #!/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") | 
|  |  | 
|  | # Force locale to C to allow recognizing output from subprocesses. | 
|  | LC_ALL=C | 
|  |  | 
|  | # Location of Chromium-top-level sources. | 
|  | CHROMIUM_SRC=$(cd "$PROGDIR"/../.. >/dev/null && pwd 2>/dev/null) | 
|  |  | 
|  | TMPDIR= | 
|  | GDBSERVER_PIDFILE= | 
|  | TARGET_GDBSERVER= | 
|  | COMMAND_PREFIX= | 
|  | COMMAND_SUFFIX= | 
|  |  | 
|  | 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 | 
|  | rm -f "$GDBSERVER_PIDFILE" | 
|  | fi | 
|  | if [ "$TARGET_GDBSERVER" ]; then | 
|  | log "Removing target gdbserver binary: $TARGET_GDBSERVER." | 
|  | "$ADB" shell "$COMMAND_PREFIX" rm "$TARGET_GDBSERVER" \ | 
|  | "$TARGET_DOMAIN_SOCKET" "$COMMAND_SUFFIX" >/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/adb-gdb-support-$USER" | 
|  | IDE_DIR="$DEFAULT_PULL_LIBS_DIR" | 
|  |  | 
|  | # 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 program name through ADB_GDB_PROGNAME | 
|  | PROGNAME=${ADB_GDB_PROGNAME:-$(basename "$0")} | 
|  |  | 
|  | ADB= | 
|  | ANNOTATE= | 
|  | CGDB= | 
|  | GDBINIT= | 
|  | GDBSERVER= | 
|  | HELP= | 
|  | IDE= | 
|  | NDK_DIR= | 
|  | NO_PULL_LIBS= | 
|  | PACKAGE_NAME= | 
|  | PID= | 
|  | PORT= | 
|  | PROGRAM_NAME="activity" | 
|  | PULL_LIBS= | 
|  | PULL_LIBS_DIR= | 
|  | ATTACH_DELAY=1 | 
|  | SU_PREFIX= | 
|  | SYMBOL_DIR= | 
|  | TARGET_ARCH= | 
|  | TOOLCHAIN= | 
|  | VERBOSE=0 | 
|  |  | 
|  | for opt; do | 
|  | optarg=$(expr "x$opt" : 'x[^=]*=\(.*\)') | 
|  | case $opt in | 
|  | --adb=*) | 
|  | ADB=$optarg | 
|  | ;; | 
|  | --device=*) | 
|  | export ANDROID_SERIAL=$optarg | 
|  | ;; | 
|  | --annotate=3) | 
|  | ANNOTATE=$optarg | 
|  | ;; | 
|  | --gdbserver=*) | 
|  | GDBSERVER=$optarg | 
|  | ;; | 
|  | --gdb=*) | 
|  | GDB=$optarg | 
|  | ;; | 
|  | --help|-h|-?) | 
|  | HELP=true | 
|  | ;; | 
|  | --ide) | 
|  | IDE=true | 
|  | ;; | 
|  | --ndk-dir=*) | 
|  | NDK_DIR=$optarg | 
|  | ;; | 
|  | --no-pull-libs) | 
|  | NO_PULL_LIBS=true | 
|  | ;; | 
|  | --package-name=*) | 
|  | PACKAGE_NAME=$optarg | 
|  | ;; | 
|  | --pid=*) | 
|  | PID=$optarg | 
|  | ;; | 
|  | --port=*) | 
|  | PORT=$optarg | 
|  | ;; | 
|  | --program-name=*) | 
|  | PROGRAM_NAME=$optarg | 
|  | ;; | 
|  | --pull-libs) | 
|  | PULL_LIBS=true | 
|  | ;; | 
|  | --pull-libs-dir=*) | 
|  | PULL_LIBS_DIR=$optarg | 
|  | ;; | 
|  | --script=*) | 
|  | GDBINIT=$optarg | 
|  | ;; | 
|  | --attach-delay=*) | 
|  | ATTACH_DELAY=$optarg | 
|  | ;; | 
|  | --su-prefix=*) | 
|  | SU_PREFIX=$optarg | 
|  | ;; | 
|  | --symbol-dir=*) | 
|  | SYMBOL_DIR=$optarg | 
|  | ;; | 
|  | --output-directory=*) | 
|  | CHROMIUM_OUTPUT_DIR=$optarg | 
|  | ;; | 
|  | --target-arch=*) | 
|  | TARGET_ARCH=$optarg | 
|  | ;; | 
|  | --toolchain=*) | 
|  | TOOLCHAIN=$optarg | 
|  | ;; | 
|  | --cgdb) | 
|  | CGDB=cgdb | 
|  | ;; | 
|  | --cgdb=*) | 
|  | CGDB=$optarg | 
|  | ;; | 
|  | --verbose) | 
|  | VERBOSE=$(( $VERBOSE + 1 )) | 
|  | ;; | 
|  | -*) | 
|  | 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 | 
|  |  | 
|  | 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 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>/lib/                (used by GYP builds) | 
|  | \$CHROMIUM_SRC/<out>/lib.unstripped/     (used by GN builds) | 
|  |  | 
|  | Where <out> is determined by CHROMIUM_OUTPUT_DIR, or --output-directory. | 
|  |  | 
|  | You can set the path manually via --symbol-dir. | 
|  |  | 
|  | 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 a device is not specified with either --device or ANDROID_SERIAL). | 
|  |  | 
|  | 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. | 
|  |  | 
|  | --cgdb[=<file>]       Use cgdb (an interface for gdb that shows the code). | 
|  | --symbol-dir=<path>   Specify directory with symbol shared libraries. | 
|  | --output-directory=<path> Specify the output directory (e.g. "out/Debug"). | 
|  | --package-name=<name> Specify package name (alternative to 1st argument). | 
|  | --program-name=<name> Specify program name (cosmetic only). | 
|  | --pid=<pid>           Specify application process pid. | 
|  | --attach-delay=<num>  Seconds to wait for gdbserver to attach to the | 
|  | remote process before starting gdb. Default 1. | 
|  | <num> may be a float if your sleep(1) supports it. | 
|  | --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. | 
|  | --device=<file>       ADB device serial to use (-s flag). | 
|  | --port=<port>         Specify the tcp port to use. | 
|  | --ide                 Forward gdb port, but do not enter gdb console. | 
|  |  | 
|  | --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. | 
|  |  | 
|  | EOF | 
|  | exit 0 | 
|  | fi | 
|  |  | 
|  | if [ -z "$PACKAGE_NAME" ]; then | 
|  | panic "Please specify a package name on the command line. See --help." | 
|  | fi | 
|  |  | 
|  | if [[ -z "$SYMBOL_DIR" && -z "$CHROMIUM_OUTPUT_DIR" ]]; then | 
|  | if [[ -e "build.ninja" ]]; then | 
|  | CHROMIUM_OUTPUT_DIR=$PWD | 
|  | else | 
|  | panic "Please specify an output directory by using one of: | 
|  | --output-directory=out/Debug | 
|  | CHROMIUM_OUTPUT_DIR=out/Debug | 
|  | Setting working directory to an output directory. | 
|  | See --help." | 
|  | fi | 
|  | fi | 
|  |  | 
|  | if ls *.so >/dev/null 2>&1; then | 
|  | panic ".so files found in your working directory. These will conflict with" \ | 
|  | "library lookup logic. Change your working directory and try again." | 
|  | fi | 
|  |  | 
|  | # Detect the build type and symbol directory. This is done by finding | 
|  | # the most recent sub-directory containing debug shared libraries under | 
|  | # $CHROMIUM_OUTPUT_DIR. | 
|  | # | 
|  | # Out: nothing, but this sets SYMBOL_DIR | 
|  | # | 
|  | detect_symbol_dir () { | 
|  | # GYP places unstripped libraries under out/lib | 
|  | # GN places them under out/lib.unstripped | 
|  | local PARENT_DIR="$CHROMIUM_OUTPUT_DIR" | 
|  | if [[ ! -e "$PARENT_DIR" ]]; then | 
|  | PARENT_DIR="$CHROMIUM_SRC/$PARENT_DIR" | 
|  | fi | 
|  | SYMBOL_DIR="$PARENT_DIR/lib.unstripped" | 
|  | if [[ -z "$(ls "$SYMBOL_DIR"/lib*.so 2>/dev/null)" ]]; then | 
|  | SYMBOL_DIR="$PARENT_DIR/lib" | 
|  | if [[ -z "$(ls "$SYMBOL_DIR"/lib*.so 2>/dev/null)" ]]; then | 
|  | panic "Could not find any symbols under \ | 
|  | $PARENT_DIR/lib{.unstripped}. Please build the program first!" | 
|  | fi | 
|  | fi | 
|  | log "Auto-config: --symbol-dir=$SYMBOL_DIR" | 
|  | } | 
|  |  | 
|  | if [ -z "$SYMBOL_DIR" ]; then | 
|  | detect_symbol_dir | 
|  | elif [[ -z "$(ls "$SYMBOL_DIR"/lib*.so 2>/dev/null)" ]]; then | 
|  | panic "Could not find any symbols under $SYMBOL_DIR" | 
|  | 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" -gt 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 a local shared library. | 
|  | # This returns an NDK-compatible architecture name. | 
|  | # out: NDK Architecture name, or empty string. | 
|  | get_gyp_target_arch () { | 
|  | # ls prints a broken pipe error when there are a lot of libs. | 
|  | local RANDOM_LIB=$(ls "$SYMBOL_DIR"/lib*.so 2>/dev/null| head -n1) | 
|  | local SO_DESC=$(file $RANDOM_LIB) | 
|  | case $ARCH in | 
|  | *32-bit*ARM,*) echo "arm";; | 
|  | *64-bit*ARM,*) echo "arm64";; | 
|  | *32-bit*Intel,*) echo "x86";; | 
|  | *x86-64,*) echo "x86_64";; | 
|  | *32-bit*MIPS,*) echo "mips";; | 
|  | *) 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) | 
|  | if [ "$HOST_ARCH" = "unknown" ]; then | 
|  | # In case where "-p" returns "unknown" just use "-m" (machine hardware | 
|  | # name). According to this patch from Fedora "-p" is equivalent to "-m" | 
|  | # anyway: https://goo.gl/Pd47x3 | 
|  | HOST_ARCH=$(uname -m) | 
|  | fi | 
|  | 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 and x86_64! | 
|  | if [ "$1" = "x86" -o "$1" = "x86_64" ]; 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 LD CONFIG | 
|  |  | 
|  | # NOTE: This will need to be updated if the NDK changes the names or moves | 
|  | #        the location of its prebuilt toolchains. | 
|  | # | 
|  | LD= | 
|  | HOST_OS=$(get_ndk_host_system) | 
|  | HOST_ARCH=$(get_ndk_host_arch) | 
|  | CONFIG=$(get_arch_gnu_config $ARCH) | 
|  | LD=$(get_ndk_toolchain_prebuilt \ | 
|  | "$NDK_DIR" "$ARCH" "$HOST_OS-$HOST_ARCH/bin/$CONFIG-ld") | 
|  | if [ -z "$LD" -a "$HOST_ARCH" = "x86_64" ]; then | 
|  | LD=$(get_ndk_toolchain_prebuilt \ | 
|  | "$NDK_DIR" "$ARCH" "$HOST_OS-x86/bin/$CONFIG-ld") | 
|  | fi | 
|  | if [ ! -f "$LD" -a "$ARCH" = "x86" ]; then | 
|  | # Special case, the x86 toolchain used to be incorrectly | 
|  | # named i686-android-linux-gcc! | 
|  | LD=$(get_ndk_toolchain_prebuilt \ | 
|  | "$NDK_DIR" "$ARCH" "$HOST_OS-x86/bin/i686-android-linux-ld") | 
|  | fi | 
|  | if [ -z "$LD" ]; then | 
|  | panic "Cannot find Android NDK toolchain for '$ARCH' architecture. \ | 
|  | Please verify your NDK installation!" | 
|  | fi | 
|  | echo "${LD%%ld}" | 
|  | } | 
|  |  | 
|  | # $1: NDK install path | 
|  | get_ndk_host_gdb_client() { | 
|  | local NDK_DIR="$1" | 
|  | local HOST_OS HOST_ARCH | 
|  |  | 
|  | HOST_OS=$(get_ndk_host_system) | 
|  | HOST_ARCH=$(get_ndk_host_arch) | 
|  | echo "$NDK_DIR/prebuilt/$HOST_OS-$HOST_ARCH/bin/gdb" | 
|  | } | 
|  |  | 
|  | # $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=$(get_ndk_host_gdb_client "$ANDROID_NDK_ROOT") | 
|  | 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 | 
|  |  | 
|  | # Return the timestamp of a given file, as number of seconds since epoch. | 
|  | # $1: file path | 
|  | # Out: file timestamp | 
|  | get_file_timestamp () { | 
|  | stat -c %Y "$1" 2>/dev/null | 
|  | } | 
|  |  | 
|  | # Allow several concurrent debugging sessions | 
|  | APP_DATA_DIR=$(adb_shell run-as $PACKAGE_NAME /system/bin/sh -c pwd) | 
|  | fail_panic "Failed to run-as $PACKAGE_NAME, is the app debuggable?" | 
|  | TARGET_GDBSERVER="$APP_DATA_DIR/gdbserver-adb-gdb-$TMP_ID" | 
|  | TMP_TARGET_GDBSERVER=/data/local/tmp/gdbserver-adb-gdb-$TMP_ID | 
|  |  | 
|  | # Select correct app_process for architecture. | 
|  | case $TARGET_ARCH in | 
|  | arm|x86|mips) GDBEXEC=app_process32;; | 
|  | arm64|x86_64) GDBEXEC=app_process64; SUFFIX_64_BIT=64;; | 
|  | *) panic "Unknown app_process for architecture!";; | 
|  | esac | 
|  |  | 
|  | # Default to app_process if bit-width specific process isn't found. | 
|  | adb_shell ls /system/bin/$GDBEXEC > /dev/null | 
|  | if [ $? != 0 ]; then | 
|  | GDBEXEC=app_process | 
|  | fi | 
|  |  | 
|  | # 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 > /dev/null | 
|  | if [ $? == 0 ]; then | 
|  | GDBEXEC=$GDBEXEC_ASAN | 
|  | fi | 
|  |  | 
|  | ORG_PULL_LIBS_DIR=$PULL_LIBS_DIR | 
|  | if [[ -n "$ANDROID_SERIAL" ]]; then | 
|  | DEFAULT_PULL_LIBS_DIR="$DEFAULT_PULL_LIBS_DIR/$ANDROID_SERIAL-$SUFFIX_64_BIT" | 
|  | fi | 
|  | PULL_LIBS_DIR=${PULL_LIBS_DIR:-$DEFAULT_PULL_LIBS_DIR} | 
|  |  | 
|  | HOST_FINGERPRINT= | 
|  | DEVICE_FINGERPRINT=$(adb_shell getprop ro.build.fingerprint) | 
|  | [[ "$DEVICE_FINGERPRINT" ]] || panic "Failed to get the device fingerprint" | 
|  | log "Device build fingerprint: $DEVICE_FINGERPRINT" | 
|  |  | 
|  | if [ ! -f "$PULL_LIBS_DIR/build.fingerprint" ]; then | 
|  | log "Auto-config: --pull-libs  (no cached libraries)" | 
|  | PULL_LIBS=true | 
|  | else | 
|  | HOST_FINGERPRINT=$(< "$PULL_LIBS_DIR/build.fingerprint") | 
|  | 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 | 
|  |  | 
|  | # If requested, work for M-x gdb.  The gdb indirections make it | 
|  | # difficult to pass --annotate=3 to the gdb binary itself. | 
|  | 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 [ -z "$PID" ]; then | 
|  | PID=$(adb_shell ps | \ | 
|  | awk '$9 == "'$PROCESSNAME'" { print $2; }' | head -1) | 
|  | fi | 
|  | if [ -z "$PID" ]; then | 
|  | panic "Can't find application process PID." | 
|  | fi | 
|  | log "Found process PID: $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'" | 
|  |  | 
|  | mkdir -p "$PULL_LIBS_DIR" | 
|  | fail_panic "Can't create --libs-dir directory: $PULL_LIBS_DIR" | 
|  |  | 
|  | # 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" | 
|  | 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 | 
|  | # Remove the fingerprint file in case pulling one of the libs fails. | 
|  | rm -f "$PULL_LIBS_DIR/build.fingerprint" | 
|  | SYSTEM_LIBS=$(echo "$MAPPINGS" | \ | 
|  | awk '$6 ~ /\/(system|apex|vendor)\/.*\.so$/ { print $6; }' | sort -u) | 
|  | for SYSLIB in /system/bin/linker$SUFFIX_64_BIT $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 "Writing the device fingerprint" | 
|  | echo "$DEVICE_FINGERPRINT" > "$PULL_LIBS_DIR/build.fingerprint" | 
|  | 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!" | 
|  |  | 
|  | # 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' ':') | 
|  | SOLIB_DIRS=${SOLIB_DIRS%:}  # Strip trailing : | 
|  |  | 
|  | # Applications with minSdkVersion >= 24 will have their data directories | 
|  | # created with rwx------ permissions, preventing adbd from forwarding to | 
|  | # the gdbserver socket. | 
|  | adb_shell $COMMAND_PREFIX chmod a+x $APP_DATA_DIR $COMMAND_SUFFIX | 
|  |  | 
|  | # 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 $COMMAND_SUFFIX && \ | 
|  | adb_shell rm $TMP_TARGET_GDBSERVER | 
|  | fail_panic "Could not copy gdbserver to the device!" | 
|  |  | 
|  | if [ -z "$PORT" ]; then | 
|  | # Random port to allow multiple concurrent sessions. | 
|  | PORT=$(( $RANDOM % 1000 + 5039 )) | 
|  | fi | 
|  | HOST_PORT=$PORT | 
|  | TARGET_DOMAIN_SOCKET=$APP_DATA_DIR/gdb-socket-$HOST_PORT | 
|  |  | 
|  | # Setup network redirection | 
|  | log "Setting network redirection (host:$HOST_PORT -> device:$TARGET_DOMAIN_SOCKET)" | 
|  | "$ADB" forward tcp:$HOST_PORT localfilesystem:$TARGET_DOMAIN_SOCKET | 
|  | fail_panic "Could not setup network redirection from \ | 
|  | host:localhost:$HOST_PORT to device:$TARGET_DOMAIN_SOCKET" | 
|  |  | 
|  | # 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? | 
|  | # | 
|  |  | 
|  | for i in 1 2; do | 
|  | log "Starting gdbserver in the background:" | 
|  | GDBSERVER_LOG=$TMPDIR/gdbserver-$TMP_ID.log | 
|  | log "adb shell $COMMAND_PREFIX $TARGET_GDBSERVER \ | 
|  | --once +$TARGET_DOMAIN_SOCKET \ | 
|  | --attach $PID $COMMAND_SUFFIX" | 
|  | "$ADB" shell $COMMAND_PREFIX $TARGET_GDBSERVER \ | 
|  | --once +$TARGET_DOMAIN_SOCKET \ | 
|  | --attach $PID $COMMAND_SUFFIX > $GDBSERVER_LOG 2>&1 & | 
|  | GDBSERVER_PID=$! | 
|  | echo "$GDBSERVER_PID" > $GDBSERVER_PIDFILE | 
|  | log "background job pid: $GDBSERVER_PID" | 
|  |  | 
|  | # Sleep to allow gdbserver to attach to the remote process and be | 
|  | # ready to connect to. | 
|  | log "Sleeping ${ATTACH_DELAY}s to ensure gdbserver is alive" | 
|  | sleep "$ATTACH_DELAY" | 
|  | log "Job control: $(jobs -l)" | 
|  | STATE=$(jobs -l | awk '$2 == "'$GDBSERVER_PID'" { print $3; }') | 
|  | if [ "$STATE" != "Running" ]; then | 
|  | pid_msg=$(grep "is already traced by process" $GDBSERVER_LOG 2>/dev/null) | 
|  | if [[ -n "$pid_msg" ]]; then | 
|  | old_pid=${pid_msg##* } | 
|  | old_pid=${old_pid//[$'\r\n']}  # Trim trailing \r. | 
|  | echo "Killing previous gdb server process (pid=$old_pid)" | 
|  | adb_shell $COMMAND_PREFIX kill -9 $old_pid $COMMAND_SUFFIX | 
|  | continue | 
|  | fi | 
|  | echo "ERROR: GDBServer either failed to run or attach to PID $PID!" | 
|  | echo "Here is the output from gdbserver (also try --verbose for more):" | 
|  | echo "===== gdbserver.log start =====" | 
|  | cat $GDBSERVER_LOG | 
|  | echo ="===== gdbserver.log end ======" | 
|  | exit 1 | 
|  | fi | 
|  | break | 
|  | done | 
|  |  | 
|  | # Generate a file containing useful GDB initialization commands | 
|  | readonly COMMANDS=$TMPDIR/gdb.init | 
|  | log "Generating GDB initialization commands file: $COMMANDS" | 
|  | cat > "$COMMANDS" <<EOF | 
|  | set osabi GNU/Linux  # Copied from ndk-gdb.py. | 
|  | set print pretty 1 | 
|  | python | 
|  | import sys | 
|  | sys.path.insert(0, '$CHROMIUM_SRC/tools/gdb/') | 
|  | try: | 
|  | import gdb_chrome | 
|  | finally: | 
|  | sys.path.pop(0) | 
|  | end | 
|  | file $TMPDIR/$GDBEXEC | 
|  | directory $CHROMIUM_OUTPUT_DIR | 
|  | set solib-absolute-prefix $PULL_LIBS_DIR | 
|  | set solib-search-path $SOLIB_DIRS:$PULL_LIBS_DIR:$SYMBOL_DIR | 
|  |  | 
|  | python | 
|  | # Copied from ndk-gdb.py: | 
|  | def target_remote_with_retry(target, timeout_seconds): | 
|  | import time | 
|  | end_time = time.time() + timeout_seconds | 
|  | while True: | 
|  | try: | 
|  | gdb.execute('target remote ' + target) | 
|  | return True | 
|  | except gdb.error as e: | 
|  | time_left = end_time - time.time() | 
|  | if time_left < 0 or time_left > timeout_seconds: | 
|  | print("Error: unable to connect to device.") | 
|  | print(e) | 
|  | return False | 
|  | time.sleep(min(0.25, time_left)) | 
|  |  | 
|  | print("Connecting to :$HOST_PORT...") | 
|  | if target_remote_with_retry(':$HOST_PORT', 5): | 
|  | print("Attached! Reading symbols (takes ~30 seconds).") | 
|  | end | 
|  | EOF | 
|  |  | 
|  | if [ "$GDBINIT" ]; then | 
|  | cat "$GDBINIT" >> "$COMMANDS" | 
|  | fi | 
|  |  | 
|  | if [ "$VERBOSE" -gt 0 ]; then | 
|  | echo "### START $COMMANDS" | 
|  | cat "$COMMANDS" | 
|  | echo "### END $COMMANDS" | 
|  | fi | 
|  |  | 
|  | if [ "$IDE" ]; then | 
|  | mkdir -p "$IDE_DIR" | 
|  | SYM_GDB="$IDE_DIR/gdb" | 
|  | SYM_EXE="$IDE_DIR/app_process" | 
|  | SYM_INIT="$IDE_DIR/gdbinit" | 
|  | ln -sf "$TMPDIR/$GDBEXEC" "$SYM_EXE" | 
|  | ln -sf "$COMMANDS" "$SYM_INIT" | 
|  | # gdb doesn't work when symlinked, so create a wrapper. | 
|  | echo | 
|  | cat > $SYM_GDB <<EOF | 
|  | #!/bin/sh | 
|  | exec $GDB "\$@" | 
|  | EOF | 
|  | chmod u+x $SYM_GDB | 
|  |  | 
|  | echo "GDB server listening on: localhost:$PORT" | 
|  | echo "GDB wrapper script: $SYM_GDB" | 
|  | echo "App executable: $SYM_EXE" | 
|  | echo "gdbinit: $SYM_INIT" | 
|  | echo "Connect with vscode: https://chromium.googlesource.com/chromium/src/+/master/docs/vscode.md#Launch-Commands" | 
|  | echo "Showing gdbserver logs. Press Ctrl-C to disconnect." | 
|  | tail -f "$GDBSERVER_LOG" | 
|  | else | 
|  | log "Launching gdb client: $GDB $GDB_ARGS -x $COMMANDS" | 
|  | echo "Server log: $GDBSERVER_LOG" | 
|  | if [ "$CGDB" ]; then | 
|  | $CGDB -d $GDB -- $GDB_ARGS -x "$COMMANDS" | 
|  | else | 
|  | $GDB $GDB_ARGS -x "$COMMANDS" | 
|  | fi | 
|  | fi |