From: Willy Tarreau Date: Wed, 18 Mar 2026 09:47:16 +0000 (+0100) Subject: MINOR: debug: read all libs in memory when set-dumpable=libs X-Git-Tag: v3.4-dev7~20 X-Git-Url: http://git.ipfire.org/gitweb/?a=commitdiff_plain;h=e1738b665d;p=thirdparty%2Fhaproxy.git MINOR: debug: read all libs in memory when set-dumpable=libs When "set-dumpable" is set to "libs", in addition to marking the process dumpable, haproxy also reads the binary and shared objects into memory as a tar archive in a page-aligned location so that these files are easily extractable from a future core dump. The goal here is to always have access to the exact same binary and libs as those which caused the core to happen. It's indeed very frequent to miss some of these, or to get mismatching files due to a local update that didn't experience a reload, or to get those of a host system instead of the container. The in-memory tar file presents everything under a directory called "core-%d" where %d corresponds to the PID of the worker process. In order to ease the finding of these data in the core dump, the memory area is contiguous and surrounded by PROT_NONE pages so that it appears in its own segment in the core file. The total size used by this is a few tens of MB, which is not a problem on large systems. --- diff --git a/doc/configuration.txt b/doc/configuration.txt index fd60f4205..b0ec3fe5f 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -3148,7 +3148,25 @@ server-state-file configuration. See also "server-state-base" and "show servers state", "load-server-state-from-file" and "server-state-file-name" -set-dumpable [ on | off ] +set-dumpable [ on | off | libs ] + This option helps choose the core dump behavior in case of process crash. + Available options are: + + - on this enables core dumping at the process level if it was + previously disabled. + + - off this disables a previously enabled core dumping. + + - libs this enables core dumping with an embedded copy of the binaries and + libraries that are required for debugging. This may be requested by + developers. In this case haproxy will try to load the libraries it + depends on into memory and keep them preciously. If the process + crashes, they will be dumped into the core so there is no need for + retrieving them from the file system anymore and no risk that they + do not match the core. This takes a few megabytes to a few tens of + megabytes of additional RAM, so it is better not to use it on small + systems. + This option is better left disabled by default and enabled only upon a developer's request. By default it is disabled. Without argument, it defaults to "on". If it has been enabled, it may still be forcibly disabled by prefixing diff --git a/include/haproxy/global-t.h b/include/haproxy/global-t.h index 212b62bf7..a303954c0 100644 --- a/include/haproxy/global-t.h +++ b/include/haproxy/global-t.h @@ -79,7 +79,7 @@ #define GTUNE_DISABLE_H2_WEBSOCKET (1<<21) #define GTUNE_DISABLE_ACTIVE_CLOSE (1<<22) #define GTUNE_QUICK_EXIT (1<<23) -/* (1<<24) unused */ +#define GTUNE_COLLECT_LIBS (1<<24) /* (1<<25) unused */ #define GTUNE_USE_FAST_FWD (1<<26) #define GTUNE_LISTENER_MQ_FAIR (1<<27) diff --git a/include/haproxy/global.h b/include/haproxy/global.h index 522af4162..1510bfe0c 100644 --- a/include/haproxy/global.h +++ b/include/haproxy/global.h @@ -58,6 +58,10 @@ extern int devnullfd; extern int fileless_mode; extern struct cfgfile fileless_cfg; +/* storage for collected libs */ +extern void *lib_storage; +extern size_t lib_size; + struct proxy; struct server; int main(int argc, char **argv); diff --git a/include/haproxy/tools.h b/include/haproxy/tools.h index cb3155a02..e6a0edfc2 100644 --- a/include/haproxy/tools.h +++ b/include/haproxy/tools.h @@ -1153,6 +1153,7 @@ 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); +void collect_libs(void); /* 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/cfgparse-global.c b/src/cfgparse-global.c index f1c4b7a17..d16f9747e 100644 --- a/src/cfgparse-global.c +++ b/src/cfgparse-global.c @@ -97,6 +97,8 @@ int cfg_parse_global(const char *file, int linenum, char **args, int kwm) } if (!*args[1] || strcmp(args[1], "on") == 0) global.tune.options |= GTUNE_SET_DUMPABLE; + else if (strcmp(args[1], "libs") == 0) + global.tune.options |= GTUNE_SET_DUMPABLE | GTUNE_COLLECT_LIBS; else if (strcmp(args[1], "off") == 0) global.tune.options &= ~GTUNE_SET_DUMPABLE; else { diff --git a/src/haproxy.c b/src/haproxy.c index d8299cdff..6bc8df85d 100644 --- a/src/haproxy.c +++ b/src/haproxy.c @@ -271,6 +271,10 @@ unsigned int tainted = 0; unsigned int experimental_directives_allowed = 0; unsigned int deprecated_directives_allowed = 0; +/* mapped storage for collected libs */ +void *lib_storage = NULL; +size_t lib_size = 0; + int check_kw_experimental(struct cfg_keyword *kw, const char *file, int linenum, char **errmsg) { @@ -2516,6 +2520,10 @@ static void step_init_2(int argc, char** argv) chunk_appendf(&trash, "TARGET='%s'", pm_target_opts); post_mortem_add_component("haproxy", haproxy_version, cc, cflags, opts, argv[0]); + + if ((global.tune.options & (GTUNE_SET_DUMPABLE | GTUNE_COLLECT_LIBS)) == + (GTUNE_SET_DUMPABLE | GTUNE_COLLECT_LIBS)) + collect_libs(); } /* This is a third part of the late init sequence, where we register signals for diff --git a/src/tools.c b/src/tools.c index f6e493e9e..390f5f3d5 100644 --- a/src/tools.c +++ b/src/tools.c @@ -6031,6 +6031,81 @@ int dump_libs(struct buffer *output, int with_addr) dl_iterate_phdr(dl_dump_libs_cb, &ctx); return output->data != old_data; } + +/* the private we pass below is a dump context initialized like this */ +struct dl_collect_ctx { + char *storage; + size_t size; + char *prefix; + int pos; +}; + +static int dl_collect_libs_cb(struct dl_phdr_info *info, size_t size, void *data) +{ + struct dl_collect_ctx *ctx = data; + const char *fname; + + 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; + + load_file_into_tar(&ctx->storage, &ctx->size, ctx->prefix, fname, NULL, "haproxy-libs-dump"); + leave: + /* increment the object's number */ + ctx->pos++; + return 0; +} + +/* dumps lib names and optionally address ranges */ +void collect_libs(void) +{ + struct dl_collect_ctx ctx = { .storage = NULL, .size = 0, .pos = 0 }; + ulong pagesize = sysconf(_SC_PAGESIZE); + char dir_name[16]; + size_t new_size; + void *page; + + /* prepend a directory named after the starting pid */ + snprintf(dir_name, sizeof(dir_name), "core-%u", getpid()); + ctx.prefix = dir_name; + + /* callbacks will (re-)allocate ctx->storage */ + dl_iterate_phdr(dl_collect_libs_cb, &ctx); + + /* now that the archive is complete, we need to close it by appending + * two empty 512B blocks. We'll also place it aligned in an isolated + * mapped area so that it uses its own segment in a core dump for + * easier locating. In order to do this, we'll allocate two extra + * pages and will punch holes around. + */ + new_size = (ctx.size + 2*512 + 2*pagesize + pagesize - 1) & -pagesize; + page = mmap(NULL, new_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + + if (page != MAP_FAILED) { + /* punch holes around that won't go into the core */ + mprotect(page, pagesize, PROT_NONE); + mprotect(page + new_size - pagesize, pagesize, PROT_NONE); + new_size -= 2*pagesize; + page += pagesize; + /* copy and make read-only */ + memcpy(page, ctx.storage, ctx.size); + mprotect(page, lib_size, PROT_READ); + vma_set_name(page, new_size, "archive", "boot-libs"); + + lib_storage = page; + lib_size = new_size; + } + + /* don't need the temporary storage anymore */ + ha_free(&ctx.storage); +} # else // no DL_ITERATE_PHDR # error "No dump_libs() function for this platform" # endif @@ -6042,6 +6117,11 @@ int dump_libs(struct buffer *output, int with_addr) return 0; } +/* unsupported platform: do not collect anything */ +void collect_libs(void) +{ +} + #endif // HA_HAVE_DUMP_LIBS /*