From 8b8713b2bf6f44c88c5a2936ceceebed865ae35f Mon Sep 17 00:00:00 2001
From: Alberto Bertogli <albertito@blitiri.com.ar>
Date: Sun, 2 Sep 2012 23:46:49 +0100
Subject: [PATCH 12/18] libfiu: Test if backtrace works in fiu_enable_stack()

In fiu_enable_stack(), test the backtrace-related functions to make sure they
work before adding the new failure point.

That way, the caller will know if the function isn't available on the platform
instead of the failure point not working when it should.

We need it because even on platforms where backtrace() is available, it may
not be working properly (because of build options, broken glibc, etc.).

The tester function takes the caller as a parameter to work around compiler
inlining issues that can result in a false negative.

Signed-off-by: Alberto Bertogli <albertito@blitiri.com.ar>
---
 libfiu/backtrace.c |   66 ++++++++++++++++++++++++++++++++++++++++++++++++++-
 libfiu/fiu.c       |    2 +-
 libfiu/internal.h  |    7 +++--
 3 files changed, 69 insertions(+), 6 deletions(-)

diff --git a/libfiu/backtrace.c b/libfiu/backtrace.c
index 1ac8d39..c85da04 100644
--- a/libfiu/backtrace.c
+++ b/libfiu/backtrace.c
@@ -13,7 +13,6 @@
 #include <sys/procfs.h>
 #include <link.h>
 
-const int have_backtrace = 1;
 
 int get_backtrace(void *buffer, int size)
 {
@@ -61,7 +60,6 @@ void *get_func_addr(const char *func_name)
 
 #include <stddef.h>	/* for NULL */
 
-const int have_backtrace = 0;
 
 int get_backtrace(void *buffer, int size)
 {
@@ -84,3 +82,67 @@ void *get_func_addr(const char *func_name)
 }
 
 #endif // DUMMY_BACKTRACE
+
+/* Ugly but useful conversion from function pointer to void *.
+ * This is not guaranteed by the standard, but has to work on all platforms
+ * where we support backtrace(), because that function assumes it so. */
+static void *fp_to_voidp(void (*funcp)())
+{
+	unsigned char **p;
+	p = (unsigned char **) &funcp;
+	return *p;
+}
+
+int backtrace_works(void (*caller)())
+{
+	/* We remember the result so we don't have to compute it over an over
+	 * again, we know it doesn't change. */
+	static int works = -1;
+
+	void *start = NULL;
+	void *end = NULL;
+	void *bt_buffer[100];
+	void *pc;
+	int nptrs, i;
+
+	/* Return the result if we know it. */
+	if (works >= 0)
+		return works;
+
+	nptrs = get_backtrace(bt_buffer, 100);
+	if (nptrs <= 0) {
+		works = 0;
+		return works;
+	}
+
+	/* We will detect if it works by looking for the caller in the
+	 * backtrace. */
+	start = get_func_start(fp_to_voidp(caller));
+	end = get_func_end(fp_to_voidp(caller));
+
+	if (start == NULL && end == NULL) {
+		works = 0;
+		return works;
+	}
+
+	for (i = 0; i < nptrs; i++) {
+		pc = bt_buffer[i];
+
+		/* On some platforms, we have everything except
+		 * get_func_end(), and that's ok. */
+		if (end) {
+			if (pc >= start && pc <= end) {
+				works = 1;
+				return works;
+			}
+		} else {
+			if (get_func_start(pc) == start) {
+				works = 1;
+				return works;
+			}
+		}
+	}
+
+	works = 0;
+	return works;
+}
diff --git a/libfiu/fiu.c b/libfiu/fiu.c
index 01f0617..d9ea787 100644
--- a/libfiu/fiu.c
+++ b/libfiu/fiu.c
@@ -517,7 +517,7 @@ int fiu_enable_stack(const char *name, int failnum, void *failinfo,
 	if (func_pos_in_stack != -1)
 		return -1;
 
-	if (have_backtrace == 0)
+	if (backtrace_works((void (*)()) fiu_enable_stack) == 0)
 		return -1;
 
 	pf = insert_new_fail(name, failnum, failinfo, flags, PF_STACK);
diff --git a/libfiu/internal.h b/libfiu/internal.h
index f804735..6e25554 100644
--- a/libfiu/internal.h
+++ b/libfiu/internal.h
@@ -7,9 +7,6 @@
 /* Recursion count, used both in fiu.c and fiu-rc.c */
 extern __thread int rec_count;
 
-/* Are these backtrace-related functions available, or dummies? */
-extern const int have_backtrace;
-
 /* 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(). */
@@ -25,5 +22,9 @@ void *get_func_start(void *func);
 /* Returns a pointer to the function given by name. */
 void *get_func_addr(const char *func_name);
 
+/* Do the above backtrace-related functions work?
+ * Takes a pointer to the caller so it can verify it's on the stack. */
+int backtrace_works(void (*caller)());
+
 #endif
 
-- 
1.6.2.2.646.gb214

