| #!/bin/sh |
| # Copyright (c) 2012 Google Inc. |
| # All rights reserved. |
| # |
| # Redistribution and use in source and binary forms, with or without |
| # modification, are permitted provided that the following conditions are |
| # met: |
| # |
| # * Redistributions of source code must retain the above copyright |
| # notice, this list of conditions and the following disclaimer. |
| # * Redistributions in binary form must reproduce the above |
| # copyright notice, this list of conditions and the following disclaimer |
| # in the documentation and/or other materials provided with the |
| # distribution. |
| # * Neither the name of Google Inc. nor the names of its |
| # contributors may be used to endorse or promote products derived from |
| # this software without specific prior written permission. |
| # |
| # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| # Sanitize the environment |
| export LANG=C |
| export LC_ALL=C |
| |
| if [ "$BASH_VERSION" ]; then |
| set -o posix |
| fi |
| |
| PROGDIR=$(dirname "$0") |
| PROGDIR=$(cd "$PROGDIR" && pwd) |
| PROGNAME=$(basename "$0") |
| |
| . $PROGDIR/common-functions.sh |
| |
| DEFAULT_ABI="armeabi" |
| VALID_ABIS="armeabi armeabi-v7a x86 mips" |
| |
| ABI= |
| ADB= |
| ALL_TESTS= |
| ENABLE_M32= |
| HELP= |
| HELP_ALL= |
| NDK_DIR= |
| NO_CLEANUP= |
| NO_DEVICE= |
| NUM_JOBS=$(get_core_count) |
| TMPDIR= |
| |
| for opt do |
| # The following extracts the value if the option is like --name=<value>. |
| optarg=$(expr -- $opt : '^--[^=]*=\(.*\)$') |
| case $opt in |
| --abi=*) ABI=$optarg;; |
| --adb=*) ADB=$optarg;; |
| --all-tests) ALL_TESTS=true;; |
| --enable-m32) ENABLE_M32=true;; |
| --help|-h|-?) HELP=TRUE;; |
| --help-all) HELP_ALL=true;; |
| --jobs=*) NUM_JOBS=$optarg;; |
| --ndk-dir=*) NDK_DIR=$optarg;; |
| --tmp-dir=*) TMPDIR=$optarg;; |
| --no-cleanup) NO_CLEANUP=true;; |
| --no-device) NO_DEVICE=true;; |
| --quiet) decrease_verbosity;; |
| --verbose) increase_verbosity;; |
| -*) panic "Invalid option '$opt', see --help for details.";; |
| *) panic "This script doesn't take any parameters. See --help for details." |
| ;; |
| esac |
| done |
| |
| if [ "$HELP" -o "$HELP_ALL" ]; then |
| echo "\ |
| Usage: $PROGNAME [options] |
| |
| This script is used to check that your Google Breakpad source tree can |
| be properly built for Android, and that the client library and host tools |
| work properly together. |
| " |
| if [ "$HELP_ALL" ]; then |
| echo "\ |
| In more details, this script will: |
| |
| - Rebuild the host version of Google Breakpad in a temporary |
| directory (with the Auto-tools based build system). |
| |
| - Rebuild the Android client library with the Google Breakpad build |
| system (using autotools/configure). This requires that you define |
| ANDROID_NDK_ROOT in your environment to point to a valid Android NDK |
| installation directory, or use the --ndk-dir=<path> option. |
| |
| - Rebuild the Android client library and a test crashing program with the |
| Android NDK build system (ndk-build). |
| |
| - Require an Android device connected to your machine, and the 'adb' |
| tool in your path. They are used to: |
| |
| - Install and run a test crashing program. |
| - Extract the corresponding minidump from the device. |
| - Dump the symbols from the test program on the host with 'dump_syms' |
| - Generate a stack trace with 'minidump_stackwalk' |
| - Check the stack trace content for valid source file locations. |
| |
| You can however skip this requirement and only test the builds by using |
| the --no-device flag. |
| |
| By default, all generated files will be created in a temporary directory |
| that is removed when the script completion. If you want to inspect the |
| files, use the --no-cleanup option. |
| |
| Finally, use --verbose to increase the verbosity level, this will help |
| you see which exact commands are being issues and their result. Use the |
| flag twice for even more output. Use --quiet to decrease verbosity |
| instead and run the script silently. |
| |
| If you have a device connected, the script will probe it to determine |
| its primary CPU ABI, and build the test program for it. You can however |
| use the --abi=<name> option to override this (this can be useful to check |
| the secondary ABI, e.g. using --abi=armeabi to check that such a program |
| works correctly on an ARMv7-A device). |
| |
| If you don't have a device connected, the test program will be built (but |
| not run) with the default '$DEFAULT_ABI' ABI. Again, you can use |
| --abi=<name> to override this. Valid ABI names are: |
| |
| $VALID_ABIS |
| |
| The script will only run the client library unit test on the device |
| by default. You can use --all-tests to also build and run the unit |
| tests for the Breakpad tools and processor, but be warned that this |
| adds several minutes of testing time. --all-tests will also run the |
| host unit tests suite. |
| " |
| |
| fi # HELP_ALL |
| |
| echo "\ |
| Valid options: |
| |
| --help|-h|-? Display this message. |
| --help-all Display extended help. |
| --enable-m32 Build 32-bit version of host tools. |
| --abi=<name> Specify target CPU ABI [auto-detected]. |
| --jobs=<count> Run <count> build tasks in parallel [$NUM_JOBS]. |
| --ndk-dir=<path> Specify NDK installation directory. |
| --tmp-dir=<path> Specify temporary directory (will be wiped-out). |
| --adb=<path> Specify adb program path. |
| --no-cleanup Don't remove temporary directory after completion. |
| --no-device Do not try to detect devices, nor run crash test. |
| --all-tests Run all unit tests (i.e. tools and processor ones too). |
| --verbose Increase verbosity. |
| --quiet Decrease verbosity." |
| |
| exit 0 |
| fi |
| |
| TESTAPP_DIR=$PROGDIR/sample_app |
| |
| # Select NDK install directory. |
| if [ -z "$NDK_DIR" ]; then |
| if [ -z "$ANDROID_NDK_ROOT" ]; then |
| panic "Please define ANDROID_NDK_ROOT in your environment, or use \ |
| --ndk-dir=<path>." |
| fi |
| NDK_DIR="$ANDROID_NDK_ROOT" |
| log "Found NDK directory: $NDK_DIR" |
| else |
| log "Using NDK directory: $NDK_DIR" |
| fi |
| # Small sanity check. |
| NDK_BUILD="$NDK_DIR/ndk-build" |
| if [ ! -f "$NDK_BUILD" ]; then |
| panic "Your NDK directory is not valid (missing ndk-build): $NDK_DIR" |
| fi |
| |
| # Ensure the temporary directory is deleted on exit, except if the --no-cleanup |
| # option is used. |
| |
| clean_tmpdir () { |
| if [ "$TMPDIR" ]; then |
| if [ -z "$NO_CLEANUP" ]; then |
| log "Cleaning up: $TMPDIR" |
| rm -rf "$TMPDIR" |
| else |
| dump "Temporary directory contents preserved: $TMPDIR" |
| fi |
| fi |
| exit "$@" |
| } |
| |
| atexit clean_tmpdir |
| |
| # If --tmp-dir=<path> is not used, create a temporary directory. |
| # Otherwise, start by cleaning up the user-provided path. |
| if [ -z "$TMPDIR" ]; then |
| TMPDIR=$(mktemp -d /tmp/$PROGNAME.XXXXXXXX) |
| fail_panic "Can't create temporary directory!" |
| log "Using temporary directory: $TMPDIR" |
| else |
| if [ ! -d "$TMPDIR" ]; then |
| mkdir -p "$TMPDIR" |
| fail_panic "Can't create temporary directory: $TMPDIR" |
| else |
| log "Cleaning up temporary directory: $TMPDIR" |
| rm -rf "$TMPDIR"/* |
| fail_panic "Cannot cleanup temporary directory!" |
| fi |
| fi |
| |
| if [ -z "$NO_DEVICE" ]; then |
| if ! adb_check_device $ADB; then |
| echo "$(adb_get_error)" |
| echo "Use --no-device to build the code without running any tests." |
| exit 1 |
| fi |
| fi |
| |
| BUILD_LOG="$TMPDIR/build.log" |
| RUN_LOG="$TMPDIR/run.log" |
| CRASH_LOG="$TMPDIR/crash.log" |
| |
| set_run_log "$RUN_LOG" |
| |
| TMPHOST="$TMPDIR/host-local" |
| |
| cd "$TMPDIR" |
| |
| # Build host version of the tools |
| dump "Building host binaries." |
| CONFIGURE_FLAGS= |
| if [ "$ENABLE_M32" ]; then |
| CONFIGURE_FLAGS="$CONFIGURE_FLAGS --enable-m32" |
| fi |
| ( |
| run mkdir "$TMPDIR/build-host" && |
| run cd "$TMPDIR/build-host" && |
| run2 "$PROGDIR/../configure" --prefix="$TMPHOST" $CONFIGURE_FLAGS && |
| run2 make -j$NUM_JOBS install |
| ) |
| fail_panic "Can't build host binaries!" |
| |
| if [ "$ALL_TESTS" ]; then |
| dump "Running host unit tests." |
| ( |
| run cd "$TMPDIR/build-host" && |
| run2 make -j$NUM_JOBS check |
| ) |
| fail_panic "Host unit tests failed!!" |
| fi |
| |
| TMPBIN=$TMPHOST/bin |
| |
| # Generate a stand-alone NDK toolchain |
| |
| # Extract CPU ABI and architecture from device, if any. |
| if adb_check_device; then |
| DEVICE_ABI=$(adb_shell getprop ro.product.cpu.abi) |
| DEVICE_ABI2=$(adb_shell getprop ro.product.cpu.abi2) |
| if [ -z "$DEVICE_ABI" ]; then |
| panic "Can't extract ABI from connected device!" |
| fi |
| if [ "$DEVICE_ABI2" ]; then |
| dump "Found device ABIs: $DEVICE_ABI $DEVICE_ABI2" |
| else |
| dump "Found device ABI: $DEVICE_ABI" |
| DEVICE_ABI2=$DEVICE_ABI |
| fi |
| |
| # If --abi=<name> is used, check that the device supports it. |
| if [ "$ABI" -a "$DEVICE_ABI" != "$ABI" -a "$DEVICE_ABI2" != "$ABI" ]; then |
| dump "ERROR: Device ABI(s) do not match --abi command-line value ($ABI)!" |
| panic "Please use --no-device to skip device tests." |
| fi |
| |
| if [ -z "$ABI" ]; then |
| ABI=$DEVICE_ABI |
| dump "Using CPU ABI: $ABI (device)" |
| else |
| dump "Using CPU ABI: $ABI (command-line)" |
| fi |
| else |
| if [ -z "$ABI" ]; then |
| # No device connected, choose default ABI |
| ABI=$DEFAULT_ABI |
| dump "Using CPU ABI: $ABI (default)" |
| else |
| dump "Using CPU ABI: $ABI (command-line)" |
| fi |
| fi |
| |
| # Check the ABI value |
| VALID= |
| for VALID_ABI in $VALID_ABIS; do |
| if [ "$ABI" = "$VALID_ABI" ]; then |
| VALID=true |
| break |
| fi |
| done |
| |
| if [ -z "$VALID" ]; then |
| panic "Unknown CPU ABI '$ABI'. Valid values are: $VALID_ABIS" |
| fi |
| |
| # Extract architecture name from ABI |
| case $ABI in |
| armeabi*) ARCH=arm;; |
| *) ARCH=$ABI;; |
| esac |
| |
| # Extract GNU configuration name |
| case $ARCH in |
| arm) |
| GNU_CONFIG=arm-linux-androideabi |
| ;; |
| x86) |
| GNU_CONFIG=i686-linux-android |
| ;; |
| mips) |
| GNU_CONFIG=mipsel-linux-android |
| ;; |
| *) |
| GNU_CONFIG="$ARCH-linux-android" |
| ;; |
| esac |
| |
| # Generate standalone NDK toolchain installation |
| NDK_STANDALONE="$TMPDIR/ndk-$ARCH-toolchain" |
| echo "Generating NDK standalone toolchain installation" |
| mkdir -p "$NDK_STANDALONE" |
| # NOTE: The --platform=android-9 is required to provide <regex.h> for GTest. |
| run "$NDK_DIR/build/tools/make-standalone-toolchain.sh" \ |
| --arch="$ARCH" \ |
| --platform=android-9 \ |
| --install-dir="$NDK_STANDALONE" |
| fail_panic "Can't generate standalone NDK toolchain installation!" |
| |
| # Rebuild the client library, processor and tools with the auto-tools based |
| # build system. Even though it's not going to be used, this checks that this |
| # still works correctly. |
| echo "Building full Android binaries with configure/make" |
| TMPTARGET="$TMPDIR/target-local" |
| ( |
| PATH="$NDK_STANDALONE/bin:$PATH" |
| run mkdir "$TMPTARGET" && |
| run mkdir "$TMPDIR"/build-target && |
| run cd "$TMPDIR"/build-target && |
| run2 "$PROGDIR"/../configure --prefix="$TMPTARGET" \ |
| --host="$GNU_CONFIG" && |
| run2 make -j$NUM_JOBS install |
| ) |
| fail_panic "Could not rebuild Android binaries!" |
| |
| # Build and/or run unit test suite. |
| # If --no-device is used, only rebuild it, otherwise, run in on the |
| # connected device. |
| if [ "$NO_DEVICE" ]; then |
| ACTION="Building" |
| # This is a trick to force the Makefile to ignore running the scripts. |
| TESTS_ENVIRONMENT="TESTS_ENVIRONMENT=true" |
| else |
| ACTION="Running" |
| TESTS_ENVIRONMENT= |
| fi |
| |
| ( |
| PATH="$NDK_STANDALONE/bin:$PATH" |
| run cd "$TMPDIR"/build-target && |
| # Reconfigure to only run the client unit test suite. |
| # This one should _never_ fail. |
| dump "$ACTION Android client library unit tests." |
| run2 "$PROGDIR"/../configure --prefix="$TMPTARGET" \ |
| --host="$GNU_CONFIG" \ |
| --disable-tools \ |
| --disable-processor && |
| run make -j$NUM_JOBS check $TESTS_ENVIRONMENT || exit $? |
| |
| if [ "$ALL_TESTS" ]; then |
| dump "$ACTION Tools and processor unit tests." |
| # Reconfigure to run the processor and tools tests. |
| # Most of these fail for now, so do not worry about it. |
| run2 "$PROGDIR"/../configure --prefix="$TMPTARGET" \ |
| --host="$GNU_CONFIG" && |
| run make -j$NUM_JOBS check $TESTS_ENVIRONMENT |
| if [ $? != 0 ]; then |
| dump "Tools and processor unit tests failed as expected. \ |
| Use --verbose for results." |
| fi |
| fi |
| ) |
| fail_panic "Client library unit test suite failed!" |
| |
| # Copy sources to temporary directory |
| PROJECT_DIR=$TMPDIR/project |
| dump "Copying test program sources to: $PROJECT_DIR" |
| run cp -r "$TESTAPP_DIR" "$PROJECT_DIR" && |
| run rm -rf "$PROJECT_DIR/obj" && |
| run rm -rf "$PROJECT_DIR/libs" |
| fail_panic "Could not copy test program sources to: $PROJECT_DIR" |
| |
| # Build the test program with ndk-build. |
| dump "Building test program with ndk-build" |
| export NDK_MODULE_PATH="$PROGDIR" |
| NDK_BUILD_FLAGS="-j$NUM_JOBS" |
| if verbosity_is_higher_than 1; then |
| NDK_BUILD_FLAGS="$NDK_BUILD_FLAGS NDK_LOG=1 V=1" |
| fi |
| run "$NDK_DIR/ndk-build" -C "$PROJECT_DIR" $NDK_BUILD_FLAGS APP_ABI=$ABI |
| fail_panic "Can't build test program!" |
| |
| # Unless --no-device was used, stop right here if ADB isn't in the path, |
| # or there is no connected device. |
| if [ "$NO_DEVICE" ]; then |
| dump "Done. Please connect a device to run all tests!" |
| clean_exit 0 |
| fi |
| |
| # Push the program to the device. |
| TESTAPP=test_google_breakpad |
| TESTAPP_FILE="$PROJECT_DIR/libs/$ABI/test_google_breakpad" |
| if [ ! -f "$TESTAPP_FILE" ]; then |
| panic "Device requires '$ABI' binaries. None found!" |
| fi |
| |
| # Run the program there |
| dump "Installing test program on device" |
| DEVICE_TMP=/data/local/tmp |
| adb_push "$TESTAPP_FILE" "$DEVICE_TMP/" |
| fail_panic "Cannot push test program to device!" |
| |
| dump "Running test program on device" |
| adb_shell cd "$DEVICE_TMP" "&&" ./$TESTAPP > "$CRASH_LOG" 2>/dev/null |
| if [ $? = 0 ]; then |
| panic "Test program did *not* crash as expected!" |
| fi |
| if verbosity_is_higher_than 0; then |
| echo -n "Crash log: " |
| cat "$CRASH_LOG" |
| fi |
| |
| # Extract minidump from device |
| MINIDUMP_NAME=$(awk '$1 == "Dump" && $2 == "path:" { print $3; }' "$CRASH_LOG") |
| MINIDUMP_NAME=$(basename "$MINIDUMP_NAME") |
| if [ -z "$MINIDUMP_NAME" ]; then |
| panic "Test program didn't write minidump properly!" |
| fi |
| |
| dump "Extracting minidump: $MINIDUMP_NAME" |
| adb_pull "$DEVICE_TMP/$MINIDUMP_NAME" . |
| fail_panic "Can't extract minidump!" |
| |
| dump "Parsing test program symbols" |
| if verbosity_is_higher_than 1; then |
| log "COMMAND: $TMPBIN/dump_syms \ |
| $PROJECT_DIR/obj/local/$ABI/$TESTAPP >$TESTAPP.sym" |
| fi |
| "$TMPBIN/dump_syms" "$PROJECT_DIR/obj/local/$ABI/$TESTAPP" > $TESTAPP.sym |
| fail_panic "dump_syms doesn't work!" |
| |
| VERSION=$(awk '$1 == "MODULE" { print $4; }' $TESTAPP.sym) |
| dump "Found module version: $VERSION" |
| if [ -z "$VERSION" ]; then |
| echo "ERROR: Can't find proper module version from symbol dump!" |
| head -n5 $TESTAPP.sym |
| clean_exit 1 |
| fi |
| |
| run mkdir -p "$TMPDIR/symbols/$TESTAPP/$VERSION" |
| run mv $TESTAPP.sym "$TMPDIR/symbols/$TESTAPP/$VERSION/" |
| |
| dump "Generating stack trace" |
| # Don't use 'run' to be able to send stdout and stderr to two different files. |
| log "COMMAND: $TMPBIN/minidump_stackwalk $MINIDUMP_NAME symbols" |
| "$TMPBIN/minidump_stackwalk" $MINIDUMP_NAME \ |
| "$TMPDIR/symbols" \ |
| > "$BUILD_LOG" 2>>"$RUN_LOG" |
| fail_panic "minidump_stackwalk doesn't work!" |
| |
| dump "Checking stack trace content" |
| |
| if verbosity_is_higher_than 1; then |
| cat "$BUILD_LOG" |
| fi |
| |
| # The generated stack trace should look like the following: |
| # |
| # Thread 0 (crashed) |
| # 0 test_google_breakpad!crash [test_breakpad.cpp : 17 + 0x4] |
| # r4 = 0x00015530 r5 = 0xbea2cbe4 r6 = 0xffffff38 r7 = 0xbea2cb5c |
| # r8 = 0x00000000 r9 = 0x00000000 r10 = 0x00000000 fp = 0x00000000 |
| # sp = 0xbea2cb50 lr = 0x00009025 pc = 0x00008f84 |
| # Found by: given as instruction pointer in context |
| # 1 test_google_breakpad!main [test_breakpad.cpp : 25 + 0x3] |
| # r4 = 0x00015530 r5 = 0xbea2cbe4 r6 = 0xffffff38 r7 = 0xbea2cb5c |
| # r8 = 0x00000000 r9 = 0x00000000 r10 = 0x00000000 fp = 0x00000000 |
| # sp = 0xbea2cb50 pc = 0x00009025 |
| # Found by: call frame info |
| # 2 libc.so + 0x164e5 |
| # r4 = 0x00008f64 r5 = 0xbea2cc34 r6 = 0x00000001 r7 = 0xbea2cc3c |
| # r8 = 0x00000000 r9 = 0x00000000 r10 = 0x00000000 fp = 0x00000000 |
| # sp = 0xbea2cc18 pc = 0x400c34e7 |
| # Found by: call frame info |
| # ... |
| # |
| # The most important part for us is ensuring that the source location could |
| # be extracted, so look at the 'test_breakpad.cpp' references here. |
| # |
| # First, extract all the lines with test_google_breakpad! in them, and |
| # dump the corresponding crash location. |
| # |
| # Note that if the source location can't be extracted, the second field |
| # will only be 'test_google_breakpad' without the exclamation mark. |
| # |
| LOCATIONS=$(awk '$2 ~ "^test_google_breakpad!.*" { print $3; }' "$BUILD_LOG") |
| |
| if [ -z "$LOCATIONS" ]; then |
| if verbosity_is_lower_than 1; then |
| cat "$BUILD_LOG" |
| fi |
| panic "No source location found in stack trace!" |
| fi |
| |
| # Now check that they all match "[<source file>" |
| BAD_LOCATIONS= |
| for LOCATION in $LOCATIONS; do |
| case $LOCATION in |
| # Escape the opening bracket, or some shells like Dash will not |
| # match them properly. |
| \[*.cpp|\[*.cc|\[*.h) # These are valid source locations in our executable |
| ;; |
| *) # Everything else is not! |
| BAD_LOCATIONS="$BAD_LOCATIONS $LOCATION" |
| ;; |
| esac |
| done |
| |
| if [ "$BAD_LOCATIONS" ]; then |
| dump "ERROR: Generated stack trace doesn't contain valid source locations:" |
| cat "$BUILD_LOG" |
| echo "Bad locations are: $BAD_LOCATIONS" |
| exit 1 |
| fi |
| |
| echo "All clear! Congratulations." |
| |