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 /*