1#!/bin/bash
2#
3# Copyright (c) 2012 The Chromium Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6#
7
8# A generic script used to attach to a running Chromium process and
9# debug it. Most users should not use this directly, but one of the
10# wrapper scripts like adb_gdb_content_shell
11#
12# Use --help to print full usage instructions.
13#
14
15PROGNAME=$(basename "$0")
16PROGDIR=$(dirname "$0")
17
18# Location of Chromium-top-level sources.
19CHROMIUM_SRC=$(cd "$PROGDIR"/../.. >/dev/null && pwd 2>/dev/null)
20
21# Location of Chromium out/ directory.
22if [ -z "$CHROMIUM_OUT_DIR" ]; then
23  CHROMIUM_OUT_DIR=out
24fi
25
26TMPDIR=
27GDBSERVER_PIDFILE=
28TARGET_GDBSERVER=
29
30clean_exit () {
31  if [ "$TMPDIR" ]; then
32    GDBSERVER_PID=$(cat $GDBSERVER_PIDFILE 2>/dev/null)
33    if [ "$GDBSERVER_PID" ]; then
34      log "Killing background gdbserver process: $GDBSERVER_PID"
35      kill -9 $GDBSERVER_PID >/dev/null 2>&1
36    fi
37    if [ "$TARGET_GDBSERVER" ]; then
38      log "Removing target gdbserver binary: $TARGET_GDBSERVER."
39      "$ADB" shell rm "$TARGET_GDBSERVER" >/dev/null 2>&1
40    fi
41    log "Cleaning up: $TMPDIR"
42    rm -rf "$TMPDIR"
43  fi
44  trap "" EXIT
45  exit $1
46}
47
48# Ensure clean exit on Ctrl-C or normal exit.
49trap "clean_exit 1" INT HUP QUIT TERM
50trap "clean_exit \$?" EXIT
51
52panic () {
53  echo "ERROR: $@" >&2
54  exit 1
55}
56
57fail_panic () {
58  if [ $? != 0 ]; then panic "$@"; fi
59}
60
61log () {
62  if [ "$VERBOSE" -gt 0 ]; then
63    echo "$@"
64  fi
65}
66
67DEFAULT_PULL_LIBS_DIR=/tmp/$USER-adb-gdb-libs
68
69# NOTE: Allow wrapper scripts to set various default through ADB_GDB_XXX
70# environment variables. This is only for cosmetic reasons, i.e. to
71# display proper
72
73# Allow wrapper scripts to set the default activity through
74# the ADB_GDB_ACTIVITY variable. Users are still able to change the
75# final activity name through --activity=<name> option.
76#
77# This is only for cosmetic reasons, i.e. to display the proper default
78# in the --help output.
79#
80DEFAULT_ACTIVITY=${ADB_GDB_ACTIVITY:-".Main"}
81
82# Allow wrapper scripts to set the program name through ADB_GDB_PROGNAME
83PROGNAME=${ADB_GDB_PROGNAME:-$(basename "$0")}
84
85ACTIVITY=$DEFAULT_ACTIVITY
86ADB=
87ANNOTATE=
88# Note: Ignore BUILDTYPE variable, because the Ninja build doesn't use it.
89BUILDTYPE=
90FORCE=
91GDBEXEPOSTFIX=gdb
92GDBINIT=
93GDBSERVER=
94HELP=
95NDK_DIR=
96NO_PULL_LIBS=
97PACKAGE_NAME=
98PID=
99PROGRAM_NAME="activity"
100PULL_LIBS=
101PULL_LIBS_DIR=
102SANDBOXED=
103SANDBOXED_INDEX=
104START=
105SU_PREFIX=
106SYMBOL_DIR=
107TARGET_ARCH=
108TOOLCHAIN=
109VERBOSE=0
110
111for opt; do
112  optarg=$(expr "x$opt" : 'x[^=]*=\(.*\)')
113  case $opt in
114    --adb=*)
115      ADB=$optarg
116      ;;
117    --activity=*)
118      ACTIVITY=$optarg
119      ;;
120    --annotate=3)
121      ANNOTATE=$optarg
122      ;;
123    --force)
124      FORCE=true
125      ;;
126    --gdbserver=*)
127      GDBSERVER=$optarg
128      ;;
129    --gdb=*)
130      GDB=$optarg
131      ;;
132    --help|-h|-?)
133      HELP=true
134      ;;
135    --ndk-dir=*)
136      NDK_DIR=$optarg
137      ;;
138    --no-pull-libs)
139      NO_PULL_LIBS=true
140      ;;
141    --package-name=*)
142      PACKAGE_NAME=$optarg
143      ;;
144    --pid=*)
145      PID=$optarg
146      ;;
147    --program-name=*)
148      PROGRAM_NAME=$optarg
149      ;;
150    --pull-libs)
151      PULL_LIBS=true
152      ;;
153    --pull-libs-dir=*)
154      PULL_LIBS_DIR=$optarg
155      ;;
156    --sandboxed)
157      SANDBOXED=true
158      ;;
159    --sandboxed=*)
160      SANDBOXED=true
161      SANDBOXED_INDEX=$optarg
162      ;;
163    --script=*)
164      GDBINIT=$optarg
165      ;;
166    --start)
167      START=true
168      ;;
169    --su-prefix=*)
170      SU_PREFIX=$optarg
171      ;;
172    --symbol-dir=*)
173      SYMBOL_DIR=$optarg
174      ;;
175    --out-dir=*)
176      CHROMIUM_OUT_DIR=$optarg
177      ;;
178    --target-arch=*)
179      TARGET_ARCH=$optarg
180      ;;
181    --toolchain=*)
182      TOOLCHAIN=$optarg
183      ;;
184    --ui)
185      GDBEXEPOSTFIX=gdbtui
186      ;;
187    --verbose)
188      VERBOSE=$(( $VERBOSE + 1 ))
189      ;;
190    --debug)
191      BUILDTYPE=Debug
192      ;;
193    --release)
194      BUILDTYPE=Release
195      ;;
196    -*)
197      panic "Unknown option $OPT, see --help." >&2
198      ;;
199    *)
200      if [ "$PACKAGE_NAME" ]; then
201        panic "You can only provide a single package name as argument!\
202 See --help."
203      fi
204      PACKAGE_NAME=$opt
205      ;;
206  esac
207done
208
209print_help_options () {
210  cat <<EOF
211EOF
212}
213
214if [ "$HELP" ]; then
215  if [ "$ADB_GDB_PROGNAME" ]; then
216    # Assume wrapper scripts all provide a default package name.
217    cat <<EOF
218Usage: $PROGNAME [options]
219
220Attach gdb to a running Android $PROGRAM_NAME process.
221EOF
222  else
223    # Assume this is a direct call to adb_gdb
224  cat <<EOF
225Usage: $PROGNAME [options] [<package-name>]
226
227Attach gdb to a running Android $PROGRAM_NAME process.
228
229If provided, <package-name> must be the name of the Android application's
230package name to be debugged. You can also use --package-name=<name> to
231specify it.
232EOF
233  fi
234
235  cat <<EOF
236
237This script is used to debug a running $PROGRAM_NAME process.
238This can be a regular Android application process, or a sandboxed
239service, if you use the --sandboxed or --sandboxed=<num> option.
240
241This script needs several things to work properly. It will try to pick
242them up automatically for you though:
243
244   - target gdbserver binary
245   - host gdb client (e.g. arm-linux-androideabi-gdb)
246   - directory with symbolic version of $PROGRAM_NAME's shared libraries.
247
248You can also use --ndk-dir=<path> to specify an alternative NDK installation
249directory.
250
251The script tries to find the most recent version of the debug version of
252shared libraries under one of the following directories:
253
254  \$CHROMIUM_SRC/<out>/Release/lib/           (used by Ninja builds)
255  \$CHROMIUM_SRC/<out>/Debug/lib/             (used by Ninja builds)
256  \$CHROMIUM_SRC/<out>/Release/lib.target/    (used by Make builds)
257  \$CHROMIUM_SRC/<out>/Debug/lib.target/      (used by Make builds)
258
259Where <out> is 'out' by default, unless the --out=<name> option is used or
260the CHROMIUM_OUT_DIR environment variable is defined.
261
262You can restrict this search by using --release or --debug to specify the
263build type, or simply use --symbol-dir=<path> to specify the file manually.
264
265The script tries to extract the target architecture from your GYP_DEFINES,
266but if this fails, will default to 'arm'. Use --target-arch=<name> to force
267its value.
268
269Otherwise, the script will complain, but you can use the --gdbserver,
270--gdb and --symbol-lib options to specify everything manually.
271
272An alternative to --gdb=<file> is to use --toollchain=<path> to specify
273the path to the host target-specific cross-toolchain.
274
275You will also need the 'adb' tool in your path. Otherwise, use the --adb
276option. The script will complain if there is more than one device connected
277and ANDROID_SERIAL is not defined.
278
279The first time you use it on a device, the script will pull many system
280libraries required by the process into a temporary directory. This
281is done to strongly improve the debugging experience, like allowing
282readable thread stacks and more. The libraries are copied to the following
283directory by default:
284
285  $DEFAULT_PULL_LIBS_DIR/
286
287But you can use the --pull-libs-dir=<path> option to specify an
288alternative. The script can detect when you change the connected device,
289and will re-pull the libraries only in this case. You can however force it
290with the --pull-libs option.
291
292Any local .gdbinit script will be ignored, but it is possible to pass a
293gdb command script with the --script=<file> option. Note that its commands
294will be passed to gdb after the remote connection and library symbol
295loading have completed.
296
297Valid options:
298  --help|-h|-?          Print this message.
299  --verbose             Increase verbosity.
300
301  --sandboxed           Debug first sandboxed process we find.
302  --sandboxed=<num>     Debug specific sandboxed process.
303  --symbol-dir=<path>   Specify directory with symbol shared libraries.
304  --out-dir=<path>      Specify the out directory.
305  --package-name=<name> Specify package name (alternative to 1st argument).
306  --program-name=<name> Specify program name (cosmetic only).
307  --pid=<pid>           Specify application process pid.
308  --force               Kill any previous debugging session, if any.
309  --start               Start package's activity on device.
310  --ui                  Use gdbtui instead of gdb
311  --activity=<name>     Activity name for --start [$DEFAULT_ACTIVITY].
312  --annotate=<num>      Enable gdb annotation.
313  --script=<file>       Specify extra GDB init script.
314
315  --gdbserver=<file>    Specify target gdbserver binary.
316  --gdb=<file>          Specify host gdb client binary.
317  --target-arch=<name>  Specify NDK target arch.
318  --adb=<file>          Specify host ADB binary.
319
320  --su-prefix=<prefix>  Prepend <prefix> to 'adb shell' commands that are
321                        run by this script. This can be useful to use
322                        the 'su' program on rooted production devices.
323                        e.g. --su-prefix="su -c"
324
325  --pull-libs           Force system libraries extraction.
326  --no-pull-libs        Do not extract any system library.
327  --libs-dir=<path>     Specify system libraries extraction directory.
328
329  --debug               Use libraries under out/Debug.
330  --release             Use libraries under out/Release.
331
332EOF
333  exit 0
334fi
335
336if [ -z "$PACKAGE_NAME" ]; then
337  panic "Please specify a package name on the command line. See --help."
338fi
339
340if [ -z "$NDK_DIR" ]; then
341  ANDROID_NDK_ROOT=$(PYTHONPATH=build/android python -c \
342'from pylib.constants import ANDROID_NDK_ROOT; print ANDROID_NDK_ROOT,')
343else
344  if [ ! -d "$NDK_DIR" ]; then
345    panic "Invalid directory: $NDK_DIR"
346  fi
347  if [ ! -f "$NDK_DIR/ndk-build" ]; then
348    panic "Not a valid NDK directory: $NDK_DIR"
349  fi
350  ANDROID_NDK_ROOT=$NDK_DIR
351fi
352
353if [ "$GDBINIT" -a ! -f "$GDBINIT" ]; then
354  panic "Unknown --script file: $GDBINIT"
355fi
356
357# Find the target architecture from our $GYP_DEFINES
358# This returns an NDK-compatible architecture name.
359# out: NDK Architecture name, or empty string.
360get_gyp_target_arch () {
361  local ARCH=$(echo $GYP_DEFINES | tr ' ' '\n' | grep '^target_arch=' |\
362               cut -d= -f2)
363  case $ARCH in
364    ia32|i?86|x86) echo "x86";;
365    mips|arm|arm64|x86_64) echo "$ARCH";;
366    *) echo "";
367  esac
368}
369
370if [ -z "$TARGET_ARCH" ]; then
371  TARGET_ARCH=$(get_gyp_target_arch)
372  if [ -z "$TARGET_ARCH" ]; then
373    TARGET_ARCH=arm
374  fi
375else
376  # Nit: accept Chromium's 'ia32' as a valid target architecture. This
377  # script prefers the NDK 'x86' name instead because it uses it to find
378  # NDK-specific files (host gdb) with it.
379  if [ "$TARGET_ARCH" = "ia32" ]; then
380    TARGET_ARCH=x86
381    log "Auto-config: --arch=$TARGET_ARCH  (equivalent to ia32)"
382  fi
383fi
384
385# Detect the NDK system name, i.e. the name used to identify the host.
386# out: NDK system name (e.g. 'linux' or 'darwin')
387get_ndk_host_system () {
388  local HOST_OS
389  if [ -z "$NDK_HOST_SYSTEM" ]; then
390    HOST_OS=$(uname -s)
391    case $HOST_OS in
392      Linux) NDK_HOST_SYSTEM=linux;;
393      Darwin) NDK_HOST_SYSTEM=darwin;;
394      *) panic "You can't run this script on this system: $HOST_OS";;
395    esac
396  fi
397  echo "$NDK_HOST_SYSTEM"
398}
399
400# Detect the NDK host architecture name.
401# out: NDK arch name (e.g. 'x86' or 'x86_64')
402get_ndk_host_arch () {
403  local HOST_ARCH HOST_OS
404  if [ -z "$NDK_HOST_ARCH" ]; then
405    HOST_OS=$(get_ndk_host_system)
406    HOST_ARCH=$(uname -p)
407    case $HOST_ARCH in
408      i?86) NDK_HOST_ARCH=x86;;
409      x86_64|amd64) NDK_HOST_ARCH=x86_64;;
410      *) panic "You can't run this script on this host architecture: $HOST_ARCH";;
411    esac
412    # Darwin trick: "uname -p" always returns i386 on 64-bit installations.
413    if [ "$HOST_OS" = darwin -a "$NDK_HOST_ARCH" = "x86" ]; then
414      # Use '/usr/bin/file', not just 'file' to avoid buggy MacPorts
415      # implementations of the tool. See http://b.android.com/53769
416      HOST_64BITS=$(/usr/bin/file -L "$SHELL" | grep -e "x86[_-]64")
417      if [ "$HOST_64BITS" ]; then
418        NDK_HOST_ARCH=x86_64
419      fi
420    fi
421  fi
422  echo "$NDK_HOST_ARCH"
423}
424
425# Convert an NDK architecture name into a GNU configure triplet.
426# $1: NDK architecture name (e.g. 'arm')
427# Out: Android GNU configure triplet (e.g. 'arm-linux-androideabi')
428get_arch_gnu_config () {
429  case $1 in
430    arm)
431      echo "arm-linux-androideabi"
432      ;;
433    arm64)
434      echo "aarch64-linux-android"
435      ;;
436    x86)
437      echo "i686-linux-android"
438      ;;
439    x86_64)
440      echo "x86_64-linux-android"
441      ;;
442    mips)
443      echo "mipsel-linux-android"
444      ;;
445    *)
446      echo "$ARCH-linux-android"
447      ;;
448  esac
449}
450
451# Convert an NDK architecture name into a toolchain name prefix
452# $1: NDK architecture name (e.g. 'arm')
453# Out: NDK toolchain name prefix (e.g. 'arm-linux-androideabi')
454get_arch_toolchain_prefix () {
455  # Return the configure triplet, except for x86!
456  if [ "$1" = "x86" ]; then
457    echo "$1"
458  else
459    get_arch_gnu_config $1
460  fi
461}
462
463# Find a NDK toolchain prebuilt file or sub-directory.
464# This will probe the various arch-specific toolchain directories
465# in the NDK for the needed file.
466# $1: NDK install path
467# $2: NDK architecture name
468# $3: prebuilt sub-path to look for.
469# Out: file path, or empty if none is found.
470get_ndk_toolchain_prebuilt () {
471  local NDK_DIR="${1%/}"
472  local ARCH="$2"
473  local SUBPATH="$3"
474  local NAME="$(get_arch_toolchain_prefix $ARCH)"
475  local FILE TARGET
476  FILE=$NDK_DIR/toolchains/$NAME-4.9/prebuilt/$SUBPATH
477  if [ ! -f "$FILE" ]; then
478    FILE=$NDK_DIR/toolchains/$NAME-4.8/prebuilt/$SUBPATH
479    if [ ! -f "$FILE" ]; then
480      FILE=
481    fi
482  fi
483  echo "$FILE"
484}
485
486# Find the path to an NDK's toolchain full prefix for a given architecture
487# $1: NDK install path
488# $2: NDK target architecture name
489# Out: install path + binary prefix (e.g.
490#      ".../path/to/bin/arm-linux-androideabi-")
491get_ndk_toolchain_fullprefix () {
492  local NDK_DIR="$1"
493  local ARCH="$2"
494  local TARGET NAME HOST_OS HOST_ARCH GCC CONFIG
495
496  # NOTE: This will need to be updated if the NDK changes the names or moves
497  #        the location of its prebuilt toolchains.
498  #
499  GCC=
500  HOST_OS=$(get_ndk_host_system)
501  HOST_ARCH=$(get_ndk_host_arch)
502  CONFIG=$(get_arch_gnu_config $ARCH)
503  GCC=$(get_ndk_toolchain_prebuilt \
504        "$NDK_DIR" "$ARCH" "$HOST_OS-$HOST_ARCH/bin/$CONFIG-gcc")
505  if [ -z "$GCC" -a "$HOST_ARCH" = "x86_64" ]; then
506    GCC=$(get_ndk_toolchain_prebuilt \
507          "$NDK_DIR" "$ARCH" "$HOST_OS-x86/bin/$CONFIG-gcc")
508  fi
509  if [ ! -f "$GCC" -a "$ARCH" = "x86" ]; then
510    # Special case, the x86 toolchain used to be incorrectly
511    # named i686-android-linux-gcc!
512    GCC=$(get_ndk_toolchain_prebuilt \
513          "$NDK_DIR" "$ARCH" "$HOST_OS-x86/bin/i686-android-linux-gcc")
514  fi
515  if [ -z "$GCC" ]; then
516    panic "Cannot find Android NDK toolchain for '$ARCH' architecture. \
517Please verify your NDK installation!"
518  fi
519  echo "${GCC%%gcc}"
520}
521
522# $1: NDK install path
523# $2: target architecture.
524get_ndk_gdbserver () {
525  local NDK_DIR="$1"
526  local ARCH=$2
527  local BINARY
528
529  # The location has moved after NDK r8
530  BINARY=$NDK_DIR/prebuilt/android-$ARCH/gdbserver/gdbserver
531  if [ ! -f "$BINARY" ]; then
532    BINARY=$(get_ndk_toolchain_prebuilt "$NDK_DIR" "$ARCH" gdbserver)
533  fi
534  echo "$BINARY"
535}
536
537# Check/probe the path to the Android toolchain installation. Always
538# use the NDK versions of gdb and gdbserver. They must match to avoid
539# issues when both binaries do not speak the same wire protocol.
540#
541if [ -z "$TOOLCHAIN" ]; then
542  ANDROID_TOOLCHAIN=$(get_ndk_toolchain_fullprefix \
543                      "$ANDROID_NDK_ROOT" "$TARGET_ARCH")
544  ANDROID_TOOLCHAIN=$(dirname "$ANDROID_TOOLCHAIN")
545  log "Auto-config: --toolchain=$ANDROID_TOOLCHAIN"
546else
547  # Be flexible, allow one to specify either the install path or the bin
548  # sub-directory in --toolchain:
549  #
550  if [ -d "$TOOLCHAIN/bin" ]; then
551    TOOLCHAIN=$TOOLCHAIN/bin
552  fi
553  ANDROID_TOOLCHAIN=$TOOLCHAIN
554fi
555
556# Cosmetic: Remove trailing directory separator.
557ANDROID_TOOLCHAIN=${ANDROID_TOOLCHAIN%/}
558
559# Find host GDB client binary
560if [ -z "$GDB" ]; then
561  GDB=$(which $ANDROID_TOOLCHAIN/*-$GDBEXEPOSTFIX 2>/dev/null | head -1)
562  if [ -z "$GDB" ]; then
563    panic "Can't find Android gdb client in your path, check your \
564--toolchain or --gdb path."
565  fi
566  log "Host gdb client: $GDB"
567fi
568
569# Find gdbserver binary, we will later push it to /data/local/tmp
570# This ensures that both gdbserver and $GDB talk the same binary protocol,
571# otherwise weird problems will appear.
572#
573if [ -z "$GDBSERVER" ]; then
574  GDBSERVER=$(get_ndk_gdbserver "$ANDROID_NDK_ROOT" "$TARGET_ARCH")
575  if [ -z "$GDBSERVER" ]; then
576    panic "Can't find NDK gdbserver binary. use --gdbserver to specify \
577valid one!"
578  fi
579  log "Auto-config: --gdbserver=$GDBSERVER"
580fi
581
582
583
584# Check that ADB is in our path
585if [ -z "$ADB" ]; then
586  ADB=$(which adb 2>/dev/null)
587  if [ -z "$ADB" ]; then
588    panic "Can't find 'adb' tool in your path. Install it or use \
589--adb=<file>"
590  fi
591  log "Auto-config: --adb=$ADB"
592fi
593
594# Check that it works minimally
595ADB_VERSION=$($ADB version 2>/dev/null)
596echo "$ADB_VERSION" | fgrep -q -e "Android Debug Bridge"
597if [ $? != 0 ]; then
598  panic "Your 'adb' tool seems invalid, use --adb=<file> to specify a \
599different one: $ADB"
600fi
601
602# If there are more than one device connected, and ANDROID_SERIAL is not
603# defined, print an error message.
604NUM_DEVICES_PLUS2=$($ADB devices 2>/dev/null | wc -l)
605if [ "$NUM_DEVICES_PLUS2" -lt 3 -a -z "$ANDROID_SERIAL" ]; then
606  echo "ERROR: There is more than one Android device connected to ADB."
607  echo "Please define ANDROID_SERIAL to specify which one to use."
608  exit 1
609fi
610
611# A unique ID for this script's session. This needs to be the same in all
612# sub-shell commands we're going to launch, so take the PID of the launcher
613# process.
614TMP_ID=$$
615
616# Temporary directory, will get cleaned up on exit.
617TMPDIR=/tmp/$USER-adb-gdb-tmp-$TMP_ID
618mkdir -p "$TMPDIR" && rm -rf "$TMPDIR"/*
619
620GDBSERVER_PIDFILE="$TMPDIR"/gdbserver-$TMP_ID.pid
621
622# Run a command through adb shell, strip the extra \r from the output
623# and return the correct status code to detect failures. This assumes
624# that the adb shell command prints a final \n to stdout.
625# $1+: command to run
626# Out: command's stdout
627# Return: command's status
628# Note: the command's stderr is lost
629adb_shell () {
630  local TMPOUT="$(mktemp)"
631  local LASTLINE RET
632  local ADB=${ADB:-adb}
633
634  # The weird sed rule is to strip the final \r on each output line
635  # Since 'adb shell' never returns the command's proper exit/status code,
636  # we force it to print it as '%%<status>' in the temporary output file,
637  # which we will later strip from it.
638  $ADB shell $@ ";" echo "%%\$?" 2>/dev/null | \
639      sed -e 's![[:cntrl:]]!!g' > $TMPOUT
640  # Get last line in log, which contains the exit code from the command
641  LASTLINE=$(sed -e '$!d' $TMPOUT)
642  # Extract the status code from the end of the line, which must
643  # be '%%<code>'.
644  RET=$(echo "$LASTLINE" | \
645    awk '{ if (match($0, "%%[0-9]+$")) { print substr($0,RSTART+2); } }')
646  # Remove the status code from the last line. Note that this may result
647  # in an empty line.
648  LASTLINE=$(echo "$LASTLINE" | \
649    awk '{ if (match($0, "%%[0-9]+$")) { print substr($0,1,RSTART-1); } }')
650  # The output itself: all lines except the status code.
651  sed -e '$d' $TMPOUT && printf "%s" "$LASTLINE"
652  # Remove temp file.
653  rm -f $TMPOUT
654  # Exit with the appropriate status.
655  return $RET
656}
657
658# If --force is specified, try to kill any gdbserver process started by the
659# same user on the device. Normally, these are killed automatically by the
660# script on exit, but there are a few corner cases where this would still
661# be needed.
662if [ "$FORCE" ]; then
663  GDBSERVER_PIDS=$(adb_shell ps | awk '$9 ~ /gdbserver/ { print $2; }')
664  for GDB_PID in $GDBSERVER_PIDS; do
665    log "Killing previous gdbserver (PID=$GDB_PID)"
666    adb_shell kill -9 $GDB_PID
667  done
668fi
669
670if [ "$START" ]; then
671  log "Starting $PROGRAM_NAME on device."
672  adb_shell am start -n $PACKAGE_NAME/$ACTIVITY 2>/dev/null
673  adb_shell ps | grep -q $PACKAGE_NAME
674  fail_panic "Could not start $PROGRAM_NAME on device. Are you sure the \
675package is installed?"
676fi
677
678# Return the timestamp of a given time, as number of seconds since epoch.
679# $1: file path
680# Out: file timestamp
681get_file_timestamp () {
682  stat -c %Y "$1" 2>/dev/null
683}
684
685# Detect the build type and symbol directory. This is done by finding
686# the most recent sub-directory containing debug shared libraries under
687# $CHROMIUM_SRC/$CHROMIUM_OUT_DIR/
688#
689# $1: $BUILDTYPE value, can be empty
690# Out: nothing, but this sets SYMBOL_DIR
691#
692detect_symbol_dir () {
693  local SUBDIRS SUBDIR LIST DIR DIR_LIBS TSTAMP
694  # Note: Ninja places debug libraries under out/$BUILDTYPE/lib/, while
695  # Make places then under out/$BUILDTYPE/lib.target.
696  if [ "$1" ]; then
697    SUBDIRS="$1/lib $1/lib.target"
698  else
699    SUBDIRS="Release/lib Debug/lib Release/lib.target Debug/lib.target"
700  fi
701  LIST=$TMPDIR/scan-subdirs-$$.txt
702  printf "" > "$LIST"
703  for SUBDIR in $SUBDIRS; do
704    DIR=$CHROMIUM_SRC/$CHROMIUM_OUT_DIR/$SUBDIR
705    if [ -d "$DIR" ]; then
706      # Ignore build directories that don't contain symbol versions
707      # of the shared libraries.
708      DIR_LIBS=$(ls "$DIR"/lib*.so 2>/dev/null)
709      if [ -z "$DIR_LIBS" ]; then
710        echo "No shared libs: $DIR"
711        continue
712      fi
713      TSTAMP=$(get_file_timestamp "$DIR")
714      printf "%s %s\n" "$TSTAMP" "$SUBDIR" >> "$LIST"
715    fi
716  done
717  SUBDIR=$(cat $LIST | sort -r | head -1 | cut -d" " -f2)
718  rm -f "$LIST"
719
720  if [ -z "$SUBDIR" ]; then
721    if [ -z "$1" ]; then
722      panic "Could not find any build directory under \
723$CHROMIUM_SRC/$CHROMIUM_OUT_DIR. Please build the program first!"
724    else
725      panic "Could not find any $1 directory under \
726$CHROMIUM_SRC/$CHROMIUM_OUT_DIR. Check your build type!"
727    fi
728  fi
729
730  SYMBOL_DIR=$CHROMIUM_SRC/$CHROMIUM_OUT_DIR/$SUBDIR
731  log "Auto-config: --symbol-dir=$SYMBOL_DIR"
732}
733
734if [ -z "$SYMBOL_DIR" ]; then
735  detect_symbol_dir "$BUILDTYPE"
736fi
737
738# Allow several concurrent debugging sessions
739TARGET_GDBSERVER=/data/local/tmp/gdbserver-adb-gdb-$TMP_ID
740
741# Return the build fingerprint contained in a build.prop file.
742# $1: path to build.prop file
743get_build_fingerprint_from () {
744  cat "$1" | grep -e '^ro.build.fingerprint=' | cut -d= -f2
745}
746
747
748ORG_PULL_LIBS_DIR=$PULL_LIBS_DIR
749PULL_LIBS_DIR=${PULL_LIBS_DIR:-$DEFAULT_PULL_LIBS_DIR}
750
751HOST_FINGERPRINT=
752DEVICE_FINGERPRINT=$(adb_shell getprop ro.build.fingerprint)
753log "Device build fingerprint: $DEVICE_FINGERPRINT"
754
755# If --pull-libs-dir is not specified, and this is a platform build, look
756# if we can use the symbolic libraries under $ANDROID_PRODUCT_OUT/symbols/
757# directly, if the build fingerprint matches the device.
758if [ -z "$ORG_PULL_LIBS_DIR" -a \
759     "$ANDROID_PRODUCT_OUT" -a \
760     -f "$ANDROID_PRODUCT_OUT/system/build.prop" ]; then
761  ANDROID_FINGERPRINT=$(get_build_fingerprint_from \
762                        "$ANDROID_PRODUCT_OUT"/system/build.prop)
763  log "Android build fingerprint:  $ANDROID_FINGERPRINT"
764  if [ "$ANDROID_FINGERPRINT" = "$DEVICE_FINGERPRINT" ]; then
765    log "Perfect match!"
766    PULL_LIBS_DIR=$ANDROID_PRODUCT_OUT/symbols
767    HOST_FINGERPRINT=$ANDROID_FINGERPRINT
768    if [ "$PULL_LIBS" ]; then
769      log "Ignoring --pull-libs since the device and platform build \
770fingerprints match."
771      NO_PULL_LIBS=true
772    fi
773  fi
774fi
775
776# If neither --pull-libs an --no-pull-libs were specified, check the build
777# fingerprints of the device, and the cached system libraries on the host.
778#
779if [ -z "$NO_PULL_LIBS" -a -z "$PULL_LIBS" ]; then
780  if [ ! -f "$PULL_LIBS_DIR/build.prop" ]; then
781    log "Auto-config: --pull-libs  (no cached libraries)"
782    PULL_LIBS=true
783  else
784    HOST_FINGERPRINT=$(get_build_fingerprint_from "$PULL_LIBS_DIR/build.prop")
785    log "Host build fingerprint:   $HOST_FINGERPRINT"
786    if [ "$HOST_FINGERPRINT" == "$DEVICE_FINGERPRINT" ]; then
787      log "Auto-config: --no-pull-libs (fingerprint match)"
788      NO_PULL_LIBS=true
789    else
790      log "Auto-config: --pull-libs  (fingerprint mismatch)"
791      PULL_LIBS=true
792    fi
793  fi
794fi
795
796# Extract the system libraries from the device if necessary.
797if [ "$PULL_LIBS" -a -z "$NO_PULL_LIBS" ]; then
798  echo "Extracting system libraries into: $PULL_LIBS_DIR"
799fi
800
801mkdir -p "$PULL_LIBS_DIR"
802fail_panic "Can't create --libs-dir directory: $PULL_LIBS_DIR"
803
804# If requested, work for M-x gdb.  The gdb indirections make it
805# difficult to pass --annotate=3 to the gdb binary itself.
806GDB_ARGS=
807if [ "$ANNOTATE" ]; then
808  GDB_ARGS=$GDB_ARGS" --annotate=$ANNOTATE"
809fi
810
811# Get the PID from the first argument or else find the PID of the
812# browser process.
813if [ -z "$PID" ]; then
814  PROCESSNAME=$PACKAGE_NAME
815  if [ "$SANDBOXED_INDEX" ]; then
816    PROCESSNAME=$PROCESSNAME:sandboxed_process$SANDBOXED_INDEX
817  elif [ "$SANDBOXED" ]; then
818    PROCESSNAME=$PROCESSNAME:sandboxed_process
819    PID=$(adb_shell ps | \
820          awk '$9 ~ /^'$PROCESSNAME'/ { print $2; }' | head -1)
821  fi
822  if [ -z "$PID" ]; then
823    PID=$(adb_shell ps | \
824          awk '$9 == "'$PROCESSNAME'" { print $2; }' | head -1)
825  fi
826  if [ -z "$PID" ]; then
827    if [ "$START" ]; then
828      panic "Can't find application process PID, did it crash?"
829    else
830      panic "Can't find application process PID, are you sure it is \
831running? Try using --start."
832    fi
833  fi
834  log "Found process PID: $PID"
835elif [ "$SANDBOXED" ]; then
836  echo "WARNING: --sandboxed option ignored due to use of --pid."
837fi
838
839# Determine if 'adb shell' runs as root or not.
840# If so, we can launch gdbserver directly, otherwise, we have to
841# use run-as $PACKAGE_NAME ..., which requires the package to be debuggable.
842#
843if [ "$SU_PREFIX" ]; then
844  # Need to check that this works properly.
845  SU_PREFIX_TEST_LOG=$TMPDIR/su-prefix.log
846  adb_shell $SU_PREFIX echo "foo" > $SU_PREFIX_TEST_LOG 2>&1
847  if [ $? != 0 -o "$(cat $SU_PREFIX_TEST_LOG)" != "foo" ]; then
848    echo "ERROR: Cannot use '$SU_PREFIX' as a valid su prefix:"
849    echo "$ adb shell $SU_PREFIX echo foo"
850    cat $SU_PREFIX_TEST_LOG
851    exit 1
852  fi
853  COMMAND_PREFIX="$SU_PREFIX"
854else
855  SHELL_UID=$(adb shell cat /proc/self/status | \
856              awk '$1 == "Uid:" { print $2; }')
857  log "Shell UID: $SHELL_UID"
858  if [ "$SHELL_UID" != 0 -o -n "$NO_ROOT" ]; then
859    COMMAND_PREFIX="run-as $PACKAGE_NAME"
860  else
861    COMMAND_PREFIX=
862  fi
863fi
864log "Command prefix: '$COMMAND_PREFIX'"
865
866# Pull device's system libraries that are mapped by our process.
867# Pulling all system libraries is too long, so determine which ones
868# we need by looking at /proc/$PID/maps instead
869if [ "$PULL_LIBS" -a -z "$NO_PULL_LIBS" ]; then
870  echo "Extracting system libraries into: $PULL_LIBS_DIR"
871  rm -f $PULL_LIBS_DIR/build.prop
872  MAPPINGS=$(adb_shell $COMMAND_PREFIX cat /proc/$PID/maps)
873  if [ $? != 0 ]; then
874    echo "ERROR: Could not list process's memory mappings."
875    if [ "$SU_PREFIX" ]; then
876      panic "Are you sure your --su-prefix is correct?"
877    else
878      panic "Use --su-prefix if the application is not debuggable."
879    fi
880  fi
881  SYSTEM_LIBS=$(echo "$MAPPINGS" | \
882      awk '$6 ~ /\/system\/.*\.so$/ { print $6; }' | sort -u)
883  for SYSLIB in /system/bin/linker $SYSTEM_LIBS; do
884    echo "Pulling from device: $SYSLIB"
885    DST_FILE=$PULL_LIBS_DIR$SYSLIB
886    DST_DIR=$(dirname "$DST_FILE")
887    mkdir -p "$DST_DIR" && adb pull $SYSLIB "$DST_FILE" 2>/dev/null
888    fail_panic "Could not pull $SYSLIB from device !?"
889  done
890  echo "Pulling device build.prop"
891  adb pull /system/build.prop $PULL_LIBS_DIR/build.prop
892  fail_panic "Could not pull device build.prop !?"
893fi
894
895# Find all the sub-directories of $PULL_LIBS_DIR, up to depth 4
896# so we can add them to solib-search-path later.
897SOLIB_DIRS=$(find $PULL_LIBS_DIR -mindepth 1 -maxdepth 4 -type d | \
898             grep -v "^$" | tr '\n' ':')
899
900# This is a re-implementation of gdbclient, where we use compatible
901# versions of gdbserver and $GDBNAME to ensure that everything works
902# properly.
903#
904
905# Push gdbserver to the device
906log "Pushing gdbserver $GDBSERVER to $TARGET_GDBSERVER"
907adb push $GDBSERVER $TARGET_GDBSERVER &>/dev/null
908fail_panic "Could not copy gdbserver to the device!"
909
910PORT=5039
911HOST_PORT=$PORT
912TARGET_PORT=$PORT
913
914# Select correct app_process for architecture.
915case $TARGET_ARCH in
916      arm|x86|mips) GDBEXEC=app_process;;
917      arm64|x86_64) GDBEXEC=app_process64;;
918      *) fail_panic "Unknown app_process for architecture!";;
919esac
920
921# Detect AddressSanitizer setup on the device. In that case app_process is a
922# script, and the real executable is app_process.real.
923GDBEXEC_ASAN=app_process.real
924adb_shell ls /system/bin/$GDBEXEC_ASAN
925if [ $? == 0 ]; then
926    GDBEXEC=$GDBEXEC_ASAN
927fi
928
929# Pull the app_process binary from the device.
930log "Pulling $GDBEXEC from device"
931adb pull /system/bin/$GDBEXEC "$TMPDIR"/$GDBEXEC &>/dev/null
932fail_panic "Could not retrieve $GDBEXEC from the device!"
933
934# Setup network redirection
935log "Setting network redirection (host:$HOST_PORT -> device:$TARGET_PORT)"
936adb forward tcp:$HOST_PORT tcp:$TARGET_PORT
937fail_panic "Could not setup network redirection from \
938host:localhost:$HOST_PORT to device:localhost:$TARGET_PORT!"
939
940# Start gdbserver in the background
941# Note that using run-as requires the package to be debuggable.
942#
943# If not, this will fail horribly. The alternative is to run the
944# program as root, which requires of course root privileges.
945# Maybe we should add a --root option to enable this?
946#
947log "Starting gdbserver in the background:"
948GDBSERVER_LOG=$TMPDIR/gdbserver-$TMP_ID.log
949log "adb shell $COMMAND_PREFIX $TARGET_GDBSERVER :$TARGET_PORT \
950--attach $PID"
951("$ADB" shell $COMMAND_PREFIX $TARGET_GDBSERVER :$TARGET_PORT \
952 --attach $PID > $GDBSERVER_LOG 2>&1) &
953GDBSERVER_PID=$!
954echo "$GDBSERVER_PID" > $GDBSERVER_PIDFILE
955log "background job pid: $GDBSERVER_PID"
956
957# Check that it is still running after a few seconds. If not, this means we
958# could not properly attach to it
959sleep 2
960log "Job control: $(jobs -l)"
961STATE=$(jobs -l | awk '$2 == "'$GDBSERVER_PID'" { print $3; }')
962if [ "$STATE" != "Running" ]; then
963  echo "ERROR: GDBServer could not attach to PID $PID!"
964  if [ $(adb_shell su -c getenforce) != "Permissive" ];  then
965    echo "Device mode is Enforcing. Changing Device mode to Permissive "
966    $(adb_shell su -c setenforce 0)
967    if [ $(adb_shell su -c getenforce) != "Permissive" ]; then
968      echo "ERROR: Failed to Change Device mode to Permissive"
969      echo "Failure log (use --verbose for more information):"
970      cat $GDBSERVER_LOG
971      exit 1
972    fi
973  else
974    echo "Failure log (use --verbose for more information):"
975    cat $GDBSERVER_LOG
976    exit 1
977  fi
978fi
979
980# Generate a file containing useful GDB initialization commands
981readonly COMMANDS=$TMPDIR/gdb.init
982log "Generating GDB initialization commands file: $COMMANDS"
983echo -n "" > $COMMANDS
984echo "set print pretty 1" >> $COMMANDS
985echo "python" >> $COMMANDS
986echo "import sys" >> $COMMANDS
987echo "sys.path.insert(0, '$CHROMIUM_SRC/tools/gdb/')" >> $COMMANDS
988echo "try:" >> $COMMANDS
989echo "  import gdb_chrome" >> $COMMANDS
990echo "finally:" >> $COMMANDS
991echo "  sys.path.pop(0)" >> $COMMANDS
992echo "end" >> $COMMANDS
993echo "file $TMPDIR/$GDBEXEC" >> $COMMANDS
994echo "directory $CHROMIUM_SRC" >> $COMMANDS
995echo "set solib-absolute-prefix $PULL_LIBS_DIR" >> $COMMANDS
996echo "set solib-search-path $SOLIB_DIRS:$PULL_LIBS_DIR:$SYMBOL_DIR" \
997    >> $COMMANDS
998echo "echo Attaching and reading symbols, this may take a while.." \
999    >> $COMMANDS
1000echo "target remote :$HOST_PORT" >> $COMMANDS
1001
1002if [ "$GDBINIT" ]; then
1003  cat "$GDBINIT" >> $COMMANDS
1004fi
1005
1006if [ "$VERBOSE" -gt 0 ]; then
1007  echo "### START $COMMANDS"
1008  cat $COMMANDS
1009  echo "### END $COMMANDS"
1010fi
1011
1012log "Launching gdb client: $GDB $GDB_ARGS -x $COMMANDS"
1013$GDB $GDB_ARGS -x $COMMANDS &&
1014rm -f "$GDBSERVER_PIDFILE"
1015