Hi, Add test-case that forces alloc.c functions to fail, and check whether fail handling is robust.
This is the test-case for "[libbacktrace] Fix segfault upon allocation failure". Without that patch, this test-case fails like this: ... allocfail.sh: line 71: 26041 Segmentation fault (core dumped) \ ./allocfail $i > /dev/null 2>&1 Unallowed fail found: 13 FAIL allocfail.sh (exit status: 1) ... This is a seperate patch because the test-case is nontrivial. Bootstrapped and reg-tested on x86_64. OK for trunk? Thanks, - Tom [libbacktrace] Add allocfail.sh test-case 2018-11-27 Tom de Vries <tdevr...@suse.de> * Makefile.am (TESTS): Add allocfail.sh. (check_PROGRAMS): Add allocfail. * Makefile.in: Regenerate. * instrumented_alloc.c: New file. Redefine malloc and realloc. Include alloc.c. * allocfail.c: New file. * allocfail.sh: New file. --- libbacktrace/Makefile.am | 15 +++++ libbacktrace/Makefile.in | 60 +++++++++++++---- libbacktrace/allocfail.c | 136 ++++++++++++++++++++++++++++++++++++++ libbacktrace/allocfail.sh | 105 +++++++++++++++++++++++++++++ libbacktrace/instrumented_alloc.c | 114 ++++++++++++++++++++++++++++++++ 5 files changed, 418 insertions(+), 12 deletions(-) diff --git a/libbacktrace/Makefile.am b/libbacktrace/Makefile.am index 13e94f27aef..fdda769f18b 100644 --- a/libbacktrace/Makefile.am +++ b/libbacktrace/Makefile.am @@ -94,6 +94,21 @@ unittest_LDADD = libbacktrace.la check_PROGRAMS += unittest +libbacktrace_without_alloc = $(libbacktrace_la_OBJECTS) \ + $(filter-out alloc.lo read.lo mmap.lo mmapio.lo, \ + $(libbacktrace_la_LIBADD)) + +instrumented_alloc.lo: alloc.c + +allocfail_SOURCES = allocfail.c testlib.c +allocfail_LDADD = $(libbacktrace_without_alloc) instrumented_alloc.lo read.lo + +check_PROGRAMS += allocfail + +allocfail.sh: allocfail + +TESTS += allocfail.sh + btest_SOURCES = btest.c testlib.c btest_CFLAGS = $(AM_CFLAGS) -g -O btest_LDADD = libbacktrace.la diff --git a/libbacktrace/Makefile.in b/libbacktrace/Makefile.in index 2d62ce20b9a..49f28395686 100644 --- a/libbacktrace/Makefile.in +++ b/libbacktrace/Makefile.in @@ -121,11 +121,13 @@ build_triplet = @build@ host_triplet = @host@ target_triplet = @target@ check_PROGRAMS = $(am__EXEEXT_1) $(am__EXEEXT_2) $(am__EXEEXT_3) -@NATIVE_TRUE@am__append_1 = unittest btest stest ztest edtest -@HAVE_ZLIB_TRUE@@NATIVE_TRUE@am__append_2 = -lz -@HAVE_PTHREAD_TRUE@@NATIVE_TRUE@am__append_3 = ttest -@HAVE_OBJCOPY_DEBUGLINK_TRUE@@NATIVE_TRUE@am__append_4 = dtest -@HAVE_COMPRESSED_DEBUG_TRUE@@NATIVE_TRUE@am__append_5 = ctestg ctesta +@NATIVE_TRUE@am__append_1 = unittest allocfail btest stest ztest \ +@NATIVE_TRUE@ edtest +@NATIVE_TRUE@am__append_2 = allocfail.sh +@HAVE_ZLIB_TRUE@@NATIVE_TRUE@am__append_3 = -lz +@HAVE_PTHREAD_TRUE@@NATIVE_TRUE@am__append_4 = ttest +@HAVE_OBJCOPY_DEBUGLINK_TRUE@@NATIVE_TRUE@am__append_5 = dtest +@HAVE_COMPRESSED_DEBUG_TRUE@@NATIVE_TRUE@am__append_6 = ctestg ctesta subdir = . ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 am__aclocal_m4_deps = $(top_srcdir)/../config/cet.m4 \ @@ -158,12 +160,18 @@ AM_V_lt = $(am__v_lt_@AM_V@) am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) am__v_lt_0 = --silent am__v_lt_1 = -@NATIVE_TRUE@am__EXEEXT_1 = unittest$(EXEEXT) btest$(EXEEXT) \ -@NATIVE_TRUE@ stest$(EXEEXT) ztest$(EXEEXT) edtest$(EXEEXT) +@NATIVE_TRUE@am__EXEEXT_1 = unittest$(EXEEXT) allocfail$(EXEEXT) \ +@NATIVE_TRUE@ btest$(EXEEXT) stest$(EXEEXT) ztest$(EXEEXT) \ +@NATIVE_TRUE@ edtest$(EXEEXT) @HAVE_PTHREAD_TRUE@@NATIVE_TRUE@am__EXEEXT_2 = ttest$(EXEEXT) @HAVE_COMPRESSED_DEBUG_TRUE@@NATIVE_TRUE@am__EXEEXT_3 = \ @HAVE_COMPRESSED_DEBUG_TRUE@@NATIVE_TRUE@ ctestg$(EXEEXT) \ @HAVE_COMPRESSED_DEBUG_TRUE@@NATIVE_TRUE@ ctesta$(EXEEXT) +@NATIVE_TRUE@am_allocfail_OBJECTS = allocfail.$(OBJEXT) \ +@NATIVE_TRUE@ testlib.$(OBJEXT) +allocfail_OBJECTS = $(am_allocfail_OBJECTS) +@NATIVE_TRUE@allocfail_DEPENDENCIES = $(libbacktrace_without_alloc) \ +@NATIVE_TRUE@ instrumented_alloc.lo read.lo @NATIVE_TRUE@am_btest_OBJECTS = btest-btest.$(OBJEXT) \ @NATIVE_TRUE@ btest-testlib.$(OBJEXT) btest_OBJECTS = $(am_btest_OBJECTS) @@ -248,9 +256,9 @@ am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) am__v_CCLD_0 = @echo " CCLD " $@; am__v_CCLD_1 = SOURCES = $(libbacktrace_la_SOURCES) $(EXTRA_libbacktrace_la_SOURCES) \ - $(btest_SOURCES) $(ctesta_SOURCES) $(ctestg_SOURCES) \ - $(edtest_SOURCES) $(stest_SOURCES) $(ttest_SOURCES) \ - $(unittest_SOURCES) $(ztest_SOURCES) + $(allocfail_SOURCES) $(btest_SOURCES) $(ctesta_SOURCES) \ + $(ctestg_SOURCES) $(edtest_SOURCES) $(stest_SOURCES) \ + $(ttest_SOURCES) $(unittest_SOURCES) $(ztest_SOURCES) am__can_run_installinfo = \ case $$AM_UPDATE_INFO_DIR in \ n|no|NO) false;; \ @@ -658,9 +666,15 @@ libbacktrace_la_LIBADD = \ $(ALLOC_FILE) libbacktrace_la_DEPENDENCIES = $(libbacktrace_la_LIBADD) -TESTS = $(check_PROGRAMS) $(am__append_4) +TESTS = $(check_PROGRAMS) $(am__append_2) $(am__append_5) @NATIVE_TRUE@unittest_SOURCES = unittest.c testlib.c @NATIVE_TRUE@unittest_LDADD = libbacktrace.la +@NATIVE_TRUE@libbacktrace_without_alloc = $(libbacktrace_la_OBJECTS) \ +@NATIVE_TRUE@ $(filter-out alloc.lo read.lo mmap.lo mmapio.lo, \ +@NATIVE_TRUE@ $(libbacktrace_la_LIBADD)) + +@NATIVE_TRUE@allocfail_SOURCES = allocfail.c testlib.c +@NATIVE_TRUE@allocfail_LDADD = $(libbacktrace_without_alloc) instrumented_alloc.lo read.lo @NATIVE_TRUE@btest_SOURCES = btest.c testlib.c @NATIVE_TRUE@btest_CFLAGS = $(AM_CFLAGS) -g -O @NATIVE_TRUE@btest_LDADD = libbacktrace.la @@ -668,7 +682,7 @@ TESTS = $(check_PROGRAMS) $(am__append_4) @NATIVE_TRUE@stest_LDADD = libbacktrace.la @NATIVE_TRUE@ztest_SOURCES = ztest.c testlib.c @NATIVE_TRUE@ztest_CFLAGS = -DSRCDIR=\"$(srcdir)\" -@NATIVE_TRUE@ztest_LDADD = libbacktrace.la $(am__append_2) \ +@NATIVE_TRUE@ztest_LDADD = libbacktrace.la $(am__append_3) \ @NATIVE_TRUE@ $(CLOCK_GETTIME_LINK) @NATIVE_TRUE@edtest_SOURCES = edtest.c edtest2_build.c testlib.c @NATIVE_TRUE@edtest_LDADD = libbacktrace.la @@ -782,6 +796,10 @@ clean-checkPROGRAMS: echo " rm -f" $$list; \ rm -f $$list +allocfail$(EXEEXT): $(allocfail_OBJECTS) $(allocfail_DEPENDENCIES) $(EXTRA_allocfail_DEPENDENCIES) + @rm -f allocfail$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(allocfail_OBJECTS) $(allocfail_LDADD) $(LIBS) + btest$(EXEEXT): $(btest_OBJECTS) $(btest_DEPENDENCIES) $(EXTRA_btest_DEPENDENCIES) @rm -f btest$(EXEEXT) $(AM_V_CCLD)$(btest_LINK) $(btest_OBJECTS) $(btest_LDADD) $(LIBS) @@ -1105,6 +1123,13 @@ unittest.log: unittest$(EXEEXT) --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) +allocfail.log: allocfail$(EXEEXT) + @p='allocfail$(EXEEXT)'; \ + b='allocfail'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) btest.log: btest$(EXEEXT) @p='btest$(EXEEXT)'; \ b='btest'; \ @@ -1154,6 +1179,13 @@ ctesta.log: ctesta$(EXEEXT) --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) +allocfail.sh.log: allocfail.sh + @p='allocfail.sh'; \ + b='allocfail.sh'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) dtest.log: dtest @p='dtest'; \ b='dtest'; \ @@ -1309,6 +1341,10 @@ uninstall-am: .PRECIOUS: Makefile +@NATIVE_TRUE@instrumented_alloc.lo: alloc.c + +@native_t...@allocfail.sh: allocfail + @NATIVE_TRUE@edtest2_build.c: gen_edtest2_build; @true @NATIVE_TRUE@gen_edtest2_build: $(srcdir)/edtest2.c @NATIVE_TRUE@ cat $(srcdir)/edtest2.c > tmp-edtest2_build.c diff --git a/libbacktrace/allocfail.c b/libbacktrace/allocfail.c new file mode 100644 index 00000000000..e9149ab977d --- /dev/null +++ b/libbacktrace/allocfail.c @@ -0,0 +1,136 @@ +/* allocfail.c -- Test for libbacktrace library + Copyright (C) 2018 Free Software Foundation, Inc. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + (1) Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + (2) 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. + + (3) The name of the author may not be used to + endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. */ + +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "filenames.h" + +#include "backtrace.h" +#include "backtrace-supported.h" + +#include "testlib.h" + +extern uint64_t get_nr_allocs (void); +extern void set_fail_at_alloc (uint64_t); +extern int at_fail_alloc_p (void); + +static int test1 (void) __attribute__ ((noinline, unused)); +static int f2 (int) __attribute__ ((noinline)); +static int f3 (int, int) __attribute__ ((noinline)); + +static unsigned callback_errors = 0; + +static void +error_callback_full (void *vdata ATTRIBUTE_UNUSED, + const char *msg ATTRIBUTE_UNUSED, + int errnum ATTRIBUTE_UNUSED) +{ + if (at_fail_alloc_p ()) + { + set_fail_at_alloc (0); + return; + } + + callback_errors++; +} + +static int +callback_full (void *vdata ATTRIBUTE_UNUSED, uintptr_t pc ATTRIBUTE_UNUSED, + const char *filename ATTRIBUTE_UNUSED, + int lineno ATTRIBUTE_UNUSED, + const char *function ATTRIBUTE_UNUSED) +{ + + return 0; +} + +static int +test1 (void) +{ + return f2 (__LINE__) + 1; +} + +static int +f2 (int f1line) +{ + return f3 (f1line, __LINE__) + 2; +} + +static int +f3 (int f1line ATTRIBUTE_UNUSED, int f2line ATTRIBUTE_UNUSED) +{ + int i; + + i = backtrace_full (state, 0, callback_full, error_callback_full, NULL); + + if (i != 0) + { + fprintf (stderr, "test1: unexpected return value %d\n", i); + ++failures; + } + + if (callback_errors) + ++failures; + + return failures; +} + +/* Run all the tests. */ + +int +main (int argc, char **argv) +{ + uint64_t fail_at = 0; + + if (argc == 2) + { + fail_at = atoi (argv[1]); + set_fail_at_alloc (fail_at); + } + + state = backtrace_create_state (argv[0], BACKTRACE_SUPPORTS_THREADS, + error_callback_full, NULL); + if (state == NULL) + exit (failures ? EXIT_FAILURE : EXIT_SUCCESS); + +#if BACKTRACE_SUPPORTED + test1 (); +#endif + + if (argc == 1) + fprintf (stderr, "%lu\n", get_nr_allocs ()); + + exit (failures ? EXIT_FAILURE : EXIT_SUCCESS); +} diff --git a/libbacktrace/allocfail.sh b/libbacktrace/allocfail.sh new file mode 100755 index 00000000000..91bc7a3e73d --- /dev/null +++ b/libbacktrace/allocfail.sh @@ -0,0 +1,105 @@ +#!/bin/sh + +# allocfail.sh -- Test for libbacktrace library. +# Copyright (C) 2018 Free Software Foundation, Inc. + +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: + +# (1) Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. + +# (2) 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. + +# (3) The name of the author may not be used to +# endorse or promote products derived from this software without +# specific prior written permission. + +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +set -e +set -o pipefail + +if [ ! -f ./allocfail ]; then + # Hard failure. + exit 99 +fi + +allocs=$(./allocfail 2>&1) +if [ "$allocs" = "" ]; then + # Hard failure. + exit 99 +fi + +# This generates the following output: +# ... +# $ allocfail.sh +# allocs: 80495 +# Status changed to 0 at 1 +# Status changed to 1 at 3 +# Status changed to 0 at 11 +# Status changed to 1 at 12 +# Status changed to 0 at 845 +# ... +# +# We have status 0 for an allocation failure at: +# - 1 because backtrace_create_state handles failure robustly +# - 2 because the fail switches backtrace_full to !can_alloc mode. +# - 11 because failure of elf_open_debugfile_by_buildid does not generate an +# error callback beyond the one for the allocation failure itself. + +echo "allocs: $allocs" + +step=1 +i=1 +passes=0 +prev_status=-1 +while [ $i -le $allocs ]; do + if ./allocfail $i >/dev/null 2>&1; status=$?; then + true + fi + if [ $status -gt 1 ]; then + echo "Unallowed fail found: $i" + # Failure. + exit 1 + fi + + # The test-case would run too long if we would excercise all allocs. + # So, run with step 1 initially, and increase the step once we have 10 + # subsequent passes, and drop back to step 1 once we encounter another + # failure. This takes ~2.6 seconds on an i7-6600U CPU @ 2.60GHz. + if [ $status -eq 0 ]; then + passes=$(($passes + 1)) + if [ $passes -ge 10 ]; then + step=$((step * 10)) + passes=0 + fi + elif [ $status -eq 1 ]; then + passes=0 + step=1 + fi + + if [ $status -ne $prev_status ]; then + echo "Status changed to $status at $i" + fi + prev_status=$status + + i=$(($i + $step)) +done + +# Success. +exit 0 diff --git a/libbacktrace/instrumented_alloc.c b/libbacktrace/instrumented_alloc.c new file mode 100644 index 00000000000..ba42ea65a04 --- /dev/null +++ b/libbacktrace/instrumented_alloc.c @@ -0,0 +1,114 @@ +/* instrumented_alloc.c -- Memory allocation instrumented to fail when + requested, for testing purposes. + Copyright (C) 2018 Free Software Foundation, Inc. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + (1) Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + (2) 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. + + (3) The name of the author may not be used to + endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. */ + +/* Include all the header files of alloc here, to make sure they're not + processed when including alloc.c below, such that the redefinitions of malloc + and realloc are only effective in alloc.c itself. This does not work for + config.h, because it's not wrapped in "#ifndef CONFIG_H\n#define CONFIG_H" + and "#endif" but that does not seem to be harmful. */ + +#include "config.h" + +#include <errno.h> +#include <stdlib.h> +#include <sys/types.h> +#include <inttypes.h> + +#include "backtrace.h" +#include "internal.h" + +extern void *instrumented_malloc (size_t size); +extern void *instrumented_realloc (void *ptr, size_t size); + +#define malloc instrumented_malloc +#define realloc instrumented_realloc +#include "alloc.c" +#undef malloc +#undef realloc + +static uint64_t nr_allocs = 0; +static uint64_t fail_at_alloc = 0; + +extern int at_fail_alloc_p (void); +extern uint64_t get_nr_allocs (void); +extern void set_fail_at_alloc (uint64_t); + +void * +instrumented_malloc (size_t size) +{ + void *res; + + if (at_fail_alloc_p ()) + return NULL; + + res = malloc (size); + if (res != NULL) + nr_allocs++; + + return res; +} + +void * +instrumented_realloc (void *ptr, size_t size) +{ + void *res; + + if (size != 0) + { + if (at_fail_alloc_p ()) + return NULL; + } + + res = realloc (ptr, size); + if (res != NULL) + nr_allocs++; + + return res; +} + +int +at_fail_alloc_p (void) +{ + return fail_at_alloc == nr_allocs + 1; +} + +uint64_t +get_nr_allocs (void) +{ + return nr_allocs; +} + +void +set_fail_at_alloc (uint64_t nr) +{ + fail_at_alloc = nr; +}