--wrappers around mmap to support dynamic code exection. --Written by Cosmin Apreutesei. Public Domain. --Tested with Windows, Linux and OSX, x86 and x86-64. local ffi = require'ffi' local C = ffi.C local function checkh(ptr) return assert(ptr ~= nil and ptr) end local function checkz(ret) assert(ret == 0) end local function checknz(ret) assert(ret ~= 0) end local new, free, protect --Using VirtualAlloc allows memory protection, but can only allocate memory in multiple-of-64K chunks. local USE_VIRTUALALLOC = false if ffi.os == 'Windows' then if USE_VIRTUALALLOC then ffi.cdef[[ void* VirtualAlloc(void* lpAddress, size_t dwSize, uint32_t flAllocationType, uint32_t flProtect); int VirtualFree(void* lpAddress, size_t dwSize, uint32_t dwFreeType); int VirtualProtect(void* lpAddress, size_t dwSize, uint32_t flNewProtect, uint32_t* lpflOldProtect); ]] local PAGE_READWRITE = 0x04 local PAGE_EXECUTE_READ = 0x20 local MEM_COMMIT = 0x1000 local MEM_RESERVE = 0x2000 local MEM_RELEASE = 0x8000 function new(size) return checkh(C.VirtualAlloc(nil, size, bit.bor(MEM_RESERVE, MEM_COMMIT), PAGE_READWRITE)) end function protect(addr, size) local oldprotect = ffi.new'uint32_t[1]' --because null not accepted checknz(C.VirtualProtect(addr, size, PAGE_EXECUTE_READ, oldprotect)) end function free(addr, size) assert(size, 'size required') --on other platforms checknz(C.VirtualFree(addr, 0, MEM_RELEASE)) end else local HEAP_NO_SERIALIZE = 0x00000001 local HEAP_ZERO_MEMORY = 0x00000008 local HEAP_CREATE_ENABLE_EXECUTE = 0x00040000 ffi.cdef[[ void* HeapCreate(uint32_t flOptions, size_t dwInitialSize, size_t dwMaximumSize); void* HeapAlloc(void* hHeap, uint32_t dwFlags, size_t dwBytes); int HeapFree(void* hHeap, uint32_t dwFlags, void* lpMem); ]] local heap function new(size) heap = heap or checkh(C.HeapCreate(bit.bor(HEAP_NO_SERIALIZE, HEAP_CREATE_ENABLE_EXECUTE), 0, 0)) return checkh(C.HeapAlloc(heap, HEAP_ZERO_MEMORY, size)) end function protect(addr, size) end function free(addr, size) assert(size, 'size required') --on other platforms checknz(C.HeapFree(heap, HEAP_NO_SERIALIZE, addr)) end end elseif ffi.os == 'Linux' or ffi.os == 'OSX' then if ffi.os == 'OSX' then ffi.cdef'typedef int64_t off_t;' else ffi.cdef'typedef long int off_t;' end ffi.cdef[[ void* mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); int munmap(void *addr, size_t length); int mprotect(void *addr, size_t len, int prot); ]] local PROT_READ = 1 local PROT_WRITE = 2 local PROT_EXEC = 4 local MAP_PRIVATE = 2 local MAP_ANON = ffi.os == 'Linux' and 0x20 or 0x1000 function new(size) local ret = C.mmap(nil, size, bit.bor(PROT_READ, PROT_WRITE), bit.bor(MAP_PRIVATE, MAP_ANON), -1, 0) if ffi.cast('intptr_t', ret) == ffi.cast('intptr_t', -1) then error(string.format('mmap errno: %d', ffi.errno())) end return checkh(ret) end function protect(addr, size) checkz(C.mprotect(addr, size, bit.bor(PROT_READ, PROT_EXEC))) end function free(addr, size) checkz(C.munmap(addr, size)) end end local new = function(size) --override for hooking to gc local addr = new(size) ffi.gc(addr, function(addr) free(addr, size) ffi.gc(addr, nil) end) return addr end if not ... then local function test(size) local addr = new(size) print(addr) addr = ffi.cast('int32_t*', addr) assert(addr[0] == 0) addr[0] = 1234 --writable assert(addr[0] == 1234) protect(addr, size) --addr[0] = 4321 --uncomment this to get a crash (r/o memory); TODO: test if executable --addr = nil; collectgarbage() --enable this to fail the assertion below return addr end local a1 = test(64*1024*1000) --64MB local a2 = test(16) --16 bytes assert(a1 ~= a2) --different pages a1 = nil a2 = nil collectgarbage() --TODO: test if finalizer was called end return {new = new, free = free, protect = protect}