#!/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/$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 program name through ADB_GDB_PROGNAME PROGNAME=${ADB_GDB_PROGNAME:-$(basename "$0")} ADB= ANNOTATE= FORCE= CGDB= GDBINIT= GDBSERVER= HELP= 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 ;; --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 ;; --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 <] Attach gdb to a running Android $PROGRAM_NAME process. If provided, must be the name of the Android application's package name to be debugged. You can also use --package-name= to specify it. EOF fi cat < 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//lib/ (used by GYP builds) \$CHROMIUM_SRC//lib.unstripped/ (used by GN builds) Where 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= 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= is to use --toollchain= 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= 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= 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[=] Use cgdb (an interface for gdb that shows the code). --symbol-dir= Specify directory with symbol shared libraries. --output-directory= Specify the output directory (e.g. "out/Debug"). --package-name= Specify package name (alternative to 1st argument). --program-name= Specify program name (cosmetic only). --pid= Specify application process pid. --force Kill any previous debugging session, if any. --attach-delay= Seconds to wait for gdbserver to attach to the remote process before starting gdb. Default 1. may be a float if your sleep(1) supports it. --annotate= Enable gdb annotation. --script= Specify extra GDB init script. --gdbserver= Specify target gdbserver binary. --gdb= Specify host gdb client binary. --target-arch= Specify NDK target arch. --adb= Specify host ADB binary. --device= ADB device serial to use (-s flag). --port= Specify the tcp port to use. --su-prefix= Prepend 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= 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=" 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= 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 '%%' 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 '%%'. 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 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 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 # 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 # 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 # 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) [[ "$DEVICE_FINGERPRINT" ]] || panic "Failed to get the device 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.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 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\/.*\.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 "Writing the device fingerprint" echo "$DEVICE_FINGERPRINT" > "$PULL_LIBS_DIR/build.fingerprint" 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' ':') SOLIB_DIRS=${SOLIB_DIRS%:} # Strip trailing : # 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 $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 # Select correct app_process for architecture. case $TARGET_ARCH in arm|x86|mips) GDBEXEC=app_process32;; arm64|x86_64) GDBEXEC=app_process64;; *) 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 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 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_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? # 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 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 # Generate a file containing useful GDB initialization commands readonly COMMANDS=$TMPDIR/gdb.init log "Generating GDB initialization commands file: $COMMANDS" cat > $COMMANDS < timeout_seconds: print("Error: unable to connect to device.") print(e) return False time.sleep(min(0.25, time_left)) target_remote_with_retry(':$HOST_PORT', 5) end EOF 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" if [ "$CGDB" ]; then $CGDB -d $GDB -- $GDB_ARGS -x $COMMANDS else $GDB $GDB_ARGS -x $COMMANDS fi rm -f "$GDBSERVER_PIDFILE"