os.mmap
Memory pages with read, write and execute permissions: map, change the permissions of and release them.
import os.mmap
Ordinary allocation (mem.Allocate) gives memory you can read and write. This package gives you whole pages straight from the operating system and lets you choose what the program may do with them: PROT_READ, PROT_WRITE and PROT_EXEC, combined with |. Alloc maps zero-filled pages, Protect changes the permissions (mprotect) and Free unmaps them. Use it for guard pages, for read-only data that must not change by accident, and for loading machine code: write the bytes while the page is writable, switch it to read and execute, then call it with os.dl.
Sizes are rounded up to whole pages by the system; PageSize tells the page size (usually 4096, 16384 on Apple silicon). The package works on Linux, macOS and the BSDs. On Windows Supported is false and Alloc returns null.
What people get wrong:
- Asking for write and execute together. Several systems refuse it (SELinux, hardened kernels, macOS on Apple silicon, which also needs a special
MAP_JITflag this package does not set). Do it in two steps: map asPROT_READ | PROT_WRITE, fill, thenProtecttoPROT_READ | PROT_EXEC. - Passing a different size to
FreeorProtectthan toAlloc. Use the same size; the permissions of whole pages change. - Using the memory after
Free, or writing to a page that is now read-only. Both crash the program at once. - Machine code is only valid for one processor family. Code written for x86-64 will not run on an ARM computer, and some ARM systems need the instruction cache flushed after the code is written.
Running a few machine instructions
The example below maps a page, writes seven bytes of x86-64 code that add up its first two arguments, makes the page executable and calls it. It only runs on a 64 bit x86 system, which the example checks with SALAM_ARCH_X64.
import os.dl
import os.mmap
func main:
if not SALAM_ARCH_X64 or not mmap.Supported():
println "this example needs 64 bit x86 and mmap"
ret
end
page := mmap.PageSize()
code := mmap.Alloc(page, mmap.PROT_READ | mmap.PROT_WRITE)
bytes := code as u8*
program := [0x48, 0x89, 0xf8, 0x48, 0x01, 0xf0, 0xc3]
repeat 7 in i:
bytes[i] = program[i] as u8
end
mmap.Protect(code, page, mmap.PROT_READ | mmap.PROT_EXEC)
println "40 + 2 =", dl.Call(code, [40 as size, 2])
mmap.Free(code, page)
endA table that cannot be changed by accident
Fill a page while it is writable, then make it read-only with Protect. From then on a stray write stops the program at once instead of silently corrupting the table, and reads keep working. Make it writable again when the data has to change. The example does that with a table of squares.
import os.mmap
func main:
if not mmap.Supported():
println "memory mapping is not available here"
ret
end
page := mmap.PageSize()
table := mmap.Alloc(page, mmap.PROT_READ | mmap.PROT_WRITE)
defer _freed := mmap.Free(table, page)
cells := table as i32*
repeat 16 in i:
cells[i] = i * i
end
println "filled:", cells[3], cells[15]
println "read-only:", mmap.Protect(table, page, mmap.PROT_READ)
println "still readable:", cells[4]
println "writable again:", mmap.Protect(table, page, mmap.PROT_READ | mmap.PROT_WRITE)
cells[4] = 99
println "after the change:", cells[4]
endConstants and variables
PROT_NONE
const PROT_NONE: i32 = 0import os.mmap
func main:
page := mmap.PageSize()
block := mmap.Alloc(page, mmap.PROT_READ | mmap.PROT_WRITE)
if block == null:
println "mapping failed"
ret
end
bytes := block as u8*
bytes[0] = 7 as u8
bytes[page - 1] = 9 as u8
println bytes[0], bytes[1], bytes[page - 1]
println mmap.Protect(block, page, mmap.PROT_READ)
println bytes[0]
println mmap.Free(block, page)
endPROT_READ
const PROT_READ: i32 = 1The example for this item is shown with the ones above: os-mmap-Alloc.salam
PROT_WRITE
const PROT_WRITE: i32 = 2The example for this item is shown with the ones above: os-mmap-Alloc.salam
PROT_EXEC
const PROT_EXEC: i32 = 4The example for this item is shown with the ones above: os-mmap-Alloc.salam
Functions
Supported
func Supported(): boolThe example for this item is shown with the ones above: os-mmap-Alloc.salam
PageSize
func PageSize(): intThe example for this item is shown with the ones above: os-mmap-Alloc.salam
Alloc
func Alloc(size: int, prot: int): void*size bytes (rounded up to whole pages) of zero-filled private memory with the given permissions and returns its address, or null when it fails, when size is not positive and on Windows.The example for this item is shown with the ones above: os-mmap-Alloc.salam
Protect
func Protect(p: void*, size: int, prot: int): boolsize bytes from p; true on success. p must be the address of a page, as returned by Alloc.The example for this item is shown with the ones above: os-mmap-Alloc.salam
Free
func Free(p: void*, size: int): boolThe example for this item is shown with the ones above: os-mmap-Alloc.salam