Files
wazero/internal/platform/mmap_windows.go
Crypt Keeper e0e9e27326 Extracts platform-specific runtime code into its own package (#608)
This moves the platform-specific runtime code (currently only used by
the compiler) into its own package. Specifically, this moves the mmap
logic, and in doing so makes it easier to test, for example new
operating systems.

This also backfills missing RATIONALE about x/sys and hints at a future
possibility to allow a plugin. However, the next step is to get FreeBSD
working natively on the compiler without any additional dependencies.

See #607

Signed-off-by: Adrian Cole <adrian@tetrate.io>
2022-06-01 09:24:02 +08:00

109 lines
3.2 KiB
Go

package platform
import (
"fmt"
"reflect"
"syscall"
"unsafe"
)
var (
kernel32 = syscall.NewLazyDLL("kernel32.dll")
procVirtualAlloc = kernel32.NewProc("VirtualAlloc")
procVirtualProtect = kernel32.NewProc("VirtualProtect")
procVirtualFree = kernel32.NewProc("VirtualFree")
)
const (
windows_MEM_COMMIT uintptr = 0x00001000
windows_MEM_RELEASE uintptr = 0x00008000
windows_PAGE_READWRITE uintptr = 0x00000004
windows_PAGE_EXECUTE_READ uintptr = 0x00000020
windows_PAGE_EXECUTE_READWRITE uintptr = 0x00000040
)
func munmapCodeSegment(code []byte) error {
return freeMemory(code)
}
// allocateMemory commits the memory region via the "VirtualAlloc" function.
// See https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc
func allocateMemory(code []byte, protect uintptr) (uintptr, error) {
address := uintptr(0) // TODO: document why zero
size := uintptr(len(code))
alloctype := windows_MEM_COMMIT
if r, _, err := procVirtualAlloc.Call(address, size, alloctype, protect); r == 0 {
return 0, fmt.Errorf("compiler: VirtualAlloc error: %w", ensureErr(err))
} else {
return r, nil
}
}
// freeMemory releases the memory region via the "VirtualFree" function.
// See https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualfree
func freeMemory(code []byte) error {
address := uintptr(unsafe.Pointer(&code[0]))
size := uintptr(0) // size must be 0 because we're using MEM_RELEASE.
freetype := windows_MEM_RELEASE
if r, _, err := procVirtualFree.Call(address, size, freetype); r == 0 {
return fmt.Errorf("compiler: VirtualFree error: %w", ensureErr(err))
}
return nil
}
func virtualProtect(address, size, newprotect uintptr, oldprotect *uint32) error {
if r, _, err := procVirtualProtect.Call(address, size, newprotect, uintptr(unsafe.Pointer(oldprotect))); r == 0 {
return fmt.Errorf("compiler: VirtualProtect error: %w", ensureErr(err))
}
return nil
}
func mmapCodeSegmentAMD64(code []byte) ([]byte, error) {
p, err := allocateMemory(code, windows_PAGE_EXECUTE_READWRITE)
if err != nil {
return nil, err
}
var mem []byte
sh := (*reflect.SliceHeader)(unsafe.Pointer(&mem))
sh.Data = p
sh.Len = len(code)
sh.Cap = len(code)
copy(mem, code)
return mem, nil
}
func mmapCodeSegmentARM64(code []byte) ([]byte, error) {
p, err := allocateMemory(code, windows_PAGE_READWRITE)
if err != nil {
return nil, err
}
var mem []byte
sh := (*reflect.SliceHeader)(unsafe.Pointer(&mem))
sh.Data = p
sh.Len = len(code)
sh.Cap = len(code)
copy(mem, code)
old := uint32(windows_PAGE_READWRITE)
err = virtualProtect(p, uintptr(len(code)), windows_PAGE_EXECUTE_READ, &old)
if err != nil {
return nil, err
}
return mem, nil
}
// ensureErr returns syscall.EINVAL when the input error is nil.
//
// We are supposed to use "GetLastError" which is more precise, but it is not safe to execute in goroutines. While
// "GetLastError" is thread-local, goroutines are not pinned to threads.
//
// See https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror
func ensureErr(err error) error {
if err != nil {
return err
}
return syscall.EINVAL
}