diff --git a/doc/management.txt b/doc/management.txt index a5ecfa005..36456d518 100644 --- a/doc/management.txt +++ b/doc/management.txt @@ -2623,6 +2623,19 @@ show info [typed|json] [desc] [float] $ echo "show info json" | socat /var/run/haproxy.sock stdio | \ python -m json.tool +show libs + Dump the list of loaded shared dynamic libraries and object files, on systems + that support it. When available, for each shared object the range of virtual + addresses will be indicated, the size and the path to the object. This can be + used for example to try to estimate what library provides a function that + appears in a dump. Note that on many systems, addresses will change upon each + restart (address space randomization), so that this list would need to be + retrieved upon startup if it is expected to be used to analyse a core file. + This command may only be issued on sockets configured for levels "operator" + or "admin". Note that the output format may vary between operating systems, + architectures and even haproxy versions, and ought not to be relied on in + scripts. + show map [[@] ] Dump info about map converters. Without argument, the list of all available maps is returned. If a is specified, its contents are dumped. is diff --git a/include/haproxy/compat.h b/include/haproxy/compat.h index 4e6cb7271..ed7e87051 100644 --- a/include/haproxy/compat.h +++ b/include/haproxy/compat.h @@ -250,6 +250,7 @@ typedef struct { } empty_t; /* dl_iterate_phdr() is available in GLIBC 2.2.4 and up. Let's round up to 2.3.x */ #if defined(USE_DL) && defined(__GNU_LIBRARY__) && (__GLIBC__ > 2 || __GLIBC__ == 2 && __GLIBC_MINOR__ >= 3) #define HA_HAVE_DL_ITERATE_PHDR +#define HA_HAVE_DUMP_LIBS #endif /* malloc_trim() can be very convenient to reclaim unused memory especially diff --git a/include/haproxy/tools.h b/include/haproxy/tools.h index 87ccb31aa..1a1751597 100644 --- a/include/haproxy/tools.h +++ b/include/haproxy/tools.h @@ -984,6 +984,7 @@ const void *resolve_sym_name(struct buffer *buf, const char *pfx, const void *ad const char *get_exec_path(void); void *get_sym_curr_addr(const char *name); void *get_sym_next_addr(const char *name); +int dump_libs(struct buffer *output, int with_addr); /* Note that this may result in opening libgcc() on first call, so it may need * to have been called once before chrooting. diff --git a/src/debug.c b/src/debug.c index 056199208..5accb0bd5 100644 --- a/src/debug.c +++ b/src/debug.c @@ -305,6 +305,21 @@ static int cli_io_handler_show_threads(struct appctx *appctx) return 1; } +#if defined(HA_HAVE_DUMP_LIBS) +/* parse a "show libs" command. It returns 1 if it emits anything otherwise zero. */ +static int debug_parse_cli_show_libs(char **args, char *payload, struct appctx *appctx, void *private) +{ + if (!cli_has_level(appctx, ACCESS_LVL_OPER)) + return 1; + + chunk_reset(&trash); + if (dump_libs(&trash, 1)) + return cli_msg(appctx, LOG_INFO, trash.area); + else + return 0; +} +#endif + /* dumps a state of all threads into the trash and on fd #2, then aborts. */ void ha_panic() { @@ -1204,6 +1219,9 @@ static struct cli_kw_list cli_kws = {{ },{ {{ "debug", "dev", "sym", NULL }, "debug dev sym : resolve symbol address", debug_parse_cli_sym, NULL, NULL, NULL, ACCESS_EXPERT }, {{ "debug", "dev", "tkill", NULL }, "debug dev tkill [thr] [sig] : send signal to thread", debug_parse_cli_tkill, NULL, NULL, NULL, ACCESS_EXPERT }, {{ "debug", "dev", "write", NULL }, "debug dev write [size] : write that many bytes in return", debug_parse_cli_write, NULL, NULL, NULL, ACCESS_EXPERT }, +#if defined(HA_HAVE_DUMP_LIBS) + {{ "show", "libs", NULL, NULL }, "show libs : show loaded object files and libraries", debug_parse_cli_show_libs, NULL, NULL }, +#endif {{ "show", "threads", NULL, NULL }, "show threads : show some threads debugging information", NULL, cli_io_handler_show_threads, NULL }, {{},} }}; diff --git a/src/tools.c b/src/tools.c index 5d8483d4e..c60f80b27 100644 --- a/src/tools.c +++ b/src/tools.c @@ -4983,6 +4983,96 @@ const void *resolve_sym_name(struct buffer *buf, const char *pfx, const void *ad return NULL; } +/* On systems where this is supported, let's provide a possibility to enumerate + * the list of object files. The output is appended to a buffer initialized by + * the caller, with one name per line. A trailing zero is always emitted if data + * are written. Only real objects are dumped (executable and .so libs). The + * function returns non-zero if it dumps anything. These functions do not make + * use of the trash so that it is possible for the caller to call them with the + * trash on input. The output format may be platform-specific but at least one + * version must emit raw object file names when argument is zero. + */ +#if defined(HA_HAVE_DUMP_LIBS) +# if defined(HA_HAVE_DL_ITERATE_PHDR) +/* the private we pass below is a dump context initialized like this */ +struct dl_dump_ctx { + struct buffer *buf; + int with_addr; +}; + +static int dl_dump_libs_cb(struct dl_phdr_info *info, size_t size, void *data) +{ + struct dl_dump_ctx *ctx = data; + const char *fname; + size_t p1, p2, beg, end; + int idx; + + if (!info || !info->dlpi_name) + goto leave; + + if (!*info->dlpi_name) + fname = get_exec_path(); + else if (strchr(info->dlpi_name, '/')) + fname = info->dlpi_name; + else + /* else it's a VDSO or similar and we're not interested */ + goto leave; + + if (!ctx->with_addr) + goto dump_name; + + /* virtual addresses are relative to the load address and are per + * pseudo-header, so we have to scan them all to find the furthest + * one from the beginning. In this case we only dump entries if + * they have at least one section. + */ + beg = ~0; end = 0; + for (idx = 0; idx < info->dlpi_phnum; idx++) { + if (!info->dlpi_phdr[idx].p_memsz) + continue; + p1 = info->dlpi_phdr[idx].p_vaddr; + if (p1 < beg) + beg = p1; + p2 = p1 + info->dlpi_phdr[idx].p_memsz - 1; + if (p2 > end) + end = p2; + } + + if (!idx) + goto leave; + + chunk_appendf(ctx->buf, "0x%012llx-0x%012llx (0x%07llx) ", + (ullong)info->dlpi_addr + beg, + (ullong)info->dlpi_addr + end, + (ullong)(end - beg + 1)); + dump_name: + chunk_appendf(ctx->buf, "%s\n", fname); + leave: + return 0; +} + +/* dumps lib names and optionally address ranges */ +int dump_libs(struct buffer *output, int with_addr) +{ + struct dl_dump_ctx ctx = { .buf = output, .with_addr = with_addr }; + size_t old_data = output->data; + + dl_iterate_phdr(dl_dump_libs_cb, &ctx); + return output->data != old_data; +} +# else // no DL_ITERATE_PHDR +# error "No dump_libs() function for this platform" +# endif +#else // no HA_HAVE_DUMP_LIBS + +/* unsupported platform: do not dump anything */ +int dump_libs(struct buffer *output, int with_addr) +{ + return 0; +} + +#endif // HA_HAVE_DUMP_LIBS + /* * Allocate an array of unsigned int with as address from string * made of integer separated by dot characters.