diff options
-rw-r--r-- | includes/Block.h | 4 | ||||
-rw-r--r-- | includes/Storage.h | 4 | ||||
-rw-r--r-- | rts/Adjustor.c | 23 | ||||
-rw-r--r-- | rts/Linker.c | 3 | ||||
-rw-r--r-- | rts/OSMem.h | 10 | ||||
-rw-r--r-- | rts/RtsUtils.c | 52 | ||||
-rw-r--r-- | rts/RtsUtils.h | 3 | ||||
-rw-r--r-- | rts/Storage.c | 99 | ||||
-rw-r--r-- | rts/posix/OSMem.c | 52 | ||||
-rw-r--r-- | rts/win32/OSMem.c | 34 |
10 files changed, 216 insertions, 68 deletions
diff --git a/includes/Block.h b/includes/Block.h index d1705ad686..408088087a 100644 --- a/includes/Block.h +++ b/includes/Block.h @@ -85,8 +85,10 @@ typedef struct bdescr_ { #define BF_PINNED 4 /* Block is part of a compacted generation */ #define BF_COMPACTED 8 -/* Block is free, and on the free list */ +/* Block is free, and on the free list (TODO: is this used?) */ #define BF_FREE 16 +/* Block is executable */ +#define BF_EXEC 32 /* Finding the block descriptor for a given block -------------------------- */ diff --git a/includes/Storage.h b/includes/Storage.h index 3a6bb2fde1..1886e0921e 100644 --- a/includes/Storage.h +++ b/includes/Storage.h @@ -166,6 +166,10 @@ doYouWantToGC( void ) return (alloc_blocks >= alloc_blocks_lim); } +/* memory allocator for executable memory */ +extern void *allocateExec (nat bytes); +extern void freeExec (void *p); + /* ----------------------------------------------------------------------------- Performing Garbage Collection diff --git a/rts/Adjustor.c b/rts/Adjustor.c index fc4182e731..4b042a17b2 100644 --- a/rts/Adjustor.c +++ b/rts/Adjustor.c @@ -40,6 +40,7 @@ Haskell side. #include "Rts.h" #include "RtsExternal.h" #include "RtsUtils.h" +#include "Storage.h" #include <stdlib.h> #if defined(_WIN32) @@ -266,7 +267,7 @@ createAdjustor(int cconv, StgStablePtr hptr, <c>: ff e0 jmp %eax # and jump to it. # the callee cleans up the stack */ - adjustor = stgMallocBytesRWX(14); + adjustor = allocateExec(14); { unsigned char *const adj_code = (unsigned char *)adjustor; adj_code[0x00] = (unsigned char)0x58; /* popl %eax */ @@ -311,7 +312,7 @@ createAdjustor(int cconv, StgStablePtr hptr, That's (thankfully) the case here with the restricted set of return types that we support. */ - adjustor = stgMallocBytesRWX(17); + adjustor = allocateExec(17); { unsigned char *const adj_code = (unsigned char *)adjustor; @@ -340,7 +341,7 @@ createAdjustor(int cconv, StgStablePtr hptr, We offload most of the work to AdjustorAsm.S. */ - AdjustorStub *adjustorStub = stgMallocBytesRWX(sizeof(AdjustorStub)); + AdjustorStub *adjustorStub = allocateExec(sizeof(AdjustorStub)); adjustor = adjustorStub; extern void adjustorCode(void); @@ -443,7 +444,7 @@ createAdjustor(int cconv, StgStablePtr hptr, } if (i < 6) { - adjustor = stgMallocBytesRWX(0x30); + adjustor = allocateExec(0x30); *(StgInt32 *)adjustor = 0x49c1894d; *(StgInt32 *)(adjustor+0x4) = 0x8948c889; @@ -457,7 +458,7 @@ createAdjustor(int cconv, StgStablePtr hptr, } else { - adjustor = stgMallocBytesRWX(0x40); + adjustor = allocateExec(0x40); *(StgInt32 *)adjustor = 0x35ff5141; *(StgInt32 *)(adjustor+0x4) = 0x00000020; @@ -504,7 +505,7 @@ createAdjustor(int cconv, StgStablePtr hptr, similarly, and local variables should be accessed via %fp, not %sp. In a nutshell: This should work! (Famous last words! :-) */ - adjustor = stgMallocBytesRWX(4*(11+1)); + adjustor = allocateExec(4*(11+1)); { unsigned long *const adj_code = (unsigned long *)adjustor; @@ -581,7 +582,7 @@ TODO: Depending on how much allocation overhead stgMallocBytes uses for 4 bytes (getting rid of the nop), hence saving memory. [ccshan] */ ASSERT(((StgWord64)wptr & 3) == 0); - adjustor = stgMallocBytesRWX(48); + adjustor = allocateExec(48); { StgWord64 *const code = (StgWord64 *)adjustor; @@ -686,7 +687,7 @@ TODO: Depending on how much allocation overhead stgMallocBytes uses for */ // allocate space for at most 4 insns per parameter // plus 14 more instructions. - adjustor = stgMallocBytesRWX(4 * (4*n + 14)); + adjustor = allocateExec(4 * (4*n + 14)); code = (unsigned*)adjustor; *code++ = 0x48000008; // b *+8 @@ -845,7 +846,7 @@ TODO: Depending on how much allocation overhead stgMallocBytes uses for #ifdef FUNDESCS adjustorStub = stgMallocBytes(sizeof(AdjustorStub), "createAdjustor"); #else - adjustorStub = stgMallocBytesRWX(sizeof(AdjustorStub)); + adjustorStub = allocateExec(sizeof(AdjustorStub)); #endif adjustor = adjustorStub; @@ -1088,7 +1089,7 @@ if ( *(unsigned char*)ptr != 0xe8 ) { #endif *((unsigned char*)ptr) = '\0'; - stgFree(ptr); + freeExec(ptr); } @@ -1101,7 +1102,7 @@ void initAdjustor(void) { #if defined(i386_HOST_ARCH) && defined(openbsd_HOST_OS) - obscure_ccall_ret_code_dyn = stgMallocBytesRWX(4); + obscure_ccall_ret_code_dyn = allocateExec(4); obscure_ccall_ret_code_dyn[0] = ((unsigned char *)obscure_ccall_ret_code)[0]; obscure_ccall_ret_code_dyn[1] = ((unsigned char *)obscure_ccall_ret_code)[1]; obscure_ccall_ret_code_dyn[2] = ((unsigned char *)obscure_ccall_ret_code)[2]; diff --git a/rts/Linker.c b/rts/Linker.c index 4aca3978b9..3cfd2a8f91 100644 --- a/rts/Linker.c +++ b/rts/Linker.c @@ -707,7 +707,8 @@ typedef struct _RtsSymbolVal { SymX(stg_interp_constr6_entry) \ SymX(stg_interp_constr7_entry) \ SymX(stg_interp_constr8_entry) \ - SymX(stgMallocBytesRWX) \ + SymX(allocateExec) \ + SymX(freeExec) \ SymX(getAllocations) \ SymX(revertCAFs) \ SymX(RtsFlags) \ diff --git a/rts/OSMem.h b/rts/OSMem.h new file mode 100644 index 0000000000..417f106948 --- /dev/null +++ b/rts/OSMem.h @@ -0,0 +1,10 @@ +/* ----------------------------------------------------------------------------- + * + * (c) The University of Glasgow 2006 + * + * OS-specific memory management + * + * ---------------------------------------------------------------------------*/ + +lnat getPageSize (void); +void setExecutable (void *p, lnat len, rtsBool exec); diff --git a/rts/RtsUtils.c b/rts/RtsUtils.c index af68905707..bf02c328fb 100644 --- a/rts/RtsUtils.c +++ b/rts/RtsUtils.c @@ -39,19 +39,6 @@ #include <pthread.h> #endif -#if defined(openbsd_HOST_OS) || defined(linux_HOST_OS) || defined(darwin_HOST_OS) -#include <unistd.h> -#include <sys/types.h> -#include <sys/mman.h> - -/* no C99 header stdint.h on OpenBSD? */ -#if defined(openbsd_HOST_OS) -typedef unsigned long my_uintptr_t; -#else -#include <stdint.h> -typedef uintptr_t my_uintptr_t; -#endif -#endif #if defined(_WIN32) #include <windows.h> @@ -325,42 +312,3 @@ int genericRaise(int sig) { return raise(sig); #endif } - -/* ----------------------------------------------------------------------------- - Allocating executable memory - -------------------------------------------------------------------------- */ - -/* Heavily arch-specific, I'm afraid.. */ - -/* - * Allocate len bytes which are readable, writable, and executable. - * - * ToDo: If this turns out to be a performance bottleneck, one could - * e.g. cache the last VirtualProtect/mprotect-ed region and do - * nothing in case of a cache hit. - */ -void* -stgMallocBytesRWX(int len) -{ - void *addr = stgMallocBytes(len, "mallocBytesRWX"); -#if defined(i386_HOST_ARCH) && defined(_WIN32) - /* This could be necessary for processors which distinguish between READ and - EXECUTE memory accesses, e.g. Itaniums. */ - DWORD dwOldProtect = 0; - if (VirtualProtect (addr, len, PAGE_EXECUTE_READWRITE, &dwOldProtect) == 0) { - barf("mallocBytesRWX: failed to protect 0x%p; error=%lu; old protection: %lu\n", - addr, (unsigned long)GetLastError(), (unsigned long)dwOldProtect); - } -#elif defined(openbsd_HOST_OS) || defined(linux_HOST_OS) || defined(darwin_HOST_OS) - /* malloced memory isn't executable by default on OpenBSD */ - my_uintptr_t pageSize = sysconf(_SC_PAGESIZE); - my_uintptr_t mask = ~(pageSize - 1); - my_uintptr_t startOfFirstPage = ((my_uintptr_t)addr ) & mask; - my_uintptr_t startOfLastPage = ((my_uintptr_t)addr + len - 1) & mask; - my_uintptr_t size = startOfLastPage - startOfFirstPage + pageSize; - if (mprotect((void*)startOfFirstPage, (size_t)size, PROT_EXEC | PROT_READ | PROT_WRITE) != 0) { - barf("mallocBytesRWX: failed to protect 0x%p\n", addr); - } -#endif - return addr; -} diff --git a/rts/RtsUtils.h b/rts/RtsUtils.h index 96a5f0d82f..93139367b3 100644 --- a/rts/RtsUtils.h +++ b/rts/RtsUtils.h @@ -16,9 +16,6 @@ extern void *stgMallocBytes(int n, char *msg) GNUC3_ATTRIBUTE(__malloc__); -extern void* stgMallocBytesRWX(int len) - GNUC3_ATTRIBUTE(__malloc__); - extern void *stgReallocBytes(void *p, int n, char *msg); extern void *stgCallocBytes(int n, int m, char *msg) diff --git a/rts/Storage.c b/rts/Storage.c index 974be45f10..ee860e27a2 100644 --- a/rts/Storage.c +++ b/rts/Storage.c @@ -22,6 +22,7 @@ #include "Storage.h" #include "Schedule.h" #include "RetainerProfile.h" // for counting memory blocks (memInventory) +#include "OSMem.h" #include <stdlib.h> #include <string.h> @@ -968,6 +969,99 @@ calcNeeded(void) return needed; } +/* ---------------------------------------------------------------------------- + Executable memory + + Executable memory must be managed separately from non-executable + memory. Most OSs these days require you to jump through hoops to + dynamically allocate executable memory, due to various security + measures. + + Here we provide a small memory allocator for executable memory. + Memory is managed with a page granularity; we allocate linearly + in the page, and when the page is emptied (all objects on the page + are free) we free the page again, not forgetting to make it + non-executable. + ------------------------------------------------------------------------- */ + +static bdescr *exec_block; + +void *allocateExec (nat bytes) +{ + void *ret; + nat n; + + ACQUIRE_SM_LOCK; + + // round up to words. + n = (bytes + sizeof(W_) + 1) / sizeof(W_); + + if (n+1 > BLOCK_SIZE_W) { + barf("allocateExec: can't handle large objects"); + } + + if (exec_block == NULL || + exec_block->free + n + 1 > exec_block->start + BLOCK_SIZE_W) { + bdescr *bd; + lnat pagesize = getPageSize(); + bd = allocGroup(stg_max(1, pagesize / BLOCK_SIZE)); + IF_DEBUG(gc, debugBelch("allocate exec block %p\n", bd->start)); + bd->gen_no = 0; + bd->flags = BF_EXEC; + bd->link = exec_block; + if (exec_block != NULL) { + exec_block->u.back = bd; + } + bd->u.back = NULL; + setExecutable(bd->start, bd->blocks * BLOCK_SIZE, rtsTrue); + exec_block = bd; + } + *(exec_block->free) = n; // store the size of this chunk + exec_block->gen_no += n; // gen_no stores the number of words allocated + ret = exec_block->free + 1; + exec_block->free += n + 1; + + RELEASE_SM_LOCK + return ret; +} + +void freeExec (void *addr) +{ + StgPtr p = (StgPtr)addr - 1; + bdescr *bd = Bdescr((StgPtr)p); + + if ((bd->flags & BF_EXEC) == 0) { + barf("freeExec: not executable"); + } + + if (*(StgPtr)p == 0) { + barf("freeExec: already free?"); + } + + ACQUIRE_SM_LOCK; + + bd->gen_no -= *(StgPtr)p; + *(StgPtr)p = 0; + + // Free the block if it is empty, but not if it is the block at + // the head of the queue. + if (bd->gen_no == 0 && bd != exec_block) { + IF_DEBUG(gc, debugBelch("free exec block %p\n", bd->start)); + if (bd->u.back) { + bd->u.back->link = bd->link; + } else { + exec_block = bd->link; + } + if (bd->link) { + bd->link->u.back = bd->u.back; + } + setExecutable(bd->start, bd->blocks * BLOCK_SIZE, rtsFalse); + freeGroup(bd); + } + + RELEASE_SM_LOCK +} + /* ----------------------------------------------------------------------------- Debugging @@ -1048,6 +1142,11 @@ memInventory(void) // count the blocks allocated by the arena allocator total_blocks += arenaBlocks(); + // count the blocks containing executable memory + for (bd = exec_block; bd; bd = bd->link) { + total_blocks += bd->blocks; + } + /* count the blocks on the free list */ free_blocks = countFreeList(); diff --git a/rts/posix/OSMem.c b/rts/posix/OSMem.c new file mode 100644 index 0000000000..6e8337bdc6 --- /dev/null +++ b/rts/posix/OSMem.c @@ -0,0 +1,52 @@ +/* ----------------------------------------------------------------------------- + * + * (c) The University of Glasgow 2006 + * + * OS-specific memory management + * + * ---------------------------------------------------------------------------*/ + +#include "Rts.h" +#include "OSMem.h" + +#include <unistd.h> +#include <sys/types.h> +#include <sys/mman.h> + +/* no C99 header stdint.h on OpenBSD? */ +#if defined(openbsd_HOST_OS) +typedef unsigned long my_uintptr_t; +#else +#include <stdint.h> +typedef uintptr_t my_uintptr_t; +#endif + +lnat getPageSize (void) +{ + static lnat pageSize = 0; + if (pageSize) { + return pageSize; + } else { + long ret; + ret = sysconf(_SC_PAGESIZE); + if (ret == -1) { + barf("getPageSize: cannot get page size"); + } + return ret; + } +} + +void setExecutable (void *p, lnat len, rtsBool exec) +{ + my_uintptr_t pageSize = getPageSize(); + + /* malloced memory isn't executable by default on OpenBSD */ + my_uintptr_t mask = ~(pageSize - 1); + my_uintptr_t startOfFirstPage = ((my_uintptr_t)p ) & mask; + my_uintptr_t startOfLastPage = ((my_uintptr_t)p + len - 1) & mask; + my_uintptr_t size = startOfLastPage - startOfFirstPage + pageSize; + if (mprotect((void*)startOfFirstPage, (size_t)size, + (exec ? PROT_EXEC : 0) | PROT_READ | PROT_WRITE) != 0) { + barf("makeExecutable: failed to protect 0x%p\n", p); + } +} diff --git a/rts/win32/OSMem.c b/rts/win32/OSMem.c new file mode 100644 index 0000000000..fe69a341e8 --- /dev/null +++ b/rts/win32/OSMem.c @@ -0,0 +1,34 @@ +/* ----------------------------------------------------------------------------- + * + * (c) The University of Glasgow 2006 + * + * OS-specific memory management + * + * ---------------------------------------------------------------------------*/ + +#include <windows.h> + +lnat getPageSize (void) +{ + static lnat pagesize = 0; + if (pagesize) { + return pagesize; + } else { + SYSTEM_INFO sSysInfo; + GetSystemInfo(&sSysInfo); + pagesize = sSysInfo.dwPageSize; + return pagesize; + } +} + +void setExecutable (void *p, lnat len, rtsBool exec) +{ + DWORD dwOldProtect = 0; + if (VirtualProtect (addr, len, + exec ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE, + &dwOldProtect) == 0) + { + barf("makeExecutable: failed to protect 0x%p; error=%lu; old protection: %lu\n", + addr, (unsigned long)GetLastError(), (unsigned long)dwOldProtect); + } +} |