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.
This commit is contained in:
Willy Tarreau 2020-03-03 17:09:08 +01:00
parent 762fb3ec8e
commit eb8b1ca3eb
2 changed files with 129 additions and 0 deletions

View File

@ -1485,6 +1485,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)

View File

@ -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>
@ -4321,6 +4335,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.