MINOR: pools: support setting debugging options using -dM

The 9 currently available debugging options may now be checked, set, or
cleared using -dM. The directive now takes a comma-delimited list of
options after the optional poisonning byte. With "help", the list of
available options is displayed with a short help and their current
status.

The management doc was updated.
This commit is contained in:
Willy Tarreau 2022-02-23 15:20:53 +01:00
parent 1408b1f8be
commit f4b79c4a01
4 changed files with 145 additions and 7 deletions

View File

@ -2531,10 +2531,10 @@ tune.comp.maxlevel <number>
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

View File

@ -227,7 +227,8 @@ list of options is :
./haproxy -W -q -c -dL -f foo.cfg | tar -T - -hzcf archive.tgz
-dM[<byte>] : forces memory poisoning, which means that each and every
-dM[<byte>[,]][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
<byte> before being passed to the caller. When <byte> 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

View File

@ -564,7 +564,7 @@ static void usage(char *name)
" [ -p <pidfile> ] [ -m <max megs> ] [ -C <dir> ] [-- <cfgfile>*]\n"
" -v displays version ; -vv shows known build options.\n"
" -d enters debug mode ; -db only disables background mode.\n"
" -dM[<byte>] poisons memory with <byte> (defaults to 0x50)\n"
" -dM[<byte>,help,...] debug memory (default: poison with <byte>/0x50)\n"
" -V enters verbose mode (disables quiet mode)\n"
" -D goes daemon ; -C changes to <dir> before loading files.\n"
" -W master-worker mode.\n"

View File

@ -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;
}