kernel: Add mechanism to replace specific object caches with the guarded heap.

This allows the guarded heap to be used in a much more targeted way,
if it's suspected specific object_caches have problems.

Enabled by default for KDEBUG. The new safemode/kernel option is
"guarded_heap_for_object_caches". This accepts a comma-separated
list of quoted strings, optionally with * at beginning and/or end
(but not middle.) Examples:

mbufs*   # matches caches starting with "mbufs"
*jumbo9*	# matches caches containing "jumbo9"
"*jumbo9 chunks","mbufs*" # matches caches ending with "jumbo9 chunks"
                          # or starting with "mbufs"

This should help with diagnosing #19973, among others, without needing
to compile custom versions of drivers or anything like that.

Unfortunately it is not possible to use this mechanism to selectively
replace parts of the slab malloc. The slab malloc free() relies on
the MemoryManager to figure out what cache an object belongs to, which
doesn't work if objects of some size classes aren't known to the
MemoryManager at all.
This commit is contained in:
Augustin Cavalier
2026-03-24 18:10:10 -04:00
parent 76df196f25
commit 7240f3762f
6 changed files with 276 additions and 54 deletions
+7 -3
View File
@@ -120,9 +120,6 @@
// space so it is quite easy to hit limits. // space so it is quite easy to hit limits.
#define DEBUG_HEAPS_DEFAULT slab #define DEBUG_HEAPS_DEFAULT slab
// Replace the object cache with the chosen debug heap to force debug features.
#define USE_DEBUG_HEAPS_FOR_OBJECT_CACHE 0
// If 0, disables memory re-use by default in the guarded heap (can be overridden // If 0, disables memory re-use by default in the guarded heap (can be overridden
// via "guarded_heap_options".) This means freed virtual memory is never reused and // via "guarded_heap_options".) This means freed virtual memory is never reused and
// stays invalid, causing any access to crash. This is a magnitude more space // stays invalid, causing any access to crash. This is a magnitude more space
@@ -130,6 +127,13 @@
// to address space waste. // to address space waste.
#define DEBUG_GUARDED_HEAP_MEMORY_REUSE_DEFAULT 1 #define DEBUG_GUARDED_HEAP_MEMORY_REUSE_DEFAULT 1
// If DEBUG_HEAPS is enabled, allow the guarded heap to replace select object_cache(s)
// (but not slab malloc ones) specified via "guarded_heap_for_object_caches".
#define GUARDED_HEAP_CAN_REPLACE_OBJECT_CACHES 1
// Replace all object caches with the chosen debug heap to force debug features.
#define USE_DEBUG_HEAPS_FOR_ALL_OBJECT_CACHES 0
// Enables additional sanity checks in the slab allocator's memory manager. // Enables additional sanity checks in the slab allocator's memory manager.
#define DEBUG_SLAB_MEMORY_MANAGER_PARANOID_CHECKS 0 #define DEBUG_SLAB_MEMORY_MANAGER_PARANOID_CHECKS 0
+3 -3
View File
@@ -504,13 +504,13 @@ guarded_heap_realloc(guarded_heap& heap, void* address, size_t newSize, uint32 f
if (oldSize == newSize) if (oldSize == newSize)
return address; return address;
void* newBlock = malloc_etc(newSize, flags); void* newBlock = guarded_heap_allocate(heap, newSize, 0, flags);
if (newBlock == NULL) if (newBlock == NULL)
return NULL; return NULL;
memcpy(newBlock, address, min_c(oldSize, newSize)); memcpy(newBlock, address, min_c(oldSize, newSize));
free_etc(address, flags); guarded_heap_free(heap, address, flags);
return newBlock; return newBlock;
} }
@@ -874,7 +874,7 @@ guarded_heap_realloc_etc(void* address, size_t newSize, uint32 flags)
kernel_heap_implementation kernel_guarded_heap = { kernel_heap_implementation kernel_guarded_heap = {
"guarded_heap", "guarded_heap",
#if USE_DEBUG_HEAPS_FOR_OBJECT_CACHE #if USE_DEBUG_HEAPS_FOR_ALL_OBJECT_CACHES
// This requires a lot of up-front memory to boot at all... // This requires a lot of up-front memory to boot at all...
/* initial_size */ 128 * 1024 * 1024, /* initial_size */ 128 * 1024 * 1024,
// ... and a lot of reserves to keep running. // ... and a lot of reserves to keep running.
+202 -43
View File
@@ -8,8 +8,9 @@
#if DEBUG_HEAPS #if DEBUG_HEAPS
#include <stdlib.h> #include <stdlib.h>
#include <heap.h> #include <ctype.h>
#include <heap.h>
#include <vm/vm.h> #include <vm/vm.h>
#include <vm/vm_page.h> #include <vm/vm_page.h>
#include <slab/Slab.h> #include <slab/Slab.h>
@@ -28,33 +29,27 @@
#define HEAP_SYMBOL_NAME(NAME) kernel_##NAME##_heap #define HEAP_SYMBOL_NAME(NAME) kernel_##NAME##_heap
#define HEAP_SYMBOL(NAME) HEAP_SYMBOL_NAME(NAME) #define HEAP_SYMBOL(NAME) HEAP_SYMBOL_NAME(NAME)
static kernel_heap_implementation* sHeap = &HEAP_SYMBOL(DEBUG_HEAPS_DEFAULT); static kernel_heap_implementation* sActiveHeaps[2] = { &HEAP_SYMBOL(DEBUG_HEAPS_DEFAULT) };
#if GUARDED_HEAP_CAN_REPLACE_OBJECT_CACHES
struct CacheSelector {
char name[32];
bool globPrefix : 1;
bool globSuffix : 1;
};
static CacheSelector* sGuardedHeapForObjectCaches = NULL;
static int32 sGuardedHeapForObjectCachesCount = 0;
#endif
// #pragma mark - // #pragma mark -
status_t static status_t
heap_init(struct kernel_args* args) init_heap(struct kernel_args* args, kernel_heap_implementation* heap)
{ {
char buffer[32];
size_t bufferSize = sizeof(buffer);
if (get_safemode_option_early(args, "kernel_malloc", buffer, &bufferSize) == B_OK) {
if (strcmp(buffer, "guarded") == 0)
sHeap = &kernel_guarded_heap;
else if (strcmp(buffer, "debug") == 0)
sHeap = &kernel_debug_heap;
#if !USE_DEBUG_HEAPS_FOR_OBJECT_CACHE
else if (strcmp(buffer, "slab") == 0)
sHeap = &kernel_slab_heap;
#endif
else
panic("unknown or unavailable kernel heap '%s'!", buffer);
}
dprintf("kernel malloc: using %s\n", sHeap->name);
addr_t heapBase = 0; addr_t heapBase = 0;
size_t heapSize = sHeap->initial_size; size_t heapSize = heap->initial_size;
if (heapSize != 0) { if (heapSize != 0) {
// try to accomodate low memory systems // try to accomodate low memory systems
while (heapSize > (vm_page_num_pages() * B_PAGE_SIZE) / 8) while (heapSize > (vm_page_num_pages() * B_PAGE_SIZE) / 8)
@@ -68,83 +63,247 @@ heap_init(struct kernel_args* args)
TRACE(("heap at 0x%lx\n", heapBase)); TRACE(("heap at 0x%lx\n", heapBase));
} }
return sHeap->init(args, heapBase, heapSize); return heap->init(args, heapBase, heapSize);
}
#if GUARDED_HEAP_CAN_REPLACE_OBJECT_CACHES
bool
guarded_heap_replaces_object_cache(const char* name)
{
if (sGuardedHeapForObjectCaches == NULL)
return false;
bool match = false;
for (int32 i = 0; !match && i < sGuardedHeapForObjectCachesCount; i++) {
CacheSelector& selector = sGuardedHeapForObjectCaches[i];
if (selector.globPrefix && selector.globSuffix) {
if (strstr(name, selector.name) != NULL)
match = true;
} else if (selector.globPrefix) {
int32 nameLen = strlen(name), selectorLen = strlen(selector.name);
if (strcmp(name + (nameLen - selectorLen), selector.name) == 0)
match = true;
} else if (selector.globSuffix) {
if (strncmp(name, selector.name, strlen(selector.name)) == 0)
match = true;
} else {
if (strcmp(name, selector.name) == 0)
match = true;
}
}
if (match)
dprintf("using guarded heap for object_cache \"%s\"\n", name);
return match;
}
static void
init_object_cache_replacements(struct kernel_args* args)
{
char buffer[1024];
size_t bufferSize = sizeof(buffer);
if (get_safemode_option_early(args, "guarded_heap_for_object_caches",
buffer, &bufferSize) != B_OK)
return;
if (sActiveHeaps[0] != &kernel_guarded_heap) {
// We need to initialize the guarded heap, too.
sActiveHeaps[1] = &kernel_guarded_heap;
init_heap(args, sActiveHeaps[1]);
}
sGuardedHeapForObjectCaches = (CacheSelector*)
kernel_guarded_heap.memalign(0, B_PAGE_SIZE, 0);
memset(sGuardedHeapForObjectCaches, 0, B_PAGE_SIZE);
// Parse the option.
const char* option = buffer;
while (*option != '\0') {
CacheSelector& selector
= sGuardedHeapForObjectCaches[sGuardedHeapForObjectCachesCount++];
size_t nameLength = 0;
char quoteEnd = '\0';
bool phraseEnd = false;
bool seenGlob = false;
while (!phraseEnd && nameLength < (sizeof(selector.name) - 1)) {
if (quoteEnd == '\0' && isspace(*option)) {
option++;
continue;
}
switch (*option) {
case '"':
case '\'':
if (quoteEnd == '\0')
quoteEnd = *option;
else if (quoteEnd == *option)
quoteEnd = '\0';
break;
case '\0':
case ',':
phraseEnd = true;
break;
case '*':
if (nameLength == 0)
selector.globPrefix = true;
else
seenGlob = true;
break;
case '\\':
option++;
// fall through
default:
if (seenGlob) {
dprintf("heap_init: error: unsupported glob pattern\n");
seenGlob = false;
}
selector.name[nameLength++] = *option;
break;
}
option++;
}
if (seenGlob)
selector.globSuffix = true;
if (!phraseEnd) {
dprintf("heap_init: error: pattern overflow after '%s'\n", selector.name);
continue;
}
}
dprintf("guarded_heap_for_object_caches: loaded %" B_PRId32 " selectors\n",
sGuardedHeapForObjectCachesCount);
}
#endif
status_t
heap_init(struct kernel_args* args)
{
char buffer[32];
size_t bufferSize = sizeof(buffer);
if (get_safemode_option_early(args, "kernel_malloc", buffer, &bufferSize) == B_OK) {
if (strcmp(buffer, "guarded") == 0)
sActiveHeaps[0] = &kernel_guarded_heap;
else if (strcmp(buffer, "debug") == 0)
sActiveHeaps[0] = &kernel_debug_heap;
#if !USE_DEBUG_HEAPS_FOR_ALL_OBJECT_CACHES
else if (strcmp(buffer, "slab") == 0)
sActiveHeaps[0] = &kernel_slab_heap;
#endif
else
panic("unknown or unavailable kernel heap '%s'!", buffer);
}
dprintf("kernel malloc: using %s\n", sActiveHeaps[0]->name);
status_t status = init_heap(args, sActiveHeaps[0]);
if (status != B_OK)
return status;
#if GUARDED_HEAP_CAN_REPLACE_OBJECT_CACHES
init_object_cache_replacements(args);
#endif
return B_OK;
} }
status_t status_t
heap_init_post_area() heap_init_post_area()
{ {
if (sHeap->init_post_area == NULL) for (size_t i = 0; i < B_COUNT_OF(sActiveHeaps); i++) {
return B_OK; if (sActiveHeaps[i] == NULL || sActiveHeaps[i]->init_post_area == NULL)
return sHeap->init_post_area(); continue;
status_t status = sActiveHeaps[i]->init_post_area();
if (status != B_OK)
return status;
}
return B_OK;
} }
status_t status_t
heap_init_post_sem() heap_init_post_sem()
{ {
if (sHeap->init_post_sem == NULL) for (size_t i = 0; i < B_COUNT_OF(sActiveHeaps); i++) {
return B_OK; if (sActiveHeaps[i] == NULL || sActiveHeaps[i]->init_post_sem == NULL)
return sHeap->init_post_sem(); continue;
status_t status = sActiveHeaps[i]->init_post_sem();
if (status != B_OK)
return status;
}
return B_OK;
} }
status_t status_t
heap_init_post_thread() heap_init_post_thread()
{ {
if (sHeap->init_post_thread == NULL) for (size_t i = 0; i < B_COUNT_OF(sActiveHeaps); i++) {
return B_OK; if (sActiveHeaps[i] == NULL || sActiveHeaps[i]->init_post_thread == NULL)
return sHeap->init_post_thread(); continue;
status_t status = sActiveHeaps[i]->init_post_thread();
if (status != B_OK)
return status;
}
return B_OK;
} }
void* void*
memalign(size_t alignment, size_t size) memalign(size_t alignment, size_t size)
{ {
return sHeap->memalign(alignment, size, 0); return sActiveHeaps[0]->memalign(alignment, size, 0);
} }
void* void*
memalign_etc(size_t alignment, size_t size, uint32 flags) memalign_etc(size_t alignment, size_t size, uint32 flags)
{ {
return sHeap->memalign(alignment, size, flags); return sActiveHeaps[0]->memalign(alignment, size, flags);
} }
void void
free_etc(void* address, uint32 flags) free_etc(void* address, uint32 flags)
{ {
return sHeap->free(address, flags); return sActiveHeaps[0]->free(address, flags);
} }
void void
free(void *address) free(void *address)
{ {
return sHeap->free(address, 0); return sActiveHeaps[0]->free(address, 0);
} }
void* void*
malloc(size_t size) malloc(size_t size)
{ {
return sHeap->memalign(0, size, 0); return sActiveHeaps[0]->memalign(0, size, 0);
} }
void* void*
realloc_etc(void* address, size_t newSize, uint32 flags) realloc_etc(void* address, size_t newSize, uint32 flags)
{ {
return sHeap->realloc(address, newSize, flags); return sActiveHeaps[0]->realloc(address, newSize, flags);
} }
void* void*
realloc(void *address, size_t newSize) realloc(void *address, size_t newSize)
{ {
return sHeap->realloc(address, newSize, 0); return sActiveHeaps[0]->realloc(address, newSize, 0);
} }
@@ -154,12 +313,12 @@ posix_memalign(void** _pointer, size_t alignment, size_t size)
if ((alignment & (sizeof(void*) - 1)) != 0 || _pointer == NULL) if ((alignment & (sizeof(void*) - 1)) != 0 || _pointer == NULL)
return B_BAD_VALUE; return B_BAD_VALUE;
*_pointer = sHeap->memalign(alignment, size, 0); *_pointer = sActiveHeaps[0]->memalign(alignment, size, 0);
return 0; return 0;
} }
#if USE_DEBUG_HEAPS_FOR_OBJECT_CACHE #if USE_DEBUG_HEAPS_FOR_ALL_OBJECT_CACHES
// #pragma mark - Slab API // #pragma mark - Slab API
@@ -218,7 +377,7 @@ object_cache_set_minimum_reserve(object_cache* cache, size_t objectCount)
void* void*
object_cache_alloc(object_cache* cache, uint32 flags) object_cache_alloc(object_cache* cache, uint32 flags)
{ {
void* object = sHeap->memalign(cache->alignment, cache->object_size, flags); void* object = sActiveHeaps[0]->memalign(cache->alignment, cache->object_size, flags);
if (object == NULL) if (object == NULL)
return NULL; return NULL;
@@ -233,7 +392,7 @@ object_cache_free(object_cache* cache, void* object, uint32 flags)
{ {
if (cache->destructor != NULL) if (cache->destructor != NULL)
cache->destructor(cache->cookie, object); cache->destructor(cache->cookie, object);
return sHeap->free(object, flags); return sActiveHeaps[0]->free(object, flags);
} }
@@ -281,7 +440,7 @@ slab_init_post_thread()
} }
#endif // USE_DEBUG_HEAPS_FOR_OBJECT_CACHE #endif // USE_DEBUG_HEAPS_FOR_ALL_OBJECT_CACHES
#endif // DEBUG_HEAPS #endif // DEBUG_HEAPS
+2
View File
@@ -28,5 +28,7 @@ extern kernel_heap_implementation kernel_slab_heap;
extern kernel_heap_implementation kernel_guarded_heap; extern kernel_heap_implementation kernel_guarded_heap;
extern kernel_heap_implementation kernel_debug_heap; extern kernel_heap_implementation kernel_debug_heap;
bool guarded_heap_replaces_object_cache(const char* name);
#endif // _KERNEL_DEBUG_HEAPS_H #endif // _KERNEL_DEBUG_HEAPS_H
+59 -2
View File
@@ -34,8 +34,12 @@
#include "slab_debug.h" #include "slab_debug.h"
#include "slab_private.h" #include "slab_private.h"
#if DEBUG_HEAPS
#include "../debug/heaps.h"
#endif
#if !USE_DEBUG_HEAPS_FOR_OBJECT_CACHE
#if !USE_DEBUG_HEAPS_FOR_ALL_OBJECT_CACHES
typedef DoublyLinkedList<ObjectCache> ObjectCacheList; typedef DoublyLinkedList<ObjectCache> ObjectCacheList;
@@ -1164,6 +1168,23 @@ create_object_cache_etc(const char* name, size_t objectSize, size_t alignment,
uint32 flags, void* cookie, object_cache_constructor constructor, uint32 flags, void* cookie, object_cache_constructor constructor,
object_cache_destructor destructor, object_cache_reclaimer reclaimer) object_cache_destructor destructor, object_cache_reclaimer reclaimer)
{ {
#if DEBUG_HEAPS && GUARDED_HEAP_CAN_REPLACE_OBJECT_CACHES
if (guarded_heap_replaces_object_cache(name)) {
ObjectCache* cache = (ObjectCache*)
kernel_guarded_heap.memalign(0, sizeof(ObjectCache), flags);
memset((void*)cache, 0, sizeof(ObjectCache));
strcpy(cache->name, name);
cache->object_size = objectSize;
cache->alignment = alignment;
cache->constructor = constructor;
cache->destructor = destructor;
cache->cookie = cookie;
cache->flags = (uint32)-1;
return cache;
}
#endif
ObjectCache* cache; ObjectCache* cache;
if (objectSize == 0) { if (objectSize == 0) {
@@ -1191,6 +1212,13 @@ create_object_cache_etc(const char* name, size_t objectSize, size_t alignment,
void void
delete_object_cache(object_cache* cache) delete_object_cache(object_cache* cache)
{ {
#if DEBUG_HEAPS && GUARDED_HEAP_CAN_REPLACE_OBJECT_CACHES
if (cache->flags == (uint32)-1) {
kernel_guarded_heap.free(cache, 0);
return;
}
#endif
T(Delete(cache)); T(Delete(cache));
{ {
@@ -1224,6 +1252,11 @@ delete_object_cache(object_cache* cache)
status_t status_t
object_cache_set_minimum_reserve(object_cache* cache, size_t objectCount) object_cache_set_minimum_reserve(object_cache* cache, size_t objectCount)
{ {
#if DEBUG_HEAPS && GUARDED_HEAP_CAN_REPLACE_OBJECT_CACHES
if (cache->flags == (uint32)-1)
return B_OK;
#endif
MutexLocker _(cache->lock); MutexLocker _(cache->lock);
if (cache->min_object_reserve == objectCount) if (cache->min_object_reserve == objectCount)
@@ -1240,6 +1273,16 @@ object_cache_set_minimum_reserve(object_cache* cache, size_t objectCount)
void* void*
object_cache_alloc(object_cache* cache, uint32 flags) object_cache_alloc(object_cache* cache, uint32 flags)
{ {
#if DEBUG_HEAPS && GUARDED_HEAP_CAN_REPLACE_OBJECT_CACHES
if (cache->flags == (uint32)-1) {
void* object = kernel_guarded_heap.memalign(cache->alignment,
cache->object_size, flags);
if (cache->constructor != NULL)
cache->constructor(cache->cookie, object);
return object;
}
#endif
void* object = NULL; void* object = NULL;
if ((cache->flags & CACHE_NO_DEPOT) == 0) if ((cache->flags & CACHE_NO_DEPOT) == 0)
object = object_depot_obtain(&cache->depot); object = object_depot_obtain(&cache->depot);
@@ -1307,6 +1350,15 @@ object_cache_alloc(object_cache* cache, uint32 flags)
void void
object_cache_free(object_cache* cache, void* object, uint32 flags) object_cache_free(object_cache* cache, void* object, uint32 flags)
{ {
#if DEBUG_HEAPS && GUARDED_HEAP_CAN_REPLACE_OBJECT_CACHES
if (cache->flags == (uint32)-1) {
if (cache->destructor != NULL)
cache->destructor(cache->cookie, object);
kernel_guarded_heap.free(object, flags);
return;
}
#endif
if (object == NULL) if (object == NULL)
return; return;
@@ -1348,6 +1400,11 @@ object_cache_free(object_cache* cache, void* object, uint32 flags)
status_t status_t
object_cache_reserve(object_cache* cache, size_t objectCount, uint32 flags) object_cache_reserve(object_cache* cache, size_t objectCount, uint32 flags)
{ {
#if DEBUG_HEAPS && GUARDED_HEAP_CAN_REPLACE_OBJECT_CACHES
if (cache->flags == (uint32)-1)
return B_OK;
#endif
if (objectCount == 0) if (objectCount == 0)
return B_OK; return B_OK;
@@ -1453,4 +1510,4 @@ slab_init_post_thread()
RANGE_MARKER_FUNCTION_END(Slab) RANGE_MARKER_FUNCTION_END(Slab)
#endif // !USE_DEBUG_HEAPS_FOR_OBJECT_CACHE #endif // !USE_DEBUG_HEAPS_FOR_ALL_OBJECT_CACHES
+3 -3
View File
@@ -23,7 +23,7 @@
#include "MemoryManager.h" #include "MemoryManager.h"
#if !USE_DEBUG_HEAPS_FOR_OBJECT_CACHE #if !USE_DEBUG_HEAPS_FOR_ALL_OBJECT_CACHES
#if DEBUG_HEAPS #if DEBUG_HEAPS
@@ -353,7 +353,7 @@ heap_init_post_thread()
RANGE_MARKER_FUNCTION_END(slab_allocator) RANGE_MARKER_FUNCTION_END(slab_allocator)
#else // USE_DEBUG_HEAPS_FOR_OBJECT_CACHE #else // USE_DEBUG_HEAPS_FOR_ALL_OBJECT_CACHES
void* void*
@@ -364,4 +364,4 @@ block_alloc_early(size_t size)
} }
#endif // USE_DEBUG_HEAPS_FOR_OBJECT_CACHE #endif // USE_DEBUG_HEAPS_FOR_ALL_OBJECT_CACHES