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
32export LANG=C
33export LC_ALL=C
34
35if [ "$BASH_VERSION" ]; then
36  set -o posix
37fi
38
39PROGDIR=$(dirname "$0")
40PROGDIR=$(cd "$PROGDIR" && pwd)
41PROGNAME=$(basename "$0")
42
43. $PROGDIR/common-functions.sh
44
45DEFAULT_ABI="armeabi"
46VALID_ABIS="armeabi armeabi-v7a x86 mips"
47
48ABI=
49ADB=
50ALL_TESTS=
51ENABLE_M32=
52HELP=
53HELP_ALL=
54NDK_DIR=
55NO_CLEANUP=
56NO_DEVICE=
57NUM_JOBS=$(get_core_count)
58TMPDIR=
59
60for 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
81done
82
83if [ "$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
166fi
167
168TESTAPP_DIR=$PROGDIR/sample_app
169
170# Select NDK install directory.
171if [ -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"
178else
179  log "Using NDK directory: $NDK_DIR"
180fi
181# Small sanity check.
182NDK_BUILD="$NDK_DIR/ndk-build"
183if [ ! -f "$NDK_BUILD" ]; then
184  panic "Your NDK directory is not valid (missing ndk-build): $NDK_DIR"
185fi
186
187# Ensure the temporary directory is deleted on exit, except if the --no-cleanup
188# option is used.
189
190clean_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
202atexit 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.
206if [ -z "$TMPDIR" ]; then
207  TMPDIR=$(mktemp -d /tmp/$PROGNAME.XXXXXXXX)
208  fail_panic "Can't create temporary directory!"
209  log "Using temporary directory: $TMPDIR"
210else
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
219fi
220
221if [ -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
227fi
228
229BUILD_LOG="$TMPDIR/build.log"
230RUN_LOG="$TMPDIR/run.log"
231CRASH_LOG="$TMPDIR/crash.log"
232
233set_run_log "$RUN_LOG"
234
235TMPHOST="$TMPDIR/host-local"
236
237cd "$TMPDIR"
238
239# Build host version of the tools
240dump "Building host binaries."
241CONFIGURE_FLAGS=
242if [ "$ENABLE_M32" ]; then
243  CONFIGURE_FLAGS="$CONFIGURE_FLAGS --enable-m32"
244fi
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)
251fail_panic "Can't build host binaries!"
252
253if [ "$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!!"
260fi
261
262TMPBIN=$TMPHOST/bin
263
264# Generate a stand-alone NDK toolchain
265
266# Extract CPU ABI and architecture from device, if any.
267if 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
292else
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
300fi
301
302# Check the ABI value
303VALID=
304for VALID_ABI in $VALID_ABIS; do
305  if [ "$ABI" = "$VALID_ABI" ]; then
306    VALID=true
307    break
308  fi
309done
310
311if [ -z "$VALID" ]; then
312  panic "Unknown CPU ABI '$ABI'. Valid values are: $VALID_ABIS"
313fi
314
315# Extract architecture name from ABI
316case $ABI in
317  armeabi*) ARCH=arm;;
318  *) ARCH=$ABI;;
319esac
320
321# Extract GNU configuration name
322case $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    ;;
335esac
336
337# Generate standalone NDK toolchain installation
338NDK_STANDALONE="$TMPDIR/ndk-$ARCH-toolchain"
339echo "Generating NDK standalone toolchain installation"
340mkdir -p "$NDK_STANDALONE"
341# NOTE: The --platform=android-9 is required to provide <regex.h> for GTest.
342run "$NDK_DIR/build/tools/make-standalone-toolchain.sh" \
343      --arch="$ARCH" \
344      --platform=android-9 \
345      --install-dir="$NDK_STANDALONE"
346fail_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.
351echo "Building full Android binaries with configure/make"
352TMPTARGET="$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)
362fail_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.
367if [ "$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"
371else
372  ACTION="Running"
373  TESTS_ENVIRONMENT=
374fi
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. \
397Use --verbose for results."
398    fi                           
399  fi
400)
401fail_panic "Client library unit test suite failed!"
402
403# Copy sources to temporary directory
404PROJECT_DIR=$TMPDIR/project
405dump "Copying test program sources to: $PROJECT_DIR"
406run cp -r "$TESTAPP_DIR" "$PROJECT_DIR" &&
407run rm -rf "$PROJECT_DIR/obj" &&
408run rm -rf "$PROJECT_DIR/libs"
409fail_panic "Could not copy test program sources to: $PROJECT_DIR"
410
411# Build the test program with ndk-build.
412dump "Building test program with ndk-build"
413export NDK_MODULE_PATH="$PROGDIR"
414NDK_BUILD_FLAGS="-j$NUM_JOBS"
415if verbosity_is_higher_than 1; then
416  NDK_BUILD_FLAGS="$NDK_BUILD_FLAGS NDK_LOG=1 V=1"
417fi
418run "$NDK_DIR/ndk-build" -C "$PROJECT_DIR" $NDK_BUILD_FLAGS APP_ABI=$ABI
419fail_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.
423if [ "$NO_DEVICE" ]; then
424  dump "Done. Please connect a device to run all tests!"
425  clean_exit 0
426fi
427
428# Push the program to the device.
429TESTAPP=test_google_breakpad
430TESTAPP_FILE="$PROJECT_DIR/libs/$ABI/test_google_breakpad"
431if [ ! -f "$TESTAPP_FILE" ]; then
432  panic "Device requires '$ABI' binaries. None found!"
433fi
434
435# Run the program there
436dump "Installing test program on device"
437DEVICE_TMP=/data/local/tmp
438adb_push "$TESTAPP_FILE" "$DEVICE_TMP/"
439fail_panic "Cannot push test program to device!"
440
441dump "Running test program on device"
442adb_shell cd "$DEVICE_TMP" "&&" ./$TESTAPP > "$CRASH_LOG" 2>/dev/null
443if [ $? = 0 ]; then
444  panic "Test program did *not* crash as expected!"
445fi
446if verbosity_is_higher_than 0; then
447  echo -n "Crash log: "
448  cat "$CRASH_LOG"
449fi
450
451# Extract minidump from device
452MINIDUMP_NAME=$(awk '$1 == "Dump" && $2 == "path:" { print $3; }' "$CRASH_LOG")
453MINIDUMP_NAME=$(basename "$MINIDUMP_NAME")
454if [ -z "$MINIDUMP_NAME" ]; then
455  panic "Test program didn't write minidump properly!"
456fi
457
458dump "Extracting minidump: $MINIDUMP_NAME"
459adb_pull "$DEVICE_TMP/$MINIDUMP_NAME" .
460fail_panic "Can't extract minidump!"
461
462dump "Parsing test program symbols"
463if verbosity_is_higher_than 1; then
464  log "COMMAND: $TMPBIN/dump_syms \
465                $PROJECT_DIR/obj/local/$ABI/$TESTAPP >$TESTAPP.sym"
466fi
467"$TMPBIN/dump_syms" "$PROJECT_DIR/obj/local/$ABI/$TESTAPP" > $TESTAPP.sym
468fail_panic "dump_syms doesn't work!"
469
470VERSION=$(awk '$1 == "MODULE" { print $4; }' $TESTAPP.sym)
471dump "Found module version: $VERSION"
472if [ -z "$VERSION" ]; then
473  echo "ERROR: Can't find proper module version from symbol dump!"
474  head -n5 $TESTAPP.sym
475  clean_exit 1
476fi
477
478run mkdir -p "$TMPDIR/symbols/$TESTAPP/$VERSION"
479run mv $TESTAPP.sym "$TMPDIR/symbols/$TESTAPP/$VERSION/"
480
481dump "Generating stack trace"
482# Don't use 'run' to be able to send stdout and stderr to two different files.
483log "COMMAND: $TMPBIN/minidump_stackwalk $MINIDUMP_NAME symbols"
484"$TMPBIN/minidump_stackwalk" $MINIDUMP_NAME \
485                             "$TMPDIR/symbols" \
486                             > "$BUILD_LOG" 2>>"$RUN_LOG"
487fail_panic "minidump_stackwalk doesn't work!"
488
489dump "Checking stack trace content"
490
491if verbosity_is_higher_than 1; then
492  cat "$BUILD_LOG"
493fi
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#
524LOCATIONS=$(awk '$2 ~ "^test_google_breakpad!.*" { print $3; }' "$BUILD_LOG")
525
526if [ -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!"
531fi
532
533# Now check that they all match "[<source file>"
534BAD_LOCATIONS=
535for 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
545done
546
547if [ "$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
552fi
553
554echo "All clear! Congratulations."
555
556