diff --git a/doc/configuration.txt b/doc/configuration.txt index b516adde6..0c62b56bf 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -2531,10 +2531,10 @@ tune.comp.maxlevel this value. The default value is 1. tune.fail-alloc - If compiled with DEBUG_FAIL_ALLOC, gives the percentage of chances an - allocation attempt fails. Must be between 0 (no failure) and 100 (no - success). This is useful to debug and make sure memory failures are handled - gracefully. + If compiled with DEBUG_FAIL_ALLOC or started with "-dMfail", gives the + percentage of chances an allocation attempt fails. Must be between 0 (no + failure) and 100 (no success). This is useful to debug and make sure memory + failures are handled gracefully. tune.fd.edge-triggered { on | off } [ EXPERIMENTAL ] Enables ('on') or disables ('off') the edge-triggered polling mode for FDs diff --git a/doc/management.txt b/doc/management.txt index e7f396200..e3a4abc54 100644 --- a/doc/management.txt +++ b/doc/management.txt @@ -227,7 +227,8 @@ list of options is : ./haproxy -W -q -c -dL -f foo.cfg | tar -T - -hzcf archive.tgz - -dM[] : forces memory poisoning, which means that each and every + -dM[[,]][help|options,...] : forces memory poisoning, and/or changes + memory other debugging options. Memory poisonning means that each and every memory region allocated with malloc() or pool_alloc() will be filled with before being passed to the caller. When is not specified, it defaults to 0x50 ('P'). While this slightly slows down operations, it is @@ -235,7 +236,74 @@ list of options is : the code that cause random crashes. Note that -dM0 has the effect of turning any malloc() into a calloc(). In any case if a bug appears or disappears when using this option it means there is a bug in haproxy, so - please report it. + please report it. A number of other options are available either alone or + after a comma following the byte. The special option "help" will list the + currently supported options and their current value. Each debugging option + may be forced on or off. The most optimal options are usually chosen at + build time based on the operating system and do not need to be adjusted, + unless suggested by a developer. Supported debugging options include + (set/clear): + - fail / no-fail: + This enables randomly failing memory allocations, in conjunction with + the global "tune.fail-alloc" setting. This is used to detect missing + error checks in the code. + + - no-merge / merge: + By default, pools of very similar sizes are merged, resulting in more + efficiency, but this complicates the analysis of certain memory dumps. + This option allows to disable this mechanism, and may slightly increase + the memory usage. + + - cold-first / hot-first: + In order to optimize the CPU cache hit ratio, by default the most + recently released objects ("hot") are recycled for new allocations. + But doing so also complicates analysis of memory dumps and may hide + use-after-free bugs. This option allows to instead pick the coldest + objects first, which may result in a slight increase of CPU usage. + + - integrity / no-integrity: + When this option is enabled, memory integrity checks are enabled on + the allocated area to verify that it hasn't been modified since it was + last released. This works best with "no-merge", "cold-first" and "tag". + Enabling this option will slightly increase the CPU usage. + + - no-global / global: + Depending on the operating system, a process-wide global memory cache + may be enabled if it is estimated that the standard allocator is too + slow or inefficient with threads. This option allows to forcefully + disable it or enable it. Disabling it may result in a CPU usage + increase with inefficient allocators. Enabling it may result in a + higher memory usage with efficient allocators. + + - no-cache / cache: + Each thread uses a very fast local object cache for allocations, which + is always enabled by default. This option allows to disable it. Since + the global cache also passes via the local caches, this will + effectively result in disabling all caches and allocating directly from + the default allocator. This may result in a significant increase of CPU + usage, but may also result in small memory savings on tiny systems. + + - caller / no-caller: + Enabling this option reserves some extra space in each allocated object + to store the address of the last caller that allocated or released it. + This helps developers go back in time when analysing memory dumps and + to guess how something unexpected happened. + + - tag / no-tag: + Enabling this option reserves some extra space in each allocated object + to store a tag that allows to detect bugs such as double-free, freeing + an invalid object, and buffer overflows. It offers much stronger + reliability guarantees at the expense of 4 or 8 extra bytes per + allocation. It usually is the first step to detect memory corruption. + + - poison / no-poison: + Enabling this option will fill allocated objects with a fixed pattern + that will make sure that some accidental values such as 0 will not be + present if a newly added field was mistakenly forgotten in an + initialization routine. Such bugs tend to rarely reproduce, especially + when pools are not merged. This is normally enabled by directly passing + the byte's value to -dM but using this option allows to disable/enable + use of a previously set value. -dS : disable use of the splice() system call. It is equivalent to the "global" section's "nosplice" keyword. This may be used when splice() is diff --git a/src/haproxy.c b/src/haproxy.c index 030a97325..0010e90f9 100644 --- a/src/haproxy.c +++ b/src/haproxy.c @@ -564,7 +564,7 @@ static void usage(char *name) " [ -p ] [ -m ] [ -C ] [-- *]\n" " -v displays version ; -vv shows known build options.\n" " -d enters debug mode ; -db only disables background mode.\n" - " -dM[] poisons memory with (defaults to 0x50)\n" + " -dM[,help,...] debug memory (default: poison with /0x50)\n" " -V enters verbose mode (disables quiet mode)\n" " -D goes daemon ; -C changes to before loading files.\n" " -W master-worker mode.\n" diff --git a/src/pool.c b/src/pool.c index 4998f09b5..0e33cd353 100644 --- a/src/pool.c +++ b/src/pool.c @@ -62,6 +62,25 @@ uint pool_debugging __read_mostly = /* set of POOL_DBG_* flags */ #endif 0; +static const struct { + uint flg; + const char *set; + const char *clr; + const char *hlp; +} dbg_options[] = { + /* flg, set, clr, hlp */ + { POOL_DBG_FAIL_ALLOC, "fail", "no-fail", "randomly fail allocations" }, + { POOL_DBG_DONT_MERGE, "no-merge", "merge", "disable merging of similar pools" }, + { POOL_DBG_COLD_FIRST, "cold-first", "hot-first", "pick cold objects first" }, + { POOL_DBG_INTEGRITY, "integrity", "no-integrity", "enable cache integrity checks" }, + { POOL_DBG_NO_GLOBAL, "no-global", "global", "disable global shared cache" }, + { POOL_DBG_NO_CACHE, "no-cache", "cache", "disable thread-local cache" }, + { POOL_DBG_CALLER, "caller", "no-caller", "save caller information in cache" }, + { POOL_DBG_TAG, "tag", "no-tag", "add tag at end of allocated objects" }, + { POOL_DBG_POISON, "poison", "no-poison", "poison newly allocated objects" }, + { 0 /* end */ } +}; + static int mem_fail_rate __read_mostly = 0; static int using_default_allocator __read_mostly = 1; static int(*my_mallctl)(const char *, void *, size_t *, void *, size_t) = NULL; @@ -902,7 +921,9 @@ unsigned long pool_total_used() */ int pool_parse_debugging(const char *str, char **err) { + struct ist args; char *end; + uint new_dbg; int v; @@ -916,6 +937,55 @@ int pool_parse_debugging(const char *str, char **err) pool_debugging &= ~POOL_DBG_POISON; str = end; } + + new_dbg = pool_debugging; + + for (args = ist(str); istlen(args); args = istadv(istfind(args, ','), 1)) { + struct ist feat = iststop(args, ','); + + if (!istlen(feat)) + continue; + + if (isteq(feat, ist("help"))) { + ha_free(err); + memprintf(err, + "-dM alone enables memory poisonning with byte 0x50 on allocation. A numeric\n" + "value may be appended immediately after -dM to use another value (0 supported).\n" + "Then an optional list of comma-delimited keywords may be appended to set or\n" + "clear some debugging options ('*' marks the current setting):\n\n" + " set clear description\n" + " -----------------+-----------------+-----------------------------------------\n"); + + for (v = 0; dbg_options[v].flg; v++) { + memprintf(err, "%s %c %-15s|%c %-15s| %s\n", + *err, + (pool_debugging & dbg_options[v].flg) ? '*' : ' ', + dbg_options[v].set, + (pool_debugging & dbg_options[v].flg) ? ' ' : '*', + dbg_options[v].clr, + dbg_options[v].hlp); + } + return -1; + } + + for (v = 0; dbg_options[v].flg; v++) { + if (isteq(feat, ist(dbg_options[v].set))) { + new_dbg |= dbg_options[v].flg; + break; + } + else if (isteq(feat, ist(dbg_options[v].clr))) { + new_dbg &= ~dbg_options[v].flg; + break; + } + } + + if (!dbg_options[v].flg) { + memprintf(err, "unknown pool debugging feature <%.*s>", (int)istlen(feat), istptr(feat)); + return -2; + } + } + + pool_debugging = new_dbg; return 1; }