MINOR: tools: add resolve_sym_name() to resolve function pointers

We use various hacks at a few places to try to identify known function
pointers in debugging outputs (show threads & show fd). Let's centralize
this into a new function dedicated to this. It already knows about the
functions matched by "show threads" and "show fd", and when built with
USE_DL, it can rely on dladdr1() to resolve other functions. There are
some limitations, as static functions are not resolved, linking with
-rdynamic is mandatory, and even then some functions will not necessarily
appear. It's possible to do a better job by rebuilding the whole symbol
table from the ELF headers in memory but it's less portable and the gains
are still limited, so this solution remains a reasonable tradeoff.
diff --git a/src/standard.c b/src/standard.c
index bd148ee..5c4daf2 100644
--- a/src/standard.c
+++ b/src/standard.c
@@ -10,6 +10,12 @@
  *
  */
 
+#if defined(USE_DL)
+#define _GNU_SOURCE
+#include <dlfcn.h>
+#include <link.h>
+#endif
+
 #include <ctype.h>
 #include <errno.h>
 #include <netdb.h>
@@ -31,7 +37,15 @@
 #include <common/standard.h>
 #include <common/tools.h>
 #include <types/global.h>
+#include <proto/applet.h>
 #include <proto/dns.h>
+#include <proto/hlua.h>
+#include <proto/listener.h>
+#include <proto/proto_udp.h>
+#include <proto/ssl_sock.h>
+#include <proto/stream_interface.h>
+#include <proto/task.h>
+
 #include <eb32tree.h>
 #include <eb32sctree.h>
 
@@ -4319,6 +4333,120 @@
 		}
 		fputc('\n', out);
 	}
+}
+
+/* Tries to append to buffer <buf> some indications about the symbol at address
+ * <addr> using the following form:
+ *   lib:+0xoffset              (unresolvable address from lib's base)
+ *   main+0xoffset              (unresolvable address from main (+/-))
+ *   lib:main+0xoffset          (unresolvable lib address from main (+/-))
+ *   name                       (resolved exact exec address)
+ *   lib:name                   (resolved exact lib address)
+ *   name+0xoffset/0xsize       (resolved address within exec symbol)
+ *   lib:name+0xoffset/0xsize   (resolved address within lib symbol)
+ *
+ * The file name (lib or executable) is limited to what lies between the last
+ * '/' and the first following '.'. An optional prefix <pfx> is prepended before
+ * the output if not null. The file is not dumped when it's the same as the one
+ * that contains the "main" symbol, or when USE_DL is not set.
+ *
+ * The symbol's base address is returned, or NULL when unresolved, in order to
+ * allow the caller to match it against known ones.
+ */
+void *resolve_sym_name(struct buffer *buf, const char *pfx, void *addr)
+{
+	const struct {
+		const void *func;
+		const char *name;
+	} fcts[] = {
+		{ .func = process_stream, .name = "process_stream" },
+		{ .func = task_run_applet, .name = "task_run_applet" },
+		{ .func = si_cs_io_cb, .name = "si_cs_io_cb" },
+		{ .func = conn_fd_handler, .name = "conn_fd_handler" },
+		{ .func = dgram_fd_handler, .name = "dgram_fd_handler" },
+		{ .func = listener_accept, .name = "listener_accept" },
+		{ .func = poller_pipe_io_handler, .name = "poller_pipe_io_handler" },
+		{ .func = mworker_accept_wrapper, .name = "mworker_accept_wrapper" },
+#ifdef USE_LUA
+		{ .func = hlua_process_task, .name = "hlua_process_task" },
+#endif
+#if defined(USE_OPENSSL) && (HA_OPENSSL_VERSION_NUMBER >= 0x1010000fL) && !defined(OPENSSL_NO_ASYNC)
+		{ .func = ssl_async_fd_free, .name = "ssl_async_fd_free" },
+		{ .func = ssl_async_fd_handler, .name = "ssl_async_fd_handler" },
+#endif
+	};
+
+#ifdef USE_DL
+	Dl_info dli, dli_main;
+	const ElfW(Sym) *sym;
+	const char *fname, *p;
+#endif
+	int i;
+
+	if (pfx)
+		chunk_appendf(buf, "%s", pfx);
+
+	for (i = 0; i < sizeof(fcts) / sizeof(fcts[0]); i++) {
+		if (addr == fcts[i].func) {
+			chunk_appendf(buf, "%s", fcts[i].name);
+			return addr;
+		}
+	}
+
+#ifdef USE_DL
+	/* Now let's try to be smarter */
+#ifdef __USE_GNU // most detailed one
+	if (!dladdr1(addr, &dli, (void **)&sym, RTLD_DL_SYMENT))
+		goto unknown;
+#else
+	if (!dladdr(addr, &dli))
+		goto unknown;
+	sym = NULL;
+#endif
+
+	/* 1. prefix the library name if it's not the same object as the one
+	 * that contains the main function. The name is picked between last '/'
+	 * and first following '.'.
+	 */
+	if (!dladdr(main, &dli_main))
+		dli_main.dli_fbase = NULL;
+
+	if (dli_main.dli_fbase != dli.dli_fbase) {
+		fname = dli.dli_fname;
+		p = strrchr(fname, '/');
+		if (p++)
+			fname = p;
+		p = strchr(fname, '.');
+		if (!p)
+			p = fname + strlen(fname);
+
+		chunk_appendf(buf, "%.*s:", (int)(long)(p - fname), fname);
+	}
+
+	/* 2. symbol name */
+	if (dli.dli_sname) {
+		/* known, dump it and return symbol's address (exact or relative) */
+		chunk_appendf(buf, "%s", dli.dli_sname);
+		if (addr != dli.dli_saddr) {
+			chunk_appendf(buf, "+%#lx", (long)(addr - dli.dli_saddr));
+			if (sym)
+				chunk_appendf(buf, "/%#lx", (long)sym->st_size);
+		}
+		return dli.dli_saddr;
+	}
+	else if (dli_main.dli_fbase != dli.dli_fbase) {
+		/* unresolved symbol from a known library, report relative offset */
+		chunk_appendf(buf, "+%#lx", (long)(addr - dli.dli_fbase));
+		return NULL;
+	}
+#endif /* USE_DL */
+ unknown:
+	/* unresolved symbol from the main file, report relative offset to main */
+	if ((void*)addr < (void*)main)
+		chunk_appendf(buf, "main-%#lx", (long)((void*)main - addr));
+	else
+		chunk_appendf(buf, "main+%#lx", (long)(addr - (void*)main));
+	return NULL;
 }
 
 /*