1 #!/bin/sh 2 # Copyright (c) 2012 Google Inc. 3 # All rights reserved. 4 # 5 # Redistribution and use in source and binary forms, with or without 6 # modification, are permitted provided that the following conditions are 7 # met: 8 # 9 # * Redistributions of source code must retain the above copyright 10 # notice, this list of conditions and the following disclaimer. 11 # * Redistributions in binary form must reproduce the above 12 # copyright notice, this list of conditions and the following disclaimer 13 # in the documentation and/or other materials provided with the 14 # distribution. 15 # * Neither the name of Google Inc. nor the names of its 16 # contributors may be used to endorse or promote products derived from 17 # this software without specific prior written permission. 18 # 19 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 31 # Sanitize the environment 32 export LANG=C 33 export LC_ALL=C 34 35 if [ "$BASH_VERSION" ]; then 36 set -o posix 37 fi 38 39 PROGDIR=$(dirname "$0") 40 PROGDIR=$(cd "$PROGDIR" && pwd) 41 PROGNAME=$(basename "$0") 42 43 . $PROGDIR/common-functions.sh 44 45 DEFAULT_ABI="armeabi" 46 VALID_ABIS="armeabi armeabi-v7a x86 mips" 47 48 ABI= 49 ADB= 50 ALL_TESTS= 51 ENABLE_M32= 52 HELP= 53 HELP_ALL= 54 NDK_DIR= 55 NO_CLEANUP= 56 NO_DEVICE= 57 NUM_JOBS=$(get_core_count) 58 TMPDIR= 59 60 for opt do 61 # The following extracts the value if the option is like --name=<value>. 62 optarg=$(expr -- $opt : '^--[^=]*=\(.*\)$') 63 case $opt in 64 --abi=*) ABI=$optarg;; 65 --adb=*) ADB=$optarg;; 66 --all-tests) ALL_TESTS=true;; 67 --enable-m32) ENABLE_M32=true;; 68 --help|-h|-?) HELP=TRUE;; 69 --help-all) HELP_ALL=true;; 70 --jobs=*) NUM_JOBS=$optarg;; 71 --ndk-dir=*) NDK_DIR=$optarg;; 72 --tmp-dir=*) TMPDIR=$optarg;; 73 --no-cleanup) NO_CLEANUP=true;; 74 --no-device) NO_DEVICE=true;; 75 --quiet) decrease_verbosity;; 76 --verbose) increase_verbosity;; 77 -*) panic "Invalid option '$opt', see --help for details.";; 78 *) panic "This script doesn't take any parameters. See --help for details." 79 ;; 80 esac 81 done 82 83 if [ "$HELP" -o "$HELP_ALL" ]; then 84 echo "\ 85 Usage: $PROGNAME [options] 86 87 This script is used to check that your Google Breakpad source tree can 88 be properly built for Android, and that the client library and host tools 89 work properly together. 90 " 91 if [ "$HELP_ALL" ]; then 92 echo "\ 93 In more details, this script will: 94 95 - Rebuild the host version of Google Breakpad in a temporary 96 directory (with the Auto-tools based build system). 97 98 - Rebuild the Android client library with the Google Breakpad build 99 system (using autotools/configure). This requires that you define 100 ANDROID_NDK_ROOT in your environment to point to a valid Android NDK 101 installation directory, or use the --ndk-dir=<path> option. 102 103 - Rebuild the Android client library and a test crashing program with the 104 Android NDK build system (ndk-build). 105 106 - Require an Android device connected to your machine, and the 'adb' 107 tool in your path. They are used to: 108 109 - Install and run a test crashing program. 110 - Extract the corresponding minidump from the device. 111 - Dump the symbols from the test program on the host with 'dump_syms' 112 - Generate a stack trace with 'minidump_stackwalk' 113 - Check the stack trace content for valid source file locations. 114 115 You can however skip this requirement and only test the builds by using 116 the --no-device flag. 117 118 By default, all generated files will be created in a temporary directory 119 that is removed when the script completion. If you want to inspect the 120 files, use the --no-cleanup option. 121 122 Finally, use --verbose to increase the verbosity level, this will help 123 you see which exact commands are being issues and their result. Use the 124 flag twice for even more output. Use --quiet to decrease verbosity 125 instead and run the script silently. 126 127 If you have a device connected, the script will probe it to determine 128 its primary CPU ABI, and build the test program for it. You can however 129 use the --abi=<name> option to override this (this can be useful to check 130 the secondary ABI, e.g. using --abi=armeabi to check that such a program 131 works correctly on an ARMv7-A device). 132 133 If you don't have a device connected, the test program will be built (but 134 not run) with the default '$DEFAULT_ABI' ABI. Again, you can use 135 --abi=<name> to override this. Valid ABI names are: 136 137 $VALID_ABIS 138 139 The script will only run the client library unit test on the device 140 by default. You can use --all-tests to also build and run the unit 141 tests for the Breakpad tools and processor, but be warned that this 142 adds several minutes of testing time. --all-tests will also run the 143 host unit tests suite. 144 " 145 146 fi # HELP_ALL 147 148 echo "\ 149 Valid options: 150 151 --help|-h|-? Display this message. 152 --help-all Display extended help. 153 --enable-m32 Build 32-bit version of host tools. 154 --abi=<name> Specify target CPU ABI [auto-detected]. 155 --jobs=<count> Run <count> build tasks in parallel [$NUM_JOBS]. 156 --ndk-dir=<path> Specify NDK installation directory. 157 --tmp-dir=<path> Specify temporary directory (will be wiped-out). 158 --adb=<path> Specify adb program path. 159 --no-cleanup Don't remove temporary directory after completion. 160 --no-device Do not try to detect devices, nor run crash test. 161 --all-tests Run all unit tests (i.e. tools and processor ones too). 162 --verbose Increase verbosity. 163 --quiet Decrease verbosity." 164 165 exit 0 166 fi 167 168 TESTAPP_DIR=$PROGDIR/sample_app 169 170 # Select NDK install directory. 171 if [ -z "$NDK_DIR" ]; then 172 if [ -z "$ANDROID_NDK_ROOT" ]; then 173 panic "Please define ANDROID_NDK_ROOT in your environment, or use \ 174 --ndk-dir=<path>." 175 fi 176 NDK_DIR="$ANDROID_NDK_ROOT" 177 log "Found NDK directory: $NDK_DIR" 178 else 179 log "Using NDK directory: $NDK_DIR" 180 fi 181 # Small sanity check. 182 NDK_BUILD="$NDK_DIR/ndk-build" 183 if [ ! -f "$NDK_BUILD" ]; then 184 panic "Your NDK directory is not valid (missing ndk-build): $NDK_DIR" 185 fi 186 187 # Ensure the temporary directory is deleted on exit, except if the --no-cleanup 188 # option is used. 189 190 clean_tmpdir () { 191 if [ "$TMPDIR" ]; then 192 if [ -z "$NO_CLEANUP" ]; then 193 log "Cleaning up: $TMPDIR" 194 rm -rf "$TMPDIR" 195 else 196 dump "Temporary directory contents preserved: $TMPDIR" 197 fi 198 fi 199 exit "$@" 200 } 201 202 atexit clean_tmpdir 203 204 # If --tmp-dir=<path> is not used, create a temporary directory. 205 # Otherwise, start by cleaning up the user-provided path. 206 if [ -z "$TMPDIR" ]; then 207 TMPDIR=$(mktemp -d /tmp/$PROGNAME.XXXXXXXX) 208 fail_panic "Can't create temporary directory!" 209 log "Using temporary directory: $TMPDIR" 210 else 211 if [ ! -d "$TMPDIR" ]; then 212 mkdir -p "$TMPDIR" 213 fail_panic "Can't create temporary directory: $TMPDIR" 214 else 215 log "Cleaning up temporary directory: $TMPDIR" 216 rm -rf "$TMPDIR"/* 217 fail_panic "Cannot cleanup temporary directory!" 218 fi 219 fi 220 221 if [ -z "$NO_DEVICE" ]; then 222 if ! adb_check_device $ADB; then 223 echo "$(adb_get_error)" 224 echo "Use --no-device to build the code without running any tests." 225 exit 1 226 fi 227 fi 228 229 BUILD_LOG="$TMPDIR/build.log" 230 RUN_LOG="$TMPDIR/run.log" 231 CRASH_LOG="$TMPDIR/crash.log" 232 233 set_run_log "$RUN_LOG" 234 235 TMPHOST="$TMPDIR/host-local" 236 237 cd "$TMPDIR" 238 239 # Build host version of the tools 240 dump "Building host binaries." 241 CONFIGURE_FLAGS= 242 if [ "$ENABLE_M32" ]; then 243 CONFIGURE_FLAGS="$CONFIGURE_FLAGS --enable-m32" 244 fi 245 ( 246 run mkdir "$TMPDIR/build-host" && 247 run cd "$TMPDIR/build-host" && 248 run2 "$PROGDIR/../configure" --prefix="$TMPHOST" $CONFIGURE_FLAGS && 249 run2 make -j$NUM_JOBS install 250 ) 251 fail_panic "Can't build host binaries!" 252 253 if [ "$ALL_TESTS" ]; then 254 dump "Running host unit tests." 255 ( 256 run cd "$TMPDIR/build-host" && 257 run2 make -j$NUM_JOBS check 258 ) 259 fail_panic "Host unit tests failed!!" 260 fi 261 262 TMPBIN=$TMPHOST/bin 263 264 # Generate a stand-alone NDK toolchain 265 266 # Extract CPU ABI and architecture from device, if any. 267 if adb_check_device; then 268 DEVICE_ABI=$(adb_shell getprop ro.product.cpu.abi) 269 DEVICE_ABI2=$(adb_shell getprop ro.product.cpu.abi2) 270 if [ -z "$DEVICE_ABI" ]; then 271 panic "Can't extract ABI from connected device!" 272 fi 273 if [ "$DEVICE_ABI2" ]; then 274 dump "Found device ABIs: $DEVICE_ABI $DEVICE_ABI2" 275 else 276 dump "Found device ABI: $DEVICE_ABI" 277 DEVICE_ABI2=$DEVICE_ABI 278 fi 279 280 # If --abi=<name> is used, check that the device supports it. 281 if [ "$ABI" -a "$DEVICE_ABI" != "$ABI" -a "$DEVICE_ABI2" != "$ABI" ]; then 282 dump "ERROR: Device ABI(s) do not match --abi command-line value ($ABI)!" 283 panic "Please use --no-device to skip device tests." 284 fi 285 286 if [ -z "$ABI" ]; then 287 ABI=$DEVICE_ABI 288 dump "Using CPU ABI: $ABI (device)" 289 else 290 dump "Using CPU ABI: $ABI (command-line)" 291 fi 292 else 293 if [ -z "$ABI" ]; then 294 # No device connected, choose default ABI 295 ABI=$DEFAULT_ABI 296 dump "Using CPU ABI: $ABI (default)" 297 else 298 dump "Using CPU ABI: $ABI (command-line)" 299 fi 300 fi 301 302 # Check the ABI value 303 VALID= 304 for VALID_ABI in $VALID_ABIS; do 305 if [ "$ABI" = "$VALID_ABI" ]; then 306 VALID=true 307 break 308 fi 309 done 310 311 if [ -z "$VALID" ]; then 312 panic "Unknown CPU ABI '$ABI'. Valid values are: $VALID_ABIS" 313 fi 314 315 # Extract architecture name from ABI 316 case $ABI in 317 armeabi*) ARCH=arm;; 318 *) ARCH=$ABI;; 319 esac 320 321 # Extract GNU configuration name 322 case $ARCH in 323 arm) 324 GNU_CONFIG=arm-linux-androideabi 325 ;; 326 x86) 327 GNU_CONFIG=i686-linux-android 328 ;; 329 mips) 330 GNU_CONFIG=mipsel-linux-android 331 ;; 332 *) 333 GNU_CONFIG="$ARCH-linux-android" 334 ;; 335 esac 336 337 # Generate standalone NDK toolchain installation 338 NDK_STANDALONE="$TMPDIR/ndk-$ARCH-toolchain" 339 echo "Generating NDK standalone toolchain installation" 340 mkdir -p "$NDK_STANDALONE" 341 # NOTE: The --platform=android-9 is required to provide <regex.h> for GTest. 342 run "$NDK_DIR/build/tools/make-standalone-toolchain.sh" \ 343 --arch="$ARCH" \ 344 --platform=android-9 \ 345 --install-dir="$NDK_STANDALONE" 346 fail_panic "Can't generate standalone NDK toolchain installation!" 347 348 # Rebuild the client library, processor and tools with the auto-tools based 349 # build system. Even though it's not going to be used, this checks that this 350 # still works correctly. 351 echo "Building full Android binaries with configure/make" 352 TMPTARGET="$TMPDIR/target-local" 353 ( 354 PATH="$NDK_STANDALONE/bin:$PATH" 355 run mkdir "$TMPTARGET" && 356 run mkdir "$TMPDIR"/build-target && 357 run cd "$TMPDIR"/build-target && 358 run2 "$PROGDIR"/../configure --prefix="$TMPTARGET" \ 359 --host="$GNU_CONFIG" && 360 run2 make -j$NUM_JOBS install 361 ) 362 fail_panic "Could not rebuild Android binaries!" 363 364 # Build and/or run unit test suite. 365 # If --no-device is used, only rebuild it, otherwise, run in on the 366 # connected device. 367 if [ "$NO_DEVICE" ]; then 368 ACTION="Building" 369 # This is a trick to force the Makefile to ignore running the scripts. 370 TESTS_ENVIRONMENT="TESTS_ENVIRONMENT=true" 371 else 372 ACTION="Running" 373 TESTS_ENVIRONMENT= 374 fi 375 376 ( 377 PATH="$NDK_STANDALONE/bin:$PATH" 378 run cd "$TMPDIR"/build-target && 379 # Reconfigure to only run the client unit test suite. 380 # This one should _never_ fail. 381 dump "$ACTION Android client library unit tests." 382 run2 "$PROGDIR"/../configure --prefix="$TMPTARGET" \ 383 --host="$GNU_CONFIG" \ 384 --disable-tools \ 385 --disable-processor && 386 run make -j$NUM_JOBS check $TESTS_ENVIRONMENT || exit $? 387 388 if [ "$ALL_TESTS" ]; then 389 dump "$ACTION Tools and processor unit tests." 390 # Reconfigure to run the processor and tools tests. 391 # Most of these fail for now, so do not worry about it. 392 run2 "$PROGDIR"/../configure --prefix="$TMPTARGET" \ 393 --host="$GNU_CONFIG" && 394 run make -j$NUM_JOBS check $TESTS_ENVIRONMENT 395 if [ $? != 0 ]; then 396 dump "Tools and processor unit tests failed as expected. \ 397 Use --verbose for results." 398 fi 399 fi 400 ) 401 fail_panic "Client library unit test suite failed!" 402 403 # Copy sources to temporary directory 404 PROJECT_DIR=$TMPDIR/project 405 dump "Copying test program sources to: $PROJECT_DIR" 406 run cp -r "$TESTAPP_DIR" "$PROJECT_DIR" && 407 run rm -rf "$PROJECT_DIR/obj" && 408 run rm -rf "$PROJECT_DIR/libs" 409 fail_panic "Could not copy test program sources to: $PROJECT_DIR" 410 411 # Build the test program with ndk-build. 412 dump "Building test program with ndk-build" 413 export NDK_MODULE_PATH="$PROGDIR" 414 NDK_BUILD_FLAGS="-j$NUM_JOBS" 415 if verbosity_is_higher_than 1; then 416 NDK_BUILD_FLAGS="$NDK_BUILD_FLAGS NDK_LOG=1 V=1" 417 fi 418 run "$NDK_DIR/ndk-build" -C "$PROJECT_DIR" $NDK_BUILD_FLAGS APP_ABI=$ABI 419 fail_panic "Can't build test program!" 420 421 # Unless --no-device was used, stop right here if ADB isn't in the path, 422 # or there is no connected device. 423 if [ "$NO_DEVICE" ]; then 424 dump "Done. Please connect a device to run all tests!" 425 clean_exit 0 426 fi 427 428 # Push the program to the device. 429 TESTAPP=test_google_breakpad 430 TESTAPP_FILE="$PROJECT_DIR/libs/$ABI/test_google_breakpad" 431 if [ ! -f "$TESTAPP_FILE" ]; then 432 panic "Device requires '$ABI' binaries. None found!" 433 fi 434 435 # Run the program there 436 dump "Installing test program on device" 437 DEVICE_TMP=/data/local/tmp 438 adb_push "$TESTAPP_FILE" "$DEVICE_TMP/" 439 fail_panic "Cannot push test program to device!" 440 441 dump "Running test program on device" 442 adb_shell cd "$DEVICE_TMP" "&&" ./$TESTAPP > "$CRASH_LOG" 2>/dev/null 443 if [ $? = 0 ]; then 444 panic "Test program did *not* crash as expected!" 445 fi 446 if verbosity_is_higher_than 0; then 447 echo -n "Crash log: " 448 cat "$CRASH_LOG" 449 fi 450 451 # Extract minidump from device 452 MINIDUMP_NAME=$(awk '$1 == "Dump" && $2 == "path:" { print $3; }' "$CRASH_LOG") 453 MINIDUMP_NAME=$(basename "$MINIDUMP_NAME") 454 if [ -z "$MINIDUMP_NAME" ]; then 455 panic "Test program didn't write minidump properly!" 456 fi 457 458 dump "Extracting minidump: $MINIDUMP_NAME" 459 adb_pull "$DEVICE_TMP/$MINIDUMP_NAME" . 460 fail_panic "Can't extract minidump!" 461 462 dump "Parsing test program symbols" 463 if verbosity_is_higher_than 1; then 464 log "COMMAND: $TMPBIN/dump_syms \ 465 $PROJECT_DIR/obj/local/$ABI/$TESTAPP >$TESTAPP.sym" 466 fi 467 "$TMPBIN/dump_syms" "$PROJECT_DIR/obj/local/$ABI/$TESTAPP" > $TESTAPP.sym 468 fail_panic "dump_syms doesn't work!" 469 470 VERSION=$(awk '$1 == "MODULE" { print $4; }' $TESTAPP.sym) 471 dump "Found module version: $VERSION" 472 if [ -z "$VERSION" ]; then 473 echo "ERROR: Can't find proper module version from symbol dump!" 474 head -n5 $TESTAPP.sym 475 clean_exit 1 476 fi 477 478 run mkdir -p "$TMPDIR/symbols/$TESTAPP/$VERSION" 479 run mv $TESTAPP.sym "$TMPDIR/symbols/$TESTAPP/$VERSION/" 480 481 dump "Generating stack trace" 482 # Don't use 'run' to be able to send stdout and stderr to two different files. 483 log "COMMAND: $TMPBIN/minidump_stackwalk $MINIDUMP_NAME symbols" 484 "$TMPBIN/minidump_stackwalk" $MINIDUMP_NAME \ 485 "$TMPDIR/symbols" \ 486 > "$BUILD_LOG" 2>>"$RUN_LOG" 487 fail_panic "minidump_stackwalk doesn't work!" 488 489 dump "Checking stack trace content" 490 491 if verbosity_is_higher_than 1; then 492 cat "$BUILD_LOG" 493 fi 494 495 # The generated stack trace should look like the following: 496 # 497 # Thread 0 (crashed) 498 # 0 test_google_breakpad!crash [test_breakpad.cpp : 17 + 0x4] 499 # r4 = 0x00015530 r5 = 0xbea2cbe4 r6 = 0xffffff38 r7 = 0xbea2cb5c 500 # r8 = 0x00000000 r9 = 0x00000000 r10 = 0x00000000 fp = 0x00000000 501 # sp = 0xbea2cb50 lr = 0x00009025 pc = 0x00008f84 502 # Found by: given as instruction pointer in context 503 # 1 test_google_breakpad!main [test_breakpad.cpp : 25 + 0x3] 504 # r4 = 0x00015530 r5 = 0xbea2cbe4 r6 = 0xffffff38 r7 = 0xbea2cb5c 505 # r8 = 0x00000000 r9 = 0x00000000 r10 = 0x00000000 fp = 0x00000000 506 # sp = 0xbea2cb50 pc = 0x00009025 507 # Found by: call frame info 508 # 2 libc.so + 0x164e5 509 # r4 = 0x00008f64 r5 = 0xbea2cc34 r6 = 0x00000001 r7 = 0xbea2cc3c 510 # r8 = 0x00000000 r9 = 0x00000000 r10 = 0x00000000 fp = 0x00000000 511 # sp = 0xbea2cc18 pc = 0x400c34e7 512 # Found by: call frame info 513 # ... 514 # 515 # The most important part for us is ensuring that the source location could 516 # be extracted, so look at the 'test_breakpad.cpp' references here. 517 # 518 # First, extract all the lines with test_google_breakpad! in them, and 519 # dump the corresponding crash location. 520 # 521 # Note that if the source location can't be extracted, the second field 522 # will only be 'test_google_breakpad' without the exclamation mark. 523 # 524 LOCATIONS=$(awk '$2 ~ "^test_google_breakpad!.*" { print $3; }' "$BUILD_LOG") 525 526 if [ -z "$LOCATIONS" ]; then 527 if verbosity_is_lower_than 1; then 528 cat "$BUILD_LOG" 529 fi 530 panic "No source location found in stack trace!" 531 fi 532 533 # Now check that they all match "[<source file>" 534 BAD_LOCATIONS= 535 for LOCATION in $LOCATIONS; do 536 case $LOCATION in 537 # Escape the opening bracket, or some shells like Dash will not 538 # match them properly. 539 \[*.cpp|\[*.cc|\[*.h) # These are valid source locations in our executable 540 ;; 541 *) # Everything else is not! 542 BAD_LOCATIONS="$BAD_LOCATIONS $LOCATION" 543 ;; 544 esac 545 done 546 547 if [ "$BAD_LOCATIONS" ]; then 548 dump "ERROR: Generated stack trace doesn't contain valid source locations:" 549 cat "$BUILD_LOG" 550 echo "Bad locations are: $BAD_LOCATIONS" 551 exit 1 552 fi 553 554 echo "All clear! Congratulations." 555 556