From 4af6cd5f383319e59b741ca82284084bc9f91e6a Mon Sep 17 00:00:00 2001 From: Michael Lotz Date: Tue, 12 Feb 2008 20:20:35 +0000 Subject: [PATCH] Adding two debug features to the new heap implementation: * Tracing of allocations, reallocations and frees * Leak checking infrastructure to dump allocations The leak checking code records the team and thread id when an allocation is made as well as stores the originally requested size. It also adds the "allocations" debugger command that can dump all current allocations (usually a huge list) or filter by either a team or thread id. This way it's easily possible to find leftover allocations of no more active teams/threads. Combined with the tracing support one might be able to track down the time and reason of an allocation and possibly find the corresponding leak if it is one. Note that kernel heap leak checking has to be enabled manually by setting the KERNEL_HEAP_LEAK_CHECK define to 1. git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@23953 a95241bf-73f2-0310-859d-f6bbb57e9c96 --- headers/private/kernel/tracing_config.h | 1 + src/system/kernel/heap.cpp | 256 +++++++++++++++++++++++- 2 files changed, 254 insertions(+), 3 deletions(-) diff --git a/headers/private/kernel/tracing_config.h b/headers/private/kernel/tracing_config.h index f99b32c610..e23c7c431b 100644 --- a/headers/private/kernel/tracing_config.h +++ b/headers/private/kernel/tracing_config.h @@ -18,6 +18,7 @@ //#define BMESSAGE_TRACING //#define BLOCK_CACHE_TRANSACTION_TRACING +//#define KERNEL_HEAP_TRACING //#define RUNTIME_LOADER_TRACING //#define SIGNAL_TRACING //#define SYSCALL_TRACING diff --git a/src/system/kernel/heap.cpp b/src/system/kernel/heap.cpp index f8c00e6ff4..080b1695f6 100644 --- a/src/system/kernel/heap.cpp +++ b/src/system/kernel/heap.cpp @@ -16,6 +16,9 @@ #include #include #include +#include +#include +#include #include //#define TRACE_HEAP @@ -31,6 +34,16 @@ #define PARANOID_KFREE 1 // validate sanity of the heap after each operation (slow!) #define PARANOID_VALIDATION 0 +// store size, thread and team info at the end of each allocation block +#define KERNEL_HEAP_LEAK_CHECK 0 + +#if KERNEL_HEAP_LEAK_CHECK +typedef struct heap_leak_check_info_s { + size_t size; + thread_id thread; + team_id team; +} heap_leak_check_info; +#endif typedef struct heap_page_s { uint16 index; @@ -74,6 +87,83 @@ static sem_id sHeapGrowSem = -1; static sem_id sHeapGrownNotify = -1; +// #pragma mark - Tracing + +#ifdef KERNEL_HEAP_TRACING +namespace KernelHeapTracing { + +class Allocate : public AbstractTraceEntry { + public: + Allocate(addr_t address, size_t size) + : fAddress(address), + fSize(size) + { + Initialized(); + } + + virtual void AddDump(TraceOutput &out) + { + out.Print("heap allocate: 0x%08lx (%lu bytes)", fAddress, fSize); + } + + private: + addr_t fAddress; + size_t fSize; +}; + + +class Reallocate : public AbstractTraceEntry { + public: + Reallocate(addr_t oldAddress, addr_t newAddress, size_t newSize) + : fOldAddress(oldAddress), + fNewAddress(newAddress), + fNewSize(newSize) + { + Initialized(); + }; + + virtual void AddDump(TraceOutput &out) + { + out.Print("heap reallocate: 0x%08lx -> 0x%08lx (%lu bytes)", + fOldAddress, fNewAddress, fNewSize); + } + + private: + addr_t fOldAddress; + addr_t fNewAddress; + size_t fNewSize; +}; + + +class Free : public AbstractTraceEntry { + public: + Free(addr_t address) + : fAddress(address) + { + Initialized(); + }; + + virtual void AddDump(TraceOutput &out) + { + out.Print("heap free: 0x%08lx", fAddress); + } + + private: + addr_t fAddress; +}; + + +} // namespace KernelHeapTracing + +# define T(x) if (!kernel_startup) new(std::nothrow) KernelHeapTracing::x; +#else +# define T(x) ; +#endif + + +// #pragma mark - Debug functions + + static void dump_page(heap_page *page) { @@ -129,6 +219,104 @@ dump_heap_list(int argc, char **argv) } +#if KERNEL_HEAP_LEAK_CHECK +static int +dump_allocations(int argc, char **argv) +{ + team_id team = -1; + thread_id thread = -1; + if (argc == 3) { + if (strcmp(argv[1], "team") == 0) + team = strtoul(argv[2], NULL, 0); + else if (strcmp(argv[1], "thread") == 0) + thread = strtoul(argv[2], NULL, 0); + else { + print_debugger_command_usage(argv[0]); + return 0; + } + } else if (argc != 1) { + print_debugger_command_usage(argv[0]); + return 0; + } + + size_t totalSize = 0; + uint32 totalCount = 0; + heap_allocator *heap = sHeapList; + while (heap) { + // go through all the pages + heap_leak_check_info *info = NULL; + for (uint32 i = 0; i < heap->page_count; i++) { + heap_page *page = &heap->page_table[i]; + if (!page->in_use) + continue; + + addr_t base = heap->base + i * B_PAGE_SIZE; + if (page->bin_index < heap->bin_count) { + // page is used by a small allocation bin + uint32 elementCount = page->empty_index; + size_t elementSize = heap->bins[page->bin_index].element_size; + for (uint32 j = 0; j < elementCount; j++, base += elementSize) { + // walk the free list to see if this element is in use + bool elementInUse = true; + for (addr_t *temp = page->free_list; temp != NULL; temp = (addr_t *)*temp) { + if ((addr_t)temp == base) { + elementInUse = false; + break; + } + } + + if (!elementInUse) + continue; + + info = (heap_leak_check_info *)(base + elementSize + - sizeof(heap_leak_check_info)); + + if ((team == -1 && thread == -1) + || (team == -1 && info->thread == thread) + || (thread == -1 && info->team == team)) { + // interesting... + dprintf("team: % 6ld; thread: % 6ld; address: 0x%08lx; size: %lu bytes\n", + info->team, info->thread, base, info->size); + totalSize += info->size; + totalCount++; + } + } + } else { + // page is used by a big allocation, find the page count + uint32 pageCount = 1; + while (i + pageCount < heap->page_count + && heap->page_table[i + pageCount].in_use + && heap->page_table[i + pageCount].bin_index == heap->bin_count + && heap->page_table[i + pageCount].allocation_id == page->allocation_id) + pageCount++; + + info = (heap_leak_check_info *)(base + pageCount * B_PAGE_SIZE + - sizeof(heap_leak_check_info)); + + if ((team == -1 && thread == -1) + || (team == -1 && info->thread == thread) + || (thread == -1 && info->team == team)) { + // interesting... + dprintf("team: % 6ld; thread: % 6ld; address: 0x%08lx; size: %lu bytes\n", + info->team, info->thread, base, info->size); + totalSize += info->size; + totalCount++; + } + + // skip the allocated pages + i += pageCount - 1; + } + } + + heap = heap->next; + } + + dprintf("total allocations: %lu; total bytes: %lu\n", totalCount, totalSize); + return 0; +} +#endif // KERNEL_HEAP_LEAK_CHECK + + #if PARANOID_VALIDATION static void heap_validate_heap(heap_allocator *heap) @@ -238,7 +426,10 @@ heap_validate_heap(heap_allocator *heap) mutex_unlock(&heap->lock); } -#endif +#endif // PARANOID_VALIDATION + + +// #pragma mark - Heap functions heap_allocator * @@ -371,6 +562,13 @@ heap_raw_alloc(heap_allocator *heap, size_t size, uint32 binIndex) page->next = page->prev = NULL; } +#if KERNEL_HEAP_LEAK_CHECK + heap_leak_check_info *info = (heap_leak_check_info *)((addr_t)address + + bin->element_size - sizeof(heap_leak_check_info)); + info->size = size - sizeof(heap_leak_check_info); + info->thread = (kernel_startup ? 0 : thread_get_current_thread_id()); + info->team = (kernel_startup ? 0 : team_get_current_team_id()); +#endif return address; } @@ -400,6 +598,15 @@ heap_raw_alloc(heap_allocator *heap, size_t size, uint32 binIndex) bin->page_list = page; } +#if KERNEL_HEAP_LEAK_CHECK + heap_leak_check_info *info = (heap_leak_check_info *)(heap->base + + page->index * B_PAGE_SIZE + bin->element_size + - sizeof(heap_leak_check_info)); + info->size = size - sizeof(heap_leak_check_info); + info->thread = (kernel_startup ? 0 : thread_get_current_thread_id()); + info->team = (kernel_startup ? 0 : team_get_current_team_id()); +#endif + // we return the first slot in this page return (void *)(heap->base + page->index * B_PAGE_SIZE); } @@ -441,6 +648,13 @@ heap_raw_alloc(heap_allocator *heap, size_t size, uint32 binIndex) page->allocation_id = allocationID; } +#if KERNEL_HEAP_LEAK_CHECK + heap_leak_check_info *info = (heap_leak_check_info *)(heap->base + + (first + pageCount) * B_PAGE_SIZE - sizeof(heap_leak_check_info)); + info->size = size - sizeof(heap_leak_check_info); + info->thread = (kernel_startup ? 0 : thread_get_current_thread_id()); + info->team = (kernel_startup ? 0 : team_get_current_team_id()); +#endif return (void *)(heap->base + first * B_PAGE_SIZE); } @@ -468,6 +682,10 @@ heap_memalign(heap_allocator *heap, size_t alignment, size_t size, mutex_lock(&heap->lock); +#if KERNEL_HEAP_LEAK_CHECK + size += sizeof(heap_leak_check_info); +#endif + // ToDo: that code "aligns" the buffer because the bins are always // aligned on their bin size if (size < alignment) @@ -491,6 +709,11 @@ heap_memalign(heap_allocator *heap, size_t alignment, size_t size, || heap->free_pages->next->next == NULL); } +#if KERNEL_HEAP_LEAK_CHECK + size -= sizeof(heap_leak_check_info); +#endif + + T(Allocate((addr_t)address, size)); mutex_unlock(&heap->lock); if (address == NULL) return address; @@ -619,6 +842,7 @@ heap_free(heap_allocator *heap, void *address) } } + T(Free((addr_t)address)); mutex_unlock(&heap->lock); return B_OK; } @@ -670,14 +894,32 @@ heap_realloc(heap_allocator *heap, void *address, void **newAddress, mutex_unlock(&heap->lock); +#if KERNEL_HEAP_LEAK_CHECK + newSize += sizeof(heap_leak_check_info); +#endif + // does the new allocation simply fit in the old allocation? if (newSize > minSize && newSize <= maxSize) { +#if KERNEL_HEAP_LEAK_CHECK + // update the size info (the info is at the end so stays where it is) + heap_leak_check_info *info = (heap_leak_check_info *)((addr_t)address + maxSize); + info->size = newSize - sizeof(heap_leak_check_info); + newSize -= sizeof(heap_leak_check_info); +#endif + + T(Reallocate((addr_t)address, (addr_t)address, newSize)); *newAddress = address; return B_OK; } +#if KERNEL_HEAP_LEAK_CHECK + // new leak check info will be created with the malloc below + newSize -= sizeof(heap_leak_check_info); +#endif + // if not, allocate a new chunk of memory *newAddress = malloc(newSize); + T(Reallocate((addr_t)address, (addr_t)*newAddress, newSize)); if (*newAddress == NULL) { // we tried but it didn't work out, but still the operation is done return B_OK; @@ -749,7 +991,15 @@ heap_init(addr_t base, size_t size) sHeapList = heap_attach(base, size, false); // set up some debug commands - add_debugger_command("heap", &dump_heap_list, "dump stats about the kernel heap(s)"); + add_debugger_command("heap", &dump_heap_list, "Dump stats about the kernel heap(s)"); +#if KERNEL_HEAP_LEAK_CHECK + add_debugger_command_etc("allocations", &dump_allocations, + "Dump current allocations", "[(\"team\" | \"thread\") ]\n" + "If no parameters are given, all current alloactions are dumped.\n" + "If either \"team\" or \"thread\" is specified as the first argument,\n" + "only allocations matching the team or thread id given in the second\n" + "argument are printed.\n", 0); +#endif return B_OK; } @@ -794,7 +1044,7 @@ heap_init_post_thread() } -// #pragma mark - +// #pragma mark - Public API void *