mmap_windows.go

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