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}