| #!/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 |