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