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 }