This narrows to what the `internal/platform` package supports, which is currently bound by Go SDK source that include `Mprotect` or windows. Ex. `zsyscall_linux_amd64.go` includes `Mprotect`, but `zsyscall_freebsd_amd64.go` does not. This should prevent errors like below, by allowing `wazero.NewRuntime()` to properly fallback to the interpreter. ``` .../mmap.go:74:16: undefined: syscall.Mprotect ``` A later change will implement FreeBSD. This is just here to ensure users don't crash on unexpected OS. See #607 Signed-off-by: Adrian Cole <adrian@tetrate.io>
111 lines
3.2 KiB
Go
111 lines
3.2 KiB
Go
//go:build windows
|
|
|
|
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
|
|
}
|