--- /dev/null
+2025-08-13 - Memory allocation in HAProxy 3.3
+
+The vast majority of dynamic memory allocations are performed from pools. Pools
+are optimized to store pre-calibrated objects of the right size for a given
+usage, try to favor locality and hot objects as much as possible, and are
+heavily instrumented to detect and help debug a wide class of bugs including
+buffer overflows, use-after-free, etc.
+
+For objects of random sizes, or those used only at configuration time, pools
+are not suited, and the regular malloc/free family is available, in addition of
+a few others.
+
+The standard allocation calls are intercepted at the code level (#define) when
+the code is compiled with -DDEBUG_MEM_STATS. For this reason, these calls are
+redefined as macros in "bug.h", and one must not try to use the pointers to
+such functions, as this may break DEBUG_MEM_STATS. This provides fine-grained
+stats about allocation/free per line of source code using locally implemented
+counters that can be consulted by "debug dev memstats". The calls are
+categorized into one of "calloc", "free", "malloc", "realloc", "strdup",
+"p_alloc", "p_free", the latter two designating pools. Extra calls such as
+memalign() and similar are also intercepted and counted as malloc.
+
+Due to the nature of this replacement, DEBUG_MEM_STATS cannot see operations
+performed in libraries or dependencies.
+
+In addition to DEBUG_MEM_STATS, when haproxy is built with USE_MEMORY_PROFILING
+the standard functions are wrapped by new ones defined in "activity.c", which
+also hold counters by call place. These ones are able to trace activity in
+libraries because the functions check the return pointer to figure where the
+call was made. The approach is different and relies on a large hash table. The
+files, function names and line numbers are not know, but by passing the pointer
+to dladdr(), we can often resolve most of these symbols. These operations are
+consulted via "show profiling memory". It must first be enabled either in the
+global config "profiling.memory on" or the CLI using "set profiling memory on".
+Memory profiling can also track pool allocations and frees thanks to knowing
+the size of the element and knowing a place where to store it. Some future
+evolutions might consider making this possible as well for pure malloc/free
+too by leveraging malloc_usable_size() a bit more.
+
+Finally, 3.3 brought aligned allocations. These are made available via a new
+family of functions around ha_aligned_alloc() that simply map to either
+posix_memalign(), memalign() or _aligned_malloc() for CYGWIN, depending on
+which one is available. This latter one requires to pass the pointer to
+_aligned_free() instead of free(), so for this reason, all aligned allocations
+have to be released using ha_aligned_free(). Since this mostly happens on
+configuration elements, in practice it's not as inconvenient as it can sound.
+These functions are in reality macros handled in "bug.h" like the previous
+ones in order to deal with DEBUG_MEM_STATS. All "alloc" variants are reported
+in memstats as "malloc". All "zalloc" variants are reported in memstats as
+"calloc".
+
+The currently available allocators are the following:
+
+ - void *ha_aligned_alloc(size_t align, size_t size)
+ - void *ha_aligned_zalloc(size_t align, size_t size)
+
+ Equivalent of malloc() but aligned to <align> bytes. The alignment MUST be
+ at least as large as one word and MUST be a power of two. The "zalloc"
+ variant also zeroes the area on success. Both return NULL on failure.
+
+ - void *ha_aligned_alloc_safe(size_t align, size_t size)
+ - void *ha_aligned_zalloc_safe(size_t align, size_t size)
+
+ Equivalent of malloc() but aligned to <align> bytes. The alignment is
+ automatically adjusted to the nearest larger power of two that is at least
+ as large as a word. The "zalloc" variant also zeroes the area on
+ success. Both return NULL on failure.
+
+ - (type *)ha_aligned_alloc_typed(size_t count, type)
+ (type *)ha_aligned_zalloc_typed(size_t count, type)
+
+ This macro returns an area aligned to the required alignment for type
+ <type>, large enough for <count> objects of this type, and the result is a
+ pointer of this type. The goal is to ease allocation of known structures
+ whose alignment is not necessarily known to the developer (and to avoid
+ encouraging to hard-code alignment). The cast in return also provides a
+ last-minute control in case a wrong type is mistakenly used due to a poor
+ copy-paste or an extra "*" after the type. When DEBUG_MEM_STATS is in use,
+ the type is stored as a string in the ".extra" field so that it can be
+ displayed in "debug dev memstats". The "zalloc" variant also zeroes the
+ area on success. Both return NULL on failure.
+
+ - void ha_aligned_free(void *ptr)
+
+ Frees the area pointed to by ptr. It is the equivalent of free() but for
+ objects allocated using one of the functions above.
static struct mem_stats _ __attribute__((used,__section__("mem_stats"),__aligned__(sizeof(void*)))) = { \
.caller = { \
.file = __FILE__, .line = __LINE__, \
- .what = MEM_STATS_TYPE_MALLOC, \
+ .what = MEM_STATS_TYPE_CALLOC, \
.func = __func__, \
}, \
}; \
static struct mem_stats _ __attribute__((used,__section__("mem_stats"),__aligned__(sizeof(void*)))) = { \
.caller = { \
.file = __FILE__, .line = __LINE__, \
- .what = MEM_STATS_TYPE_MALLOC, \
+ .what = MEM_STATS_TYPE_CALLOC, \
.func = __func__, \
}, \
}; \
_ha_aligned_zalloc_safe(__a, __s); \
})
+// Since the type is known, the .extra field will contain its name
+#undef ha_aligned_alloc_typed
+#define ha_aligned_alloc_typed(cnt,type) ({ \
+ size_t __a = __alignof__(type); \
+ size_t __s = ((size_t)cnt) * sizeof(type); \
+ static struct mem_stats _ __attribute__((used,__section__("mem_stats"),__aligned__(sizeof(void*)))) = { \
+ .caller = { \
+ .file = __FILE__, .line = __LINE__, \
+ .what = MEM_STATS_TYPE_MALLOC, \
+ .func = __func__, \
+ }, \
+ .extra = #type, \
+ }; \
+ HA_WEAK(__start_mem_stats); \
+ HA_WEAK(__stop_mem_stats); \
+ _HA_ATOMIC_INC(&_.calls); \
+ _HA_ATOMIC_ADD(&_.size, __s); \
+ (type*)_ha_aligned_alloc(__a, __s); \
+})
+
+// Since the type is known, the .extra field will contain its name
+#undef ha_aligned_zalloc_typed
+#define ha_aligned_zalloc_typed(cnt,type) ({ \
+ size_t __a = __alignof__(type); \
+ size_t __s = ((size_t)cnt) * sizeof(type); \
+ static struct mem_stats _ __attribute__((used,__section__("mem_stats"),__aligned__(sizeof(void*)))) = { \
+ .caller = { \
+ .file = __FILE__, .line = __LINE__, \
+ .what = MEM_STATS_TYPE_CALLOC, \
+ .func = __func__, \
+ }, \
+ .extra = #type, \
+ }; \
+ HA_WEAK(__start_mem_stats); \
+ HA_WEAK(__stop_mem_stats); \
+ _HA_ATOMIC_INC(&_.calls); \
+ _HA_ATOMIC_ADD(&_.size, __s); \
+ (type*)_ha_aligned_zalloc_safe(__a, __s); \
+})
+
#undef ha_aligned_free
#define ha_aligned_free(x) ({ \
typeof(x) __x = (x); \
#define ha_aligned_zalloc(a,s) _ha_aligned_zalloc(a, s)
#define ha_aligned_alloc_safe(a,s) _ha_aligned_alloc_safe(a, s)
#define ha_aligned_zalloc_safe(a,s) _ha_aligned_zalloc_safe(a, s)
+#define ha_aligned_alloc_typed(cnt,type) ((type*)_ha_aligned_alloc(__alignof__(type), ((size_t)cnt) * sizeof(type)))
+#define ha_aligned_zalloc_typed(cnt,type) ((type*)_ha_aligned_zalloc(__alignof__(type), ((size_t)cnt) * sizeof(type)))
#define ha_aligned_free(p) _ha_aligned_free(p)
#define ha_aligned_free_size(p,s) _ha_aligned_free(p)