MINOR: tools: add resolve_sym_name() to resolve function pointers
authorWilly Tarreau <w@1wt.eu>
Tue, 3 Mar 2020 16:09:08 +0000 (17:09 +0100)
committerWilly Tarreau <w@1wt.eu>
Fri, 1 May 2020 15:09:20 +0000 (17:09 +0200)
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.

(cherry picked from commit eb8b1ca3eb4c8d4688e1a4a1d9c1b91f68324e09)
Signed-off-by: Willy Tarreau <w@1wt.eu>

include/common/standard.h
src/standard.c

index e8b6917..74e294a 100644 (file)
@@ -1499,6 +1499,7 @@ int dump_text_line(struct buffer *out, const char *buf, int bsize, int len,
 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)
index a75ab88..4c1bcd6 100644 (file)
  *
  */
 
+#if defined(USE_DL)
+#define _GNU_SOURCE
+#include <dlfcn.h>
+#include <link.h>
+#endif
+
 #include <ctype.h>
 #include <errno.h>
 #include <netdb.h>
 #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>
 
@@ -4329,6 +4343,120 @@ void debug_hexdump(FILE *out, const char *pfx, const char *buf,
        }
 }
 
+/* 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;
+}
+
 /*
  * Allocate an array of unsigned int with <nums> as address from <str> string
  * made of integer sepereated by dot characters.