Use mmap to allocate cells and mprotect to watch them
authorLeandro Lucarella <llucax@gmail.com>
Sun, 6 Sep 2009 20:22:07 +0000 (17:22 -0300)
committerLeandro Lucarella <llucax@gmail.com>
Sun, 6 Sep 2009 20:27:16 +0000 (17:27 -0300)
Cells are allocated using mmap() to be able to easily mprotect() cell data
to detect mutator reads/writes to freed/swept cells. Using mprotect()
makes the program die with a SIGSEGV *exactly* where it tried to use a
free cell.

This patch assumes a page size of 4096, it uses a full page for the cell
header to be able to mprotect just the cell data (because mprotect ca only
protect full pages), allowing the GC to keep manipulating cell headers as
needed.

gc/cell.d
gc/gc.d

index cd8fae1..978ca95 100644 (file)
--- a/gc/cell.d
+++ b/gc/cell.d
@@ -16,6 +16,7 @@
 module gc.cell;
 
 import cstdlib = tango.stdc.stdlib;
+import mman = tango.stdc.posix.sys.mman;
 
 package:
 
@@ -97,10 +98,18 @@ struct Cell
      */
     static Cell* alloc(size_t size, uint attr = 0)
     {
-        auto cell = cast(Cell*) cstdlib.malloc(size + Cell.sizeof);
-        if (cell is null)
+        // The capacity is increased to the number of bytes used by the minimun
+        // number of pages needed to allocate the requested size.
+        size_t capacity = size + 4096 - size % 4096;
+        // Allocate one page for the cell header and as many pages as necessary
+        // for the cell data using mmap(2). Page size is assumed to be 4096.
+        auto ptr = mman.mmap(null, 4096 + capacity,
+                mman.PROT_READ | mman.PROT_WRITE,
+                mman.MAP_PRIVATE | mman.MAP_ANON, -1, 0);
+        if (ptr == mman.MAP_FAILED)
             return null;
-        cell.capacity = size;
+        auto cell = cast(Cell*) ptr;
+        cell.capacity = capacity;
         cell.size = size;
         cell.attr = cast(BlkAttr) attr;
         cell.marked = true;
@@ -111,7 +120,9 @@ struct Cell
     /// Free a cell allocated by Cell.alloc().
     static void free(Cell* cell)
     {
-        cstdlib.free(cell);
+        // Unmap the mmap(2)ed memory. The extra page (4096) is the cell header.
+        int r = mman.munmap(cell, 4096 + cell.capacity);
+        assert (r != -1);
     }
 
     /**
@@ -124,13 +135,15 @@ struct Cell
     {
         if (ptr is null)
             return null;
-        return cast(Cell*) (cast(byte*) ptr - Cell.sizeof);
+        // Subtract one page to the pointer to get the start of the cell header.
+        return cast(Cell*) (cast(byte*) ptr - 4096);
     }
 
     /// Get the base address of the object stored in the cell.
     void* ptr()
     {
-        return cast(void*) (cast(byte*) this + Cell.sizeof);
+        // Add one page to the header pointer to get the start of the cell data.
+        return cast(void*) (cast(byte*) this + 4096);
     }
 
     /// Return true if the cell should be finalized, false otherwise.
diff --git a/gc/gc.d b/gc/gc.d
index a10143a..147617d 100644 (file)
--- a/gc/gc.d
+++ b/gc/gc.d
@@ -37,6 +37,9 @@ import gc.arch: push_registers, pop_registers;
 
 // Standard imports
 import cstring = tango.stdc.string;
+import mman = tango.stdc.posix.sys.mman;
+// XXX: missing in Tango 0.99.8 for Linux
+extern (C) int mprotect(void*, size_t, int);
 
 // Debug imports
 
@@ -318,6 +321,10 @@ private:
                 this.live_list.unlink(cell);
                 if (cell.has_finalizer)
                     rt_finalize(cell.ptr, false);
+                // Forbid the mutator read/write cell data
+                int r = mprotect(cast(byte*) cell + 4096, cell.capacity,
+                        mman.PROT_NONE);
+                assert (r != -1);
                 this.free_list.link(cell);
             }
         }
@@ -541,6 +548,10 @@ public:
         return null;
 
     reuse:
+        // Allow the mutator to read/write cell data
+        int r = mprotect(cast(byte*) cell + 4096, cell.capacity,
+                mman.PROT_READ | mman.PROT_WRITE);
+        assert (r != -1);
         cell.size = size;
         cell.attr = cast(BlkAttr) attr;
 
@@ -580,6 +591,7 @@ public:
      */
     void* realloc(void* ptr, size_t size, uint attr=0)
     {
+        debug (gc_malloc) printf("gc.realloc(%p, %u, %u)\n", ptr, size, attr);
 
         // Undercover malloc()
         if (ptr is null)
@@ -596,6 +608,9 @@ public:
 
         // We have enough capacity already, just change the size
         if (cell.capacity >= size) {
+            debug (gc_malloc)
+                printf("gc.realloc() - cell has %zu capacity, no need to "
+                        "realloc\n", cell.capacity);
             cell.size = size;
             return cell.ptr;
         }
@@ -654,6 +669,10 @@ public:
         auto cell = Cell.alloc(size);
         if (cell is null)
             return 0;
+        // Forbid the mutator read/write cell data
+        int r = mprotect(cast(byte*) cell + 4096, cell.capacity,
+                mman.PROT_NONE);
+        assert (r != -1);
         this.free_list.link(cell);
         return cell.capacity;
     }
@@ -671,12 +690,19 @@ public:
      */
     void free(void* ptr)
     {
+        debug (gc_malloc) printf("gc.free(%p)\n", ptr);
+
         if (ptr is null)
             return;
 
         auto cell = this.live_list.pop(ptr);
         assert (cell !is null);
 
+        // Forbid the mutator read/write cell data
+        int r = mprotect(cast(byte*) cell + 4096, cell.capacity,
+                mman.PROT_NONE);
+        assert (r != -1);
+
         this.free_list.link(cell);
     }