mirror of
https://git.haproxy.org/git/haproxy.git/
synced 2025-09-20 13:21:29 +02:00
MINOR: tools: also implement ha_aligned_alloc_typed()
This one is a macro and will allocate a properly aligned and sized object. This will help make sure that the alignment promised to the compiler is respected. When memstats is used, the type name is passed as a string into the .extra field so that it can be displayed in "debug dev memstats". Two tiny mistakes related to memstats macros were also fixed (calloc instead of malloc for zalloc), and the doc was also added to document how to use these calls.
This commit is contained in:
parent
9432e7d688
commit
33d72568dd
86
doc/internals/api/memory.txt
Normal file
86
doc/internals/api/memory.txt
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
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.
|
@ -646,7 +646,7 @@ struct mem_stats {
|
|||||||
static struct mem_stats _ __attribute__((used,__section__("mem_stats"),__aligned__(sizeof(void*)))) = { \
|
static struct mem_stats _ __attribute__((used,__section__("mem_stats"),__aligned__(sizeof(void*)))) = { \
|
||||||
.caller = { \
|
.caller = { \
|
||||||
.file = __FILE__, .line = __LINE__, \
|
.file = __FILE__, .line = __LINE__, \
|
||||||
.what = MEM_STATS_TYPE_MALLOC, \
|
.what = MEM_STATS_TYPE_CALLOC, \
|
||||||
.func = __func__, \
|
.func = __func__, \
|
||||||
}, \
|
}, \
|
||||||
}; \
|
}; \
|
||||||
@ -682,7 +682,7 @@ struct mem_stats {
|
|||||||
static struct mem_stats _ __attribute__((used,__section__("mem_stats"),__aligned__(sizeof(void*)))) = { \
|
static struct mem_stats _ __attribute__((used,__section__("mem_stats"),__aligned__(sizeof(void*)))) = { \
|
||||||
.caller = { \
|
.caller = { \
|
||||||
.file = __FILE__, .line = __LINE__, \
|
.file = __FILE__, .line = __LINE__, \
|
||||||
.what = MEM_STATS_TYPE_MALLOC, \
|
.what = MEM_STATS_TYPE_CALLOC, \
|
||||||
.func = __func__, \
|
.func = __func__, \
|
||||||
}, \
|
}, \
|
||||||
}; \
|
}; \
|
||||||
@ -693,6 +693,46 @@ struct mem_stats {
|
|||||||
_ha_aligned_zalloc_safe(__a, __s); \
|
_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
|
#undef ha_aligned_free
|
||||||
#define ha_aligned_free(x) ({ \
|
#define ha_aligned_free(x) ({ \
|
||||||
typeof(x) __x = (x); \
|
typeof(x) __x = (x); \
|
||||||
@ -742,6 +782,8 @@ struct mem_stats {
|
|||||||
#define ha_aligned_zalloc(a,s) _ha_aligned_zalloc(a, s)
|
#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_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_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(p) _ha_aligned_free(p)
|
||||||
#define ha_aligned_free_size(p,s) _ha_aligned_free(p)
|
#define ha_aligned_free_size(p,s) _ha_aligned_free(p)
|
||||||
|
|
||||||
|
@ -2253,9 +2253,9 @@ static int debug_iohandler_memstats(struct appctx *appctx)
|
|||||||
func = ptr->caller.func;
|
func = ptr->caller.func;
|
||||||
|
|
||||||
switch (ptr->caller.what) {
|
switch (ptr->caller.what) {
|
||||||
case MEM_STATS_TYPE_CALLOC: type = "CALLOC"; direction = 1; break;
|
case MEM_STATS_TYPE_CALLOC: type = "CALLOC"; direction = 1; if (ptr->extra) info = (const char *)ptr->extra; break;
|
||||||
case MEM_STATS_TYPE_FREE: type = "FREE"; direction = -1; break;
|
case MEM_STATS_TYPE_FREE: type = "FREE"; direction = -1; break;
|
||||||
case MEM_STATS_TYPE_MALLOC: type = "MALLOC"; direction = 1; break;
|
case MEM_STATS_TYPE_MALLOC: type = "MALLOC"; direction = 1; if (ptr->extra) info = (const char *)ptr->extra; break;
|
||||||
case MEM_STATS_TYPE_REALLOC: type = "REALLOC"; break;
|
case MEM_STATS_TYPE_REALLOC: type = "REALLOC"; break;
|
||||||
case MEM_STATS_TYPE_STRDUP: type = "STRDUP"; direction = 1; break;
|
case MEM_STATS_TYPE_STRDUP: type = "STRDUP"; direction = 1; break;
|
||||||
case MEM_STATS_TYPE_P_ALLOC: type = "P_ALLOC"; direction = 1; if (ptr->extra) info = ((const struct pool_head *)ptr->extra)->name; break;
|
case MEM_STATS_TYPE_P_ALLOC: type = "P_ALLOC"; direction = 1; if (ptr->extra) info = ((const struct pool_head *)ptr->extra)->name; break;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user