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/include/common/standard.h b/include/common/standard.h
index 5cd0564..b278f08 100644
--- a/include/common/standard.h
+++ b/include/common/standard.h
@@ -1485,6 +1485,7 @@
void dump_addr_and_bytes(struct buffer *buf, const char *pfx, const void *addr, int n);
void dump_hex(struct buffer *out, const char *pfx, const void *buf, int len, int unsafe);
int may_access(const void *ptr);
+void *resolve_sym_name(struct buffer *buf, const char *pfx, void *addr);
/* same as realloc() except that ptr is also freed upon failure */
static inline void *my_realloc2(void *ptr, size_t size)
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;
}
/*