author | Alberto Bertogli
<albertito@blitiri.com.ar> 2012-03-27 23:52:09 UTC |
committer | Alberto Bertogli
<albertito@blitiri.com.ar> 2012-03-27 23:59:19 UTC |
parent | 7cb3d44c9c8dfeb70064827447ef8d9005f0c420 |
.gitignore | +3 | -1 |
Makefile | +4 | -1 |
libfiu/Makefile | +19 | -3 |
libfiu/backtrace.c | +82 | -0 |
libfiu/fiu-control.h | +41 | -0 |
libfiu/fiu.c | +82 | -0 |
libfiu/internal.h | +16 | -0 |
libfiu/libfiu.3 | +39 | -0 |
tests/Makefile | +62 | -0 |
tests/test-1.c | +46 | -0 |
tests/test-2.c | +46 | -0 |
diff --git a/.gitignore b/.gitignore index 72d4573..8b1881d 100644 --- a/.gitignore +++ b/.gitignore @@ -25,4 +25,6 @@ preload/run/*.so preload/run/fiu-run preload/run/build-flags preload/run/build-needlibdl - +tests/*.o +tests/build-flags +tests/test-? diff --git a/Makefile b/Makefile index 9d418e1..c980b21 100644 --- a/Makefile +++ b/Makefile @@ -51,6 +51,8 @@ utils_install: utils utils_uninstall: $(MAKE) -C utils uninstall +test: libfiu + $(MAKE) -C tests bindings: python2 python3 @@ -82,6 +84,7 @@ clean: python_clean preload_clean libfiu_clean utils_clean python2 python2_install python3 python3_install python_clean \ bindings bindings_install bindings_clean \ preload preload_clean preload_install preload_uninstall \ - utils utils_clean utils_install utils_uninstall + utils utils_clean utils_install utils_uninstall \ + test diff --git a/libfiu/Makefile b/libfiu/Makefile index 07ebfca..1ddfddf 100644 --- a/libfiu/Makefile +++ b/libfiu/Makefile @@ -21,7 +21,7 @@ DESTDIR=$(PREFIX) INSTALL=install -OBJS = fiu.o fiu-rc.o +OBJS = fiu.o fiu-rc.o backtrace.o ifneq ($(V), 1) @@ -34,6 +34,17 @@ LIB_VER=0.14 LIB_SO_VER=0 +# We attempt to detect if we have the extensions we use in backtrace.c by just +# attempting to compile it normally, and fall back to the dummy version if +# that fails. Not very sophisticated but should be safe. +USE_DUMMY_BACKTRACE := $(shell $(CC) -c backtrace.c -o /dev/null 2>/dev/null || \ + echo -DDUMMY_BACKTRACE) +ifndef USE_DUMMY_BACKTRACE +# The real backtrace needs linking against libdl. +USE_LIBDL = -ldl +endif + + default: all all: libs libfiu.pc @@ -50,8 +61,9 @@ libs: libfiu.so libfiu.a libfiu.so: build-flags fiu.h $(OBJS) $(NICE_CC) $(ALL_CFLAGS) -shared -fPIC \ -Wl,-soname,libfiu.so.$(LIB_SO_VER) \ - $(OBJS) -lpthread -o libfiu.so.$(LIB_VER) + $(OBJS) -lpthread $(USE_LIBDL) -o libfiu.so.$(LIB_VER) ln -fs libfiu.so.$(LIB_VER) libfiu.so + ln -fs libfiu.so.$(LIB_VER) libfiu.so.$(LIB_SO_VER) libfiu.a: build-flags fiu.h $(OBJS) $(AR) cr libfiu.a $(OBJS) @@ -104,18 +116,22 @@ build-flags: .force-build-flags echo "$(BF)" > build-flags; \ fi + $(OBJS): build-flags .c.o: $(NICE_CC) $(ALL_CFLAGS) -c $< -o $@ +backtrace.o: ALL_CFLAGS += $(USE_DUMMY_BACKTRACE) + doxygen: $(MAKE) LIB_VER=$(LIB_VER) -C doxygen clean: - rm -f libfiu.pc $(OBJS) libfiu.so libfiu.so.$(LIB_VER) libfiu.a + rm -f libfiu.pc $(OBJS) + rm -f libfiu.so libfiu.so.$(LIB_VER) libfiu.so.$(LIB_SO_VER) libfiu.a rm -f *.bb *.bbg *.da *.gcov *.gcda *.gcno gmon.out build-flags $(MAKE) -C doxygen $@ diff --git a/libfiu/backtrace.c b/libfiu/backtrace.c new file mode 100644 index 0000000..fc44d9a --- /dev/null +++ b/libfiu/backtrace.c @@ -0,0 +1,82 @@ + +/* Since our implementation relies on nonstandard functions and headers, we + * provide a dummy one below for compatibility purposes. The dummy version can + * be selected at build time using -DDUMMY_BACKTRACE. */ +#ifndef DUMMY_BACKTRACE + +/* This is needed for some of the functions below. */ +#define _GNU_SOURCE + +#include <execinfo.h> +#include <dlfcn.h> +#include <sys/procfs.h> +#include <link.h> + + +int get_backtrace(void *buffer, int size) +{ + return backtrace(buffer, size); +} + +void *get_func_start(void *pc) +{ + int r; + Dl_info info; + + r = dladdr(pc, &info); + if (r == 0) + return NULL; + + return info.dli_saddr; +} + +void *get_func_end(void *func) +{ + int r; + Dl_info dl_info; + ElfW(Sym) *elf_info; + + r = dladdr1(func, &dl_info, (void **) &elf_info, RTLD_DL_SYMENT); + if (r == 0) + return NULL; + if (elf_info == NULL) + return NULL; + if (dl_info.dli_saddr == NULL) + return NULL; + + return ((unsigned char *) func) + elf_info->st_size; +} + +void *get_func_addr(const char *func_name) +{ + return dlsym(RTLD_DEFAULT, func_name); +} + +#else +/* Dummy versions */ + +#warning Using dummy versions of backtrace + +#include <stddef.h> /* for NULL */ + +int get_backtrace(void *buffer, int size) +{ + return 0; +} + +void *get_func_end(void *pc) +{ + return NULL; +} + +void *get_func_start(void *pc) +{ + return NULL; +} + +void *get_func_addr(const char *func_name) +{ + return NULL; +} + +#endif // DUMMY_BACKTRACE diff --git a/libfiu/fiu-control.h b/libfiu/fiu-control.h index 1a8dfff..cb2b0a4 100644 --- a/libfiu/fiu-control.h +++ b/libfiu/fiu-control.h @@ -69,6 +69,47 @@ typedef int external_cb_t(const char *name, int *failnum, void **failinfo, int fiu_enable_external(const char *name, int failnum, void *failinfo, unsigned int flags, external_cb_t *external_cb); +/* Enables the given point of failure, but only if the given function is in + * the stack at the given position. + * + * This function relies on GNU extensions such as backtrace() and dladdr(), so + * it may not be available on your platform. + * + * - name: point of failure name. + * - failnum: what will fiu_fail() return, must be != 0. + * - failinfo: what will fiu_failinfo() return. + * - flags: flags. + * - func: pointer to the function. + * - func_pos: position where we expect the function to be; use -1 for "any". + * Values other than -1 are not supported at the moment, but will be in + * the future. + */ +int fiu_enable_stack(const char *name, int failnum, void *failinfo, + unsigned int flags, void *func, int func_pos_in_stack); + +/** Enables the given point of failure, but only if 'func_name' is in + * the stack at 'func_pos_in_stack'. + * + * This function relies on GNU extensions such as backtrace() and dladdr(), so + * if your platform does not support them, it will always return failure. + * + * It is exactly like fiu_enable_stack() but takes a function name, it will + * ask the dynamic linker to find the corresponding function pointer. + * + * @param name Name of the point of failure to enable. + * @param failnum What will fiu_fail() return, must be != 0. + * @param failinfo What will fiu_failinfo() return. + * @param flags Flags. + * @param func_name Name of the function. + * @param func_pos_in_stack Position where we expect the function to be; use + * -1 for "any". Values other than -1 are not supported at the + * moment, but will be in the future. + * @returns 0 if success, < 0 otherwise. + */ +int fiu_enable_stack_by_name(const char *name, int failnum, void *failinfo, + unsigned int flags, const char *func_name, + int func_pos_in_stack); + /** Disables the given point of failure. That makes it NOT fail. * * @param name Name of the point of failure to disable. diff --git a/libfiu/fiu.c b/libfiu/fiu.c index 1a7e0a4..cff61bc 100644 --- a/libfiu/fiu.c +++ b/libfiu/fiu.c @@ -19,6 +19,7 @@ enum pf_method { PF_ALWAYS = 1, PF_PROB, PF_EXTERNAL, + PF_STACK, }; /* Point of failure information */ @@ -39,6 +40,12 @@ struct pf_info { /* To use when method == PF_EXTERNAL */ external_cb_t *external_cb; + /* To use when method == PF_STACK */ + struct stack { + void *func_start; + void *func_end; + int func_pos_in_stack; + } stack; } minfo; }; @@ -167,6 +174,41 @@ static int shrink_enabled_fails(void) return 0; } +/* Determines if the given address is within the function code. */ +static int pc_in_func(struct pf_info *pf, void *pc) +{ + /* We don't know if the platform allows us to know func_end, + * so we use different methods depending on its availability. */ + if (pf->minfo.stack.func_end) { + return (pc > pf->minfo.stack.func_start && + pc < pf->minfo.stack.func_end); + } else { + return pf->minfo.stack.func_start == get_func_start(pc); + } +} + +/* Determines wether to fail or not the given failure point, which is of type + * PF_STACK. Returns 1 if it should fail, or 0 if it should not. */ +static int should_stack_fail(struct pf_info *pf) +{ + // TODO: Find the right offset for pos_in_stack: we should look for + // fiu_fail(), and start counting from there. + int nptrs, i; + void *buffer[100]; + + nptrs = get_backtrace(buffer, 100); + + for (i = 0; i < nptrs; i++) { + if (pc_in_func(pf, buffer[i]) && + (pf->minfo.stack.func_pos_in_stack == -1 || + i == pf->minfo.stack.func_pos_in_stack)) { + return 1; + } + } + + return 0; +} + /* Pseudorandom number generator. * * The performance of the PRNG is very sensitive to us, so we implement our @@ -291,6 +333,10 @@ int fiu_fail(const char *name) &(pf->flags))) goto exit_fail; break; + case PF_STACK: + if (should_stack_fail(pf)) + goto exit_fail; + break; default: break; } @@ -460,6 +506,42 @@ int fiu_enable_external(const char *name, int failnum, void *failinfo, return 0; } +/* Makes the given name fail when func is in the stack at func_pos. + * If func_pos is -1, then any position will match. */ +int fiu_enable_stack(const char *name, int failnum, void *failinfo, + unsigned int flags, void *func, int func_pos_in_stack) +{ + struct pf_info *pf; + + /* Specifying the stack position is unsupported for now */ + if (func_pos_in_stack != -1) + return -1; + + pf = insert_new_fail(name, failnum, failinfo, flags, PF_STACK); + if (pf == NULL) + return -1; + + pf->minfo.stack.func_start = func; + pf->minfo.stack.func_end = get_func_end(func); + pf->minfo.stack.func_pos_in_stack = func_pos_in_stack; + return 0; +} + +/* Same as fiu_enable_stack(), but takes a function name. */ +int fiu_enable_stack_by_name(const char *name, int failnum, void *failinfo, + unsigned int flags, const char *func_name, + int func_pos_in_stack) +{ + void *fp; + + fp = get_func_addr(func_name); + if (fp == NULL) + return -1; + + return fiu_enable_stack(name, failnum, failinfo, flags, fp, + func_pos_in_stack); +} + /* Makes the given name NOT fail. */ int fiu_disable(const char *name) { diff --git a/libfiu/internal.h b/libfiu/internal.h index 2c3bfba..1cb95ff 100644 --- a/libfiu/internal.h +++ b/libfiu/internal.h @@ -7,5 +7,21 @@ /* Recursion count, used both in fiu.c and fiu-rc.c */ extern __thread int rec_count; + +/* Gets a stack trace. The pointers are stored in the given buffer, which must + * be of the given size. The number of entries is returned. + * It's a wrapper around glibc's backtrace(). */ +int get_backtrace(void *buffer, int size); + +/* Returns a pointer to the start of the function containing the given code + * address, or NULL if it can't find any. */ +void *get_func_end(void *pc); + +/* Returns a pointer to the end of the given function. */ +void *get_func_start(void *func); + +/* Returns a pointer to the function given by name. */ +void *get_func_addr(const char *func_name); + #endif diff --git a/libfiu/libfiu.3 b/libfiu/libfiu.3 index 32e0939..ac9b406 100644 --- a/libfiu/libfiu.3 +++ b/libfiu/libfiu.3 @@ -25,6 +25,12 @@ libfiu - Fault injection in userspace .BI "int fiu_enable_external(const char *" name ", int " failnum "," .BI " void *" failinfo ", unsigned int " flags "," .BI " external_cb_t *" external_cb ");" +.BI "int fiu_enable_stack(const char *" name ", int " failnum "," +.BI " void *" failinfo ", unsigned int " flags "," +.BI " void *" func ", int " func_pos_in_stack ");" +.BI "int fiu_enable_stack_by_name(const char *" name ", int " failnum "," +.BI " void *" failinfo ", unsigned int " flags "," +.BI " const char *" func_name ", int " func_pos_in_stack ");" .BI "int fiu_disable(const char *" name ");" .BI "int fiu_rc_fifo(const char *" basename ");" .sp @@ -161,6 +167,39 @@ to the given external function, which should return 0 if it is not to fail, or same as the ones in .BR fiu_enable() . +.TP +.BI "int fiu_enable_stack(" name ", " failnum ", " failinfo ", " flags ", " func ", " func_pos_in_stack ")" +Enables the given point of failure, but only if +.I func +is in the stack at +.IR func_pos_in_stack . + +.I func +must be a function pointer, and +.I func_pos_in_stack +is the position where we expect the function to be, or -1 for "any" (values +other than -1 are not yet supported). The rest of the parameters, as well as +the return value, are the same as the ones in +.BR fiu_enable() . + +This function relies on some GNU extensions, so it may be not available in all +platforms. + +.TP +.BI "int fiu_enable_stack_by_name(" name ", " failnum ", " failinfo ", " flags ", " func_name ", " func_pos_in_stack ")" +Enables the given point of failure, but only if +.I func_name +is in the stack at +.IR func_pos_in_stack . + +.I func +must be the name of a function (resolved at runtime using dlsym()); the rest +of the parameters, as well as the return value, are the same as the ones in +.BR fiu_enable_stack . + +This function relies on some GNU extensions, so it may be not available in all +platforms. + .TP .BI "fiu_disable(" name ")" Disables the given point of failure, undoing the actions of the diff --git a/tests/Makefile b/tests/Makefile new file mode 100644 index 0000000..6dee4a9 --- /dev/null +++ b/tests/Makefile @@ -0,0 +1,62 @@ + +CFLAGS += -std=c99 -pedantic -Wall -rdynamic +ALL_CFLAGS = -I../libfiu/ -L../libfiu/ \ + -D_XOPEN_SOURCE=600 -D_GNU_SOURCE -fPIC -DFIU_ENABLE=1 $(CFLAGS) + +ifdef DEBUG +ALL_CFLAGS += -g +endif + +ifdef PROFILE +ALL_CFLAGS += -g -pg -fprofile-arcs -ftest-coverage +endif + + +OBJS = test-1.o test-2.o + + +ifneq ($(V), 1) + NICE_CC = @echo " CC $@"; $(CC) + NICE_RUN = @echo " RUN $<"; LD_LIBRARY_PATH=../libfiu/ +else + NICE_CC = $(CC) + NICE_RUN = LD_LIBRARY_PATH=../libfiu/ +endif + +default: tests + +all: tests + +tests: run-test-1 run-test-2 + +test-%: test-%.o + $(NICE_CC) $(ALL_CFLAGS) $< -lfiu -o $@ + +run-%: % FORCE + $(NICE_RUN) ./$< + + +BF = $(ALL_CFLAGS) ~ $(PREFIX) +build-flags: .force-build-flags + @if [ x"$(BF)" != x"`cat build-flags 2>/dev/null`" ]; then \ + if [ -f build-flags ]; then \ + echo "build flags changed, rebuilding"; \ + fi; \ + echo "$(BF)" > build-flags; \ + fi + +$(OBJS): build-flags + +.c.o: + $(NICE_CC) $(ALL_CFLAGS) -c $< -o $@ + +clean: + rm -f $(OBJS) test1 + rm -f *.bb *.bbg *.da *.gcov *.gcda *.gcno gmon.out build-flags + +FORCE: + +.PHONY: default all clean \ + .force-build-flags + + diff --git a/tests/test-1.c b/tests/test-1.c new file mode 100644 index 0000000..588390c --- /dev/null +++ b/tests/test-1.c @@ -0,0 +1,46 @@ + +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> + +#include <fiu.h> +#include <fiu-control.h> + +void func1(int should_fail) +{ + int failed; + + /* + int nptrs; + void *buffer[100]; + nptrs = backtrace(buffer, 100); + backtrace_symbols_fd(buffer, nptrs, 1); + */ + + failed = fiu_fail("fp-1") != 0; + assert(failed == should_fail); +} + +void func2(int should_fail) +{ + func1(should_fail); +} + + +int main(void) +{ + fiu_init(0); + fiu_enable_stack("fp-1", 1, NULL, 0, (void *) &func2, -1); + + func1(0); + func2(1); + + + fiu_disable("fp-1"); + + func1(0); + func2(0); + + return 0; +} + diff --git a/tests/test-2.c b/tests/test-2.c new file mode 100644 index 0000000..78c56ce --- /dev/null +++ b/tests/test-2.c @@ -0,0 +1,46 @@ + +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> + +#include <fiu.h> +#include <fiu-control.h> + +void func1(int should_fail) +{ + int failed; + + /* + int nptrs; + void *buffer[100]; + nptrs = backtrace(buffer, 100); + backtrace_symbols_fd(buffer, nptrs, 1); + */ + + failed = fiu_fail("fp-1") != 0; + assert(failed == should_fail); +} + +void func2(int should_fail) +{ + func1(should_fail); +} + + +int main(void) +{ + fiu_init(0); + fiu_enable_stack_by_name("fp-1", 1, NULL, 0, "func2", -1); + + func1(0); + func2(1); + + + fiu_disable("fp-1"); + + func1(0); + func2(0); + + return 0; +} +