From cc637a89be9b51945246bd65fa15080464a90aca Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Sun, 6 Sep 2009 17:22:07 -0300 Subject: [PATCH] Use mmap to allocate cells and mprotect to watch them 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 | 25 +++++++++++++++++++------ gc/gc.d | 26 ++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/gc/cell.d b/gc/cell.d index cd8fae1..978ca95 100644 --- 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 --- 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); } -- 1.7.5.4