BUG/MEDIUM: tools: avoid calling dlsym() in static builds (try 2)

The first approach in commit 288dc1d8e ("BUG/MEDIUM: tools: avoid calling
dlsym() in static builds") relied on dlopen() but on certain configs (at
least gcc-4.8+ld-2.27+glibc-2.17) it used to catch situations where it
ought not fail.

Let's have a second try on this using dladdr() instead. The variable was
renamed "build_is_static" as it's exactly what's being detected there.
We could even take it for reporting in -vv though that doesn't seem very
useful. At least the variable was made global to ease inspection via the
debugger, or in case it's useful later.

Now it properly detects a static build even with gcc-4.4+glibc-2.11.1 and
doesn't crash anymore.

(cherry picked from commit 5b3cd9561bdbf7bac7db04bafead76125f1458ae)
Signed-off-by: Christopher Faulet <cfaulet@haproxy.com>
(cherry picked from commit 1dbb19401536db79d394a39da5758838e483659d)
Signed-off-by: Christopher Faulet <cfaulet@haproxy.com>
(cherry picked from commit cf3050812faf628e1beda1dfbe9cabdb8f06f60a)
Signed-off-by: Christopher Faulet <cfaulet@haproxy.com>
diff --git a/include/haproxy/tools.h b/include/haproxy/tools.h
index a564f63..52f0c90 100644
--- a/include/haproxy/tools.h
+++ b/include/haproxy/tools.h
@@ -70,6 +70,7 @@
  */
 extern THREAD_LOCAL int itoa_idx; /* index of next itoa_str to use */
 extern THREAD_LOCAL char itoa_str[][171];
+extern int build_is_static;
 extern char *ultoa_r(unsigned long n, char *buffer, int size);
 extern char *lltoa_r(long long int n, char *buffer, int size);
 extern char *sltoa_r(long n, char *buffer, int size);
diff --git a/src/tools.c b/src/tools.c
index c381f21..da0c8d7 100644
--- a/src/tools.c
+++ b/src/tools.c
@@ -95,6 +95,9 @@
  */
 THREAD_LOCAL unsigned int statistical_prng_state = 2463534242U;
 
+/* set to true if this is a static build */
+int build_is_static = 0;
+
 /*
  * unsigned long long ASCII representation
  *
@@ -4826,22 +4829,25 @@
 	return ret;
 }
 
-/* dlopen() support: 0=no/not yet checked, 1=ok */
-static int dlopen_usable = 0;
-
-/* Sets dlopen_usable to true if dlopen() works and is usable. We verify if
- * we're in a static build because some old glibcs used to crash in dlsym()
- * in this case.
+/* Sets build_is_static to true if we detect a static build. Some older glibcs
+ * tend to crash inside dlsym() in static builds, but tests show that at least
+ * dladdr() still works (and will fail to resolve anything of course). Thus we
+ * try to determine if we're on a static build to avoid calling dlsym() in this
+ * case.
  */
-void check_if_dlopen_usable()
+void check_if_static_build()
 {
-	/* dlopen(NULL) returns a handle to the main program or NULL
-	 * on static builds.
-	 */
-	dlopen_usable = dlopen(NULL, RTLD_LAZY) ? 1 : 0;
+	Dl_info dli = { };
+	size_t size = 0;
+
+	/* Now let's try to be smarter */
+	if (!dladdr_and_size(&main, &dli, &size))
+		build_is_static = 1;
+	else
+		build_is_static = 0;
 }
 
-INITCALL0(STG_PREPARE, check_if_dlopen_usable);
+INITCALL0(STG_PREPARE, check_if_static_build);
 
 /* Tries to retrieve the address of the first occurrence symbol <name>.
  * Note that NULL in return is not always an error as a symbol may have that
@@ -4852,7 +4858,7 @@
 	void *ptr = NULL;
 
 #ifdef RTLD_DEFAULT
-	if (dlopen_usable)
+	if (!build_is_static)
 		ptr = dlsym(RTLD_DEFAULT, name);
 #endif
 	return ptr;
@@ -4868,7 +4874,7 @@
 	void *ptr = NULL;
 
 #ifdef RTLD_NEXT
-	if (dlopen_usable)
+	if (!build_is_static)
 		ptr = dlsym(RTLD_NEXT, name);
 #endif
 	return ptr;