]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: debug: read all libs in memory when set-dumpable=libs
authorWilly Tarreau <w@1wt.eu>
Wed, 18 Mar 2026 09:47:16 +0000 (10:47 +0100)
committerWilly Tarreau <w@1wt.eu>
Wed, 18 Mar 2026 14:30:39 +0000 (15:30 +0100)
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.

doc/configuration.txt
include/haproxy/global-t.h
include/haproxy/global.h
include/haproxy/tools.h
src/cfgparse-global.c
src/haproxy.c
src/tools.c

index fd60f42052b340ae07e761cd513abdf914871e9a..b0ec3fe5f9228ef2dfc1eda2a68e7093dc167076 100644 (file)
@@ -3148,7 +3148,25 @@ server-state-file <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
index 212b62bf78f530055d8c383bf88bd93aeaae2e43..a303954c031c12871eafaaa23f477d4061b0ee13 100644 (file)
@@ -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)
index 522af4162745f754e03680803967d0c8971d828d..1510bfe0c6d4c54ec80595b68bd7e8b917f03503 100644 (file)
@@ -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);
index cb3155a0285dede8ace1a3945a682ad855957e26..e6a0edfc236da203b33878f0211aeec7f538b36a 100644 (file)
@@ -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.
index f1c4b7a17a9fb3e04db24eec0e786e5d502df141..d16f9747e242ea8132baa3898e45fa7bf7affa92 100644 (file)
@@ -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 {
index d8299cdff3170cec89c5df3dcd38108453d2c9e3..6bc8df85d69a5b0c05d73976cbbed9501510d180 100644 (file)
@@ -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
index f6e493e9ec3ebc3b8dc00b377ce1c815ef1f8523..390f5f3d5a5d92c61571067e5260de9f93fe5a62 100644 (file)
@@ -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 <data> 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
 
 /*