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