github.com/tetratelabs/wazero@v1.7.3-0.20240513003603-48f702e154b5/internal/platform/mmap_windows.go (about)

     1  package platform
     2  
     3  import (
     4  	"fmt"
     5  	"syscall"
     6  	"unsafe"
     7  )
     8  
     9  var (
    10  	kernel32           = syscall.NewLazyDLL("kernel32.dll")
    11  	procVirtualAlloc   = kernel32.NewProc("VirtualAlloc")
    12  	procVirtualProtect = kernel32.NewProc("VirtualProtect")
    13  	procVirtualFree    = kernel32.NewProc("VirtualFree")
    14  )
    15  
    16  const (
    17  	windows_MEM_COMMIT             uintptr = 0x00001000
    18  	windows_MEM_RELEASE            uintptr = 0x00008000
    19  	windows_PAGE_READWRITE         uintptr = 0x00000004
    20  	windows_PAGE_EXECUTE_READ      uintptr = 0x00000020
    21  	windows_PAGE_EXECUTE_READWRITE uintptr = 0x00000040
    22  )
    23  
    24  const MmapSupported = true
    25  
    26  func munmapCodeSegment(code []byte) error {
    27  	return freeMemory(code)
    28  }
    29  
    30  // allocateMemory commits the memory region via the "VirtualAlloc" function.
    31  // See https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc
    32  func allocateMemory(size uintptr, protect uintptr) (uintptr, error) {
    33  	address := uintptr(0) // system determines where to allocate the region.
    34  	alloctype := windows_MEM_COMMIT
    35  	if r, _, err := procVirtualAlloc.Call(address, size, alloctype, protect); r == 0 {
    36  		return 0, fmt.Errorf("compiler: VirtualAlloc error: %w", ensureErr(err))
    37  	} else {
    38  		return r, nil
    39  	}
    40  }
    41  
    42  // freeMemory releases the memory region via the "VirtualFree" function.
    43  // See https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualfree
    44  func freeMemory(code []byte) error {
    45  	address := unsafe.Pointer(&code[0])
    46  	size := uintptr(0) // size must be 0 because we're using MEM_RELEASE.
    47  	freetype := windows_MEM_RELEASE
    48  	if r, _, err := procVirtualFree.Call(uintptr(address), size, freetype); r == 0 {
    49  		return fmt.Errorf("compiler: VirtualFree error: %w", ensureErr(err))
    50  	}
    51  	return nil
    52  }
    53  
    54  func virtualProtect(address, size, newprotect uintptr, oldprotect *uint32) error {
    55  	if r, _, err := procVirtualProtect.Call(address, size, newprotect, uintptr(unsafe.Pointer(oldprotect))); r == 0 {
    56  		return fmt.Errorf("compiler: VirtualProtect error: %w", ensureErr(err))
    57  	}
    58  	return nil
    59  }
    60  
    61  func mmapCodeSegmentAMD64(size int) ([]byte, error) {
    62  	p, err := allocateMemory(uintptr(size), windows_PAGE_EXECUTE_READWRITE)
    63  	if err != nil {
    64  		return nil, err
    65  	}
    66  
    67  	return unsafe.Slice((*byte)(unsafe.Pointer(p)), size), nil
    68  }
    69  
    70  func mmapCodeSegmentARM64(size int) ([]byte, error) {
    71  	p, err := allocateMemory(uintptr(size), windows_PAGE_READWRITE)
    72  	if err != nil {
    73  		return nil, err
    74  	}
    75  
    76  	return unsafe.Slice((*byte)(unsafe.Pointer(p)), size), nil
    77  }
    78  
    79  var old = uint32(windows_PAGE_READWRITE)
    80  
    81  func MprotectRX(b []byte) (err error) {
    82  	err = virtualProtect(uintptr(unsafe.Pointer(&b[0])), uintptr(len(b)), windows_PAGE_EXECUTE_READ, &old)
    83  	return
    84  }
    85  
    86  // ensureErr returns syscall.EINVAL when the input error is nil.
    87  //
    88  // We are supposed to use "GetLastError" which is more precise, but it is not safe to execute in goroutines. While
    89  // "GetLastError" is thread-local, goroutines are not pinned to threads.
    90  //
    91  // See https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror
    92  func ensureErr(err error) error {
    93  	if err != nil {
    94  		return err
    95  	}
    96  	return syscall.EINVAL
    97  }