git » libfiu » commit b17a81b

libfiu: Implement fiu_enable_stack() and fiu_enable_stack_by_name()

author Alberto Bertogli
2012-03-27 23:52:09 UTC
committer Alberto Bertogli
2012-03-27 23:59:19 UTC
parent 7cb3d44c9c8dfeb70064827447ef8d9005f0c420

libfiu: Implement fiu_enable_stack() and fiu_enable_stack_by_name()

This patch implements two new ways of enable points of failure:
fiu_enable_stack() and fiu_enable_stack_by_name().

It allows the user to say "enable the point of failure X, if the function F is
on the stack".

When X is evaluated, it traverses the stack looking for F, and makes the point
fail if the function is found. This relies on some GNU-specific functions, so
it's not portable, but it should fail gracefully on platforms where it's not
available.

There's support in the API for specifying the position of the function F,
although it has not been implemented yet.

Some very basic tests are introduced, more tests will probably be added in
later patches.

Signed-off-by: Alberto Bertogli <albertito@blitiri.com.ar>

.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;
+}
+