Commit e3c55aeb authored by Simon Marlow's avatar Simon Marlow

replace stgMallocBytesRWX() with our own allocator

See bug #738

Allocating executable memory is getting more difficult these days.  In
particular, the default SELinux policy on Fedora Core 5 disallows
making the heap (i.e. malloc()'d memory) executable, although it does
apparently allow mmap()'ing anonymous executable memory by default.

Previously, stgMallocBytesRWX() used malloc() underneath, and then
tried to make the page holding the memory executable.  This was rather
hacky and fails with Fedora Core 5.  

This patch adds a mini-allocator for executable memory, based on the
block allocator.  We grab page-sized blocks and make them executable,
then allocate small objects from the page.  There's a simple free
function, that will free whole pages back to the system when they are
empty.
parent 6b36d8ad
......@@ -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 -------------------------- */
......
......@@ -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
......
......@@ -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];
......
......@@ -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) \
......
/* -----------------------------------------------------------------------------
*
* (c) The University of Glasgow 2006
*
* OS-specific memory management
*
* ---------------------------------------------------------------------------*/
lnat getPageSize (void);
void setExecutable (void *p, lnat len, rtsBool exec);
......@@ -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;
}
......@@ -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)
......
......@@ -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();
......
/* -----------------------------------------------------------------------------
*
* (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);
}
}
/* -----------------------------------------------------------------------------
*
* (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);
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment