1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000 |
- #!/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/+/main/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
|