107 lines
3.2 KiB
Go
107 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(size uintptr, protect uintptr) (uintptr, error) {
|
|
address := uintptr(0) // system determines where to allocate the region.
|
|
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 := 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(uintptr(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(size int) ([]byte, error) {
|
|
p, err := allocateMemory(uintptr(size), windows_PAGE_EXECUTE_READWRITE)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var mem []byte
|
|
sh := (*reflect.SliceHeader)(unsafe.Pointer(&mem))
|
|
sh.Data = p
|
|
sh.Len = size
|
|
sh.Cap = size
|
|
return mem, err
|
|
}
|
|
|
|
func mmapCodeSegmentARM64(size int) ([]byte, error) {
|
|
p, err := allocateMemory(uintptr(size), windows_PAGE_READWRITE)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var mem []byte
|
|
sh := (*reflect.SliceHeader)(unsafe.Pointer(&mem))
|
|
sh.Data = p
|
|
sh.Len = size
|
|
sh.Cap = size
|
|
return mem, nil
|
|
}
|
|
|
|
var old = uint32(windows_PAGE_READWRITE)
|
|
|
|
func MprotectRX(b []byte) (err error) {
|
|
err = virtualProtect(uintptr(unsafe.Pointer(&b[0])), uintptr(len(b)), windows_PAGE_EXECUTE_READ, &old)
|
|
return
|
|
}
|
|
|
|
// 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
|
|
}
|