when a memory leak is detected, report which blocks are unreachable
authorSimon Marlow <marlowsd@gmail.com>
Tue, 9 Sep 2008 14:51:22 +0000 (14:51 +0000)
committerSimon Marlow <marlowsd@gmail.com>
Tue, 9 Sep 2008 14:51:22 +0000 (14:51 +0000)
includes/Block.h
rts/sm/BlockAlloc.c
rts/sm/BlockAlloc.h
rts/sm/MBlock.c
rts/sm/MBlock.h
rts/sm/Storage.c

index 3d7a5c8..28e0374 100644 (file)
@@ -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 -------------------------- */
 
index 81baf6c..daf9fb0 100644 (file)
@@ -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
index 594135a..776df9b 100644 (file)
@@ -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
index f302f84..28aa7b0 100644 (file)
@@ -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)
    -------------------------------------------------------------------------- */
index 17ade51..14244dc 100644 (file)
@@ -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.
 
index 6cccf34..a6134c6 100644 (file)
@@ -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);
 }