1001 строка
30 KiB
Bash
Executable File
1001 строка
30 KiB
Bash
Executable File
#!/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 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
|
|
|
|
# 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\/.*\.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
|