summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon Marlow <marlowsd@gmail.com>2008-09-09 14:51:22 +0000
committerSimon Marlow <marlowsd@gmail.com>2008-09-09 14:51:22 +0000
commit43a120b706a5eece6624ca4907af89fc9a480c5e (patch)
tree62d0b792c19ec86e089b032d5c13f6e3d3710a47
parentccb958030d8e3afcef8ef905372f9586e5ada2a3 (diff)
downloadhaskell-43a120b706a5eece6624ca4907af89fc9a480c5e.tar.gz
when a memory leak is detected, report which blocks are unreachable
-rw-r--r--includes/Block.h3
-rw-r--r--rts/sm/BlockAlloc.c32
-rw-r--r--rts/sm/BlockAlloc.h2
-rw-r--r--rts/sm/MBlock.c93
-rw-r--r--rts/sm/MBlock.h5
-rw-r--r--rts/sm/Storage.c54
6 files changed, 184 insertions, 5 deletions
diff --git a/includes/Block.h b/includes/Block.h
index 3d7a5c8a86..28e0374492 100644
--- a/includes/Block.h
+++ b/includes/Block.h
@@ -92,7 +92,8 @@ typedef struct bdescr_ {
#define BF_EXEC 32
/* Block contains only a small amount of live data */
#define BF_FRAGMENTED 64
-
+/* we know about this block (for finding leaks) */
+#define BF_KNOWN 128
/* Finding the block descriptor for a given block -------------------------- */
diff --git a/rts/sm/BlockAlloc.c b/rts/sm/BlockAlloc.c
index 81baf6c196..daf9fb0ba1 100644
--- a/rts/sm/BlockAlloc.c
+++ b/rts/sm/BlockAlloc.c
@@ -727,4 +727,36 @@ countFreeList(void)
}
return total_blocks;
}
+
+void
+markBlocks (bdescr *bd)
+{
+ for (; bd != NULL; bd = bd->link) {
+ bd->flags |= BF_KNOWN;
+ }
+}
+
+void
+reportUnmarkedBlocks (void)
+{
+ void *mblock;
+ bdescr *bd;
+
+ debugBelch("Unreachable blocks:\n");
+ for (mblock = getFirstMBlock(); mblock != NULL;
+ mblock = getNextMBlock(mblock)) {
+ for (bd = FIRST_BDESCR(mblock); bd <= LAST_BDESCR(mblock); ) {
+ if (!(bd->flags & BF_KNOWN) && bd->free != (P_)-1) {
+ debugBelch(" %p\n",bd);
+ }
+ if (bd->blocks >= BLOCKS_PER_MBLOCK) {
+ mblock += (BLOCKS_TO_MBLOCKS(bd->blocks) - 1) * MBLOCK_SIZE;
+ break;
+ } else {
+ bd += bd->blocks;
+ }
+ }
+ }
+}
+
#endif
diff --git a/rts/sm/BlockAlloc.h b/rts/sm/BlockAlloc.h
index 594135ac56..776df9b627 100644
--- a/rts/sm/BlockAlloc.h
+++ b/rts/sm/BlockAlloc.h
@@ -14,6 +14,8 @@
#ifdef DEBUG
extern void checkFreeListSanity(void);
nat countFreeList(void);
+void markBlocks (bdescr *bd);
+void reportUnmarkedBlocks (void);
#endif
extern lnat n_alloc_blocks; // currently allocated blocks
diff --git a/rts/sm/MBlock.c b/rts/sm/MBlock.c
index f302f844e9..28aa7b0a43 100644
--- a/rts/sm/MBlock.c
+++ b/rts/sm/MBlock.c
@@ -34,13 +34,13 @@ StgWord8 mblock_map[MBLOCK_MAP_SIZE]; // initially all zeros
#elif SIZEOF_VOID_P == 8
static MBlockMap dummy_mblock_map;
MBlockMap *mblock_cache = &dummy_mblock_map;
-int mblock_map_count = 0;
+nat mblock_map_count = 0;
MBlockMap **mblock_maps = NULL;
static MBlockMap *
findMBlockMap(void *p)
{
- int i;
+ nat i;
StgWord32 hi = (StgWord32) (((StgWord)p) >> 32);
for( i = 0; i < mblock_map_count; i++ )
{
@@ -86,6 +86,95 @@ markHeapAlloced(void *p)
#endif
}
+/* ----------------------------------------------------------------------------
+ Debugging code for traversing the allocated MBlocks
+
+ This is used for searching for lost blocks when a memory leak is
+ detected; see Blocks.c:findUnmarkedBlock().
+ ------------------------------------------------------------------------ */
+
+#ifdef DEBUG
+
+#if SIZEOF_VOID_P == 4
+
+STATIC_INLINE
+void * mapEntryToMBlock(nat i)
+{
+ return (void *)((StgWord)i << MBLOCK_SHIFT);
+}
+
+void * getFirstMBlock(void)
+{
+ nat i;
+
+ for (i = 0; i < MBLOCK_MAP_SIZE; i++) {
+ if (mblock_map[i]) return mapEntryToMBlock(i);
+ }
+ return NULL;
+}
+
+void * getNextMBlock(void *mblock)
+{
+ nat i;
+
+ for (i = MBLOCK_MAP_ENTRY(mblock) + 1; i < MBLOCK_MAP_SIZE; i++) {
+ if (mblock_map[i]) return mapEntryToMBlock(i);
+ }
+ return NULL;
+}
+
+#elif SIZEOF_VOID_P == 8
+
+STATIC_INLINE
+void * mapEntryToMBlock(MBlockMap *map, nat i)
+{
+ return (void *)(((StgWord)map->addrHigh32) << 32) +
+ ((StgWord)i << MBLOCK_SHIFT);
+}
+
+void * getFirstMBlock(void)
+{
+ MBlockMap *map;
+ nat i, j;
+
+ for (j = 0; j < mblock_map_count; j++) {
+ map = mblock_maps[j];
+ for (i = 0; i < MBLOCK_MAP_SIZE; i++) {
+ if (map->mblocks[i]) return mapEntryToMBlock(map,i);
+ }
+ }
+ return NULL;
+}
+
+void * getNextMBlock(void *mblock)
+{
+ MBlockMap *map;
+ nat i, j;
+
+ for (j = 0; j < mblock_map_count; j++) {
+ map = mblock_maps[j];
+ if (map->addrHigh32 == (StgWord)mblock >> 32) break;
+ }
+ if (j == mblock_map_count) return NULL;
+
+ for (; j < mblock_map_count; j++) {
+ map = mblock_maps[j];
+ if (map->addrHigh32 == (StgWord)mblock >> 32) {
+ i = MBLOCK_MAP_ENTRY(mblock) + 1;
+ } else {
+ i = 0;
+ }
+ for (; i < MBLOCK_MAP_SIZE; i++) {
+ if (map->mblocks[i]) return mapEntryToMBlock(map,i);
+ }
+ }
+ return NULL;
+}
+
+#endif // SIZEOF_VOID_P
+
+#endif // DEBUG
+
/* -----------------------------------------------------------------------------
Allocate new mblock(s)
-------------------------------------------------------------------------- */
diff --git a/rts/sm/MBlock.h b/rts/sm/MBlock.h
index 17ade51761..14244dcef3 100644
--- a/rts/sm/MBlock.h
+++ b/rts/sm/MBlock.h
@@ -16,6 +16,11 @@ extern void * getMBlock(void);
extern void * getMBlocks(nat n);
extern void freeAllMBlocks(void);
+#ifdef DEBUG
+extern void *getFirstMBlock(void);
+extern void *getNextMBlock(void *mblock);
+#endif
+
/* -----------------------------------------------------------------------------
The HEAP_ALLOCED() test.
diff --git a/rts/sm/Storage.c b/rts/sm/Storage.c
index 6cccf34121..a6134c6a62 100644
--- a/rts/sm/Storage.c
+++ b/rts/sm/Storage.c
@@ -1270,6 +1270,51 @@ stepBlocks (step *stp)
countAllocdBlocks(stp->large_objects);
}
+// If memInventory() calculates that we have a memory leak, this
+// function will try to find the block(s) that are leaking by marking
+// all the ones that we know about, and search through memory to find
+// blocks that are not marked. In the debugger this can help to give
+// us a clue about what kind of block leaked. In the future we might
+// annotate blocks with their allocation site to give more helpful
+// info.
+static void
+findMemoryLeak (void)
+{
+ nat g, s, i;
+ for (g = 0; g < RtsFlags.GcFlags.generations; g++) {
+ for (i = 0; i < n_capabilities; i++) {
+ markBlocks(capabilities[i].mut_lists[g]);
+ }
+ markBlocks(generations[g].mut_list);
+ for (s = 0; s < generations[g].n_steps; s++) {
+ markBlocks(generations[g].steps[s].blocks);
+ markBlocks(generations[g].steps[s].large_objects);
+ }
+ }
+
+ for (i = 0; i < n_nurseries; i++) {
+ markBlocks(nurseries[i].blocks);
+ markBlocks(nurseries[i].large_objects);
+ }
+
+#ifdef PROFILING
+ // TODO:
+ // if (RtsFlags.ProfFlags.doHeapProfile == HEAP_BY_RETAINER) {
+ // markRetainerBlocks();
+ // }
+#endif
+
+ // count the blocks allocated by the arena allocator
+ // TODO:
+ // markArenaBlocks();
+
+ // count the blocks containing executable memory
+ markBlocks(exec_block);
+
+ reportUnmarkedBlocks();
+}
+
+
void
memInventory (rtsBool show)
{
@@ -1327,8 +1372,6 @@ memInventory (rtsBool show)
leak = live_blocks + free_blocks != mblocks_allocated * BLOCKS_PER_MBLOCK;
- ASSERT(n_alloc_blocks == live_blocks);
-
if (show || leak)
{
if (leak) {
@@ -1357,6 +1400,13 @@ memInventory (rtsBool show)
mblocks_allocated * BLOCKS_PER_MBLOCK, mblocks_allocated);
}
}
+
+ if (leak) {
+ debugBelch("\n");
+ findMemoryLeak();
+ }
+ ASSERT(n_alloc_blocks == live_blocks);
+ ASSERT(!leak);
}