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
This commit is contained in:
Michael Lotz
2008-02-12 20:20:35 +00:00
parent d9b0e6addd
commit 4af6cd5f38
2 changed files with 254 additions and 3 deletions
+1
View File
@@ -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
+253 -3
View File
@@ -16,6 +16,9 @@
#include <malloc.h>
#include <signal.h>
#include <string.h>
#include <team.h>
#include <thread.h>
#include <tracing.h>
#include <vm.h>
//#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\") <id>]\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 *