AxlMem — Memory Allocation
AxlMem — Memory Allocation
Memory allocation with dmalloc-inspired debug features. Size-tracking headers
enable axl_realloc without passing the old size. Debug builds add
fence-post guards, alloc/free fill patterns, file/line tracking, and leak
reporting.
Header: <axl/axl-mem.h>
Overview
AXL provides its own allocator on top of UEFI’s pool memory. All allocated
memory is tracked with a small header that stores the block size, enabling
axl_realloc and debug features without requiring the caller to pass the
old size.
Do not mix axl_malloc/axl_free with UEFI’s
AllocatePool/FreePool — they use different headers.
Debug vs. Release
In debug builds (-DAXL_MEM_DEBUG, the default for make):
Fill patterns: newly allocated memory is filled with
0xDA; freed memory is filled with0xDD. Use-after-free often manifests as reads of0xDD.Fence-post guards: 8 bytes of
0xFDare placed before and after each allocation.axl_mem_check(ptr)verifies these guards.File/line tracking: each allocation records
__FILE__and__LINE__for leak reports.Leak reporting:
axl_mem_dump_leaks()prints all outstanding allocations with their sizes and source locations.OOM fault injection:
axl_mem_fail_next_alloc(N)arms the Nth subsequent allocation to return NULL without touching the backend. Used by unit tests to exercise caller-side error paths (rollback, cleanup, error logging) that would otherwise be unreachable.
In release builds (make BUILD=RELEASE), these features are disabled
and the allocator has minimal overhead.
OOM Fault Injection
AXL’s error-handling paths — allocator OOM, hash-table insert
rollback, radix-tree split fallbacks, HTTP cache eviction, etc. —
are only ever taken under memory pressure, which is hard to trigger
naturally in tests. axl_mem_fail_next_alloc lets a test
deterministically force a specific allocation to fail:
// Exercise the OOM path of a constructor
axl_mem_fail_next_alloc(1);
AxlHashTable *h = axl_hash_table_new_str();
assert(h == NULL);
// Let the first N-1 allocations through, fail the Nth
axl_mem_fail_next_alloc(3);
void *a = axl_malloc(16); // succeeds
void *b = axl_malloc(16); // succeeds
void *c = axl_malloc(16); // returns NULL
// Passing 0 disables injection (the default state)
axl_mem_fail_next_alloc(0);
After the failure fires, the counter clears itself and subsequent
allocations succeed normally. Because every allocation path
(axl_calloc, axl_realloc, axl_strdup, axl_memdup) routes
through the same axl_malloc_impl, one hook point catches them all.
The injection logs at axl_debug level so it doesn’t pollute the
real-OOM error signal in test output.
The hook is active in both DEBUG and RELEASE — the counter check is one well-predicted branch on the malloc path, cheap enough that production builds keep the contract too. The fence/leak-tracking machinery (alloc-fill, fences, alloc-list) stays DEBUG-only; only the fail-next-alloc counter is universal.
RAII Auto-Cleanup
AXL provides GLib-style auto-cleanup macros using
__attribute__((cleanup)):
// Automatically freed when 's' goes out of scope
AXL_AUTO_FREE char *s = axl_strdup("hello");
// Automatically freed when 'h' goes out of scope
AXL_AUTOPTR(AxlHashTable) h = axl_hash_table_new_str();
if (error_condition) {
return -1; // both 's' and 'h' are freed automatically
}
Important: Always initialize at declaration. Never use with goto
that jumps over the declaration.
Quick Reference
#include <axl.h>
// Basic allocation
char *buf = axl_malloc(256);
void *copy = axl_memdup(original, size);
char *str = axl_strdup("hello");
// Typed allocation (zero-initialized)
MyStruct *s = axl_new(MyStruct);
int *arr = axl_new_array(int, 100);
// Free (NULL-safe)
axl_free(buf);
// Debug: check for leaks before exit
axl_mem_dump_leaks();
See also: AxlString for auto-growing string builders, AxlStream / AxlFs for I/O.
API Reference
Defines
-
AXL_AUTO_FREE
-
AXL_ARRAY_SIZE(a)
Get the number of elements in a static array.
Only works on actual arrays, not pointers. Produces a compile-time constant suitable for use in static initializers.
-
AXL_SIGNATURE_32(a, b, c, d)
Construct a 32-bit signature from four ASCII characters.
Encodes characters in little-endian order. Commonly used for structure validation signatures in UEFI drivers.
-
AXL_CONTAINER_OF(ptr, type, member)
Derive a pointer to the enclosing structure from a member pointer.
Given a pointer to a struct member, returns a pointer to the containing structure. Equivalent to Linux kernel’s container_of and EDK2’s CR/BASE_CR macros.
- Parameters:
ptr – pointer to the member
type – type of the containing structure
member – name of the member within the structure
Functions
-
void *axl_malloc(size_t size)
Allocate size bytes of uninitialized memory.
axl-mem.h:
Memory allocation with dmalloc-inspired debug features.
Size-tracking header enables realloc without old_size. DEBUG builds add fence-post guards, alloc/free fill patterns, file/line tracking, and leak reporting.
Do NOT free axl_malloc’d memory with FreePool (it has a header).
- Parameters:
size – number of bytes to allocate
- Returns:
pointer to allocated memory, or NULL on failure. Free with axl_free().
-
void *axl_calloc(size_t count, size_t size)
Allocate zero-initialized memory for count elements of size bytes each.
- Parameters:
count – number of elements
size – size of each element in bytes
- Returns:
pointer to allocated memory, or NULL on failure. Free with axl_free().
-
void *axl_realloc(void *ptr, size_t size)
Resize a previously allocated block to size bytes.
Contents are preserved up to the smaller of old and new sizes. If ptr is NULL, behaves like axl_malloc().
- Parameters:
ptr – pointer from axl_malloc/axl_calloc/axl_realloc, or NULL
size – new size in bytes
- Returns:
pointer to reallocated memory, or NULL on failure (original block is unchanged).
-
void axl_free(void *ptr)
Free memory allocated by axl_malloc, axl_calloc, axl_realloc, axl_strdup, or axl_memdup. NULL-safe.
- Parameters:
ptr – pointer to free, or NULL
-
char *axl_strdup(const char *s)
Duplicate a NUL-terminated string.
- Parameters:
s – string to duplicate, or NULL
- Returns:
newly allocated copy, or NULL on failure. Free with axl_free().
-
void *axl_memdup(const void *src, size_t size)
Duplicate size bytes of memory from src.
- Parameters:
src – source buffer
size – number of bytes to copy
- Returns:
newly allocated copy, or NULL on failure. Free with axl_free().
-
void *axl_new(size_t Type)
Allocate and zero-initialize a single instance of a type.
Usage:
MyStruct *p = axl_new(MyStruct);- Parameters:
Type – the type to allocate (passed as a type name)
- Returns:
typed pointer, or NULL on failure. Free with axl_free().
-
void *axl_new_array(size_t Type, size_t Count)
Allocate and zero-initialize an array of elements.
Usage:
int *arr = axl_new_array(int, 100);- Parameters:
Type – the type to allocate (passed as a type name)
Count – number of elements
- Returns:
typed pointer, or NULL on failure. Free with axl_free().
-
void axl_free_impl(void *ptr)
Auto-free a heap pointer when it goes out of scope.
Works with axl_malloc, axl_strdup, axl_memdup, and any pointer freed by axl_free().
AXL_AUTO_FREE char *s = axl_strdup("hello"); if (error) return -1; // s is freed automatically
IMPORTANT: Always initialize at declaration. Never use with goto that jumps over the declaration. Cleanup runs at scope exit regardless of whether the variable was assigned.
-
static inline void _axl_auto_free_func(void *p)
-
int axl_alloc_pages(size_t count, uint64_t *phys_addr)
Allocate contiguous page-aligned memory.
Allocates count pages (each 4096 bytes) of contiguous physical memory. Suitable for DMA buffers, RAM disks, and other uses requiring physical address alignment.
- Parameters:
count – number of 4KB pages to allocate
phys_addr – [out] receives physical address
- Returns:
AXL_OK on success, AXL_ERR on error.
-
void axl_free_pages(uint64_t phys_addr, size_t count)
Free page-aligned memory allocated by axl_alloc_pages.
- Parameters:
phys_addr – physical address from axl_alloc_pages
count – number of pages (must match alloc)
-
void axl_mem_get_stats(AxlMemStats *stats)
Get current allocation statistics.
- Parameters:
stats – [out] receives statistics
-
void axl_mem_dump_leaks(void)
Print all outstanding allocations to the log (debug builds).
Each leaked block is reported with its size, file, and line number. No-op in release builds.
-
bool axl_mem_check(const void *ptr)
Validate a heap pointer’s fence-post guards (debug builds).
- Parameters:
ptr – pointer to validate
- Returns:
true if valid, false if corrupted or not an axl_malloc’d pointer. Always returns true in release builds.
-
void axl_mem_fail_next_alloc(size_t n)
Inject an out-of-memory failure for testing error paths.
After calling
axl_mem_fail_next_alloc(N)with N > 0, the Nth subsequent allocation through the AXL allocator (axl_malloc/axl_calloc/axl_realloc/axl_strdup/axl_memdup) returns NULL without touching the backend. Allocations before the Nth succeed normally. After the failure fires, the counter resets to 0 (disabled) so subsequent allocations also succeed.// Exercise the OOM path of some constructor. axl_mem_fail_next_alloc(1); MyThing *t = my_thing_new(); assert(t == NULL);
Pass 0 to disable injection. Available in both DEBUG and RELEASE builds — the cost on the malloc path is one well-predicted branch. The fence/leak-tracking machinery stays DEBUG-only; only the fail-counter is universal.
- Parameters:
n – fail the Nth next alloc (1 = next, 0 = disabled)
-
struct AxlMemStats