windows: add IsWow64Process2 for detecting x86 on arm

The original IsWow64Process returns false on arm, always, and so
IsWow64Process2 was added to account for this scenario. This isn't
available on older versions of Windows, so we mark it as such using the
new '?' notation. Finally, we add a test to make sure this all works and
does the expected thing on different versions of Windows.

Change-Id: Ic0412578cfb3f4cf6c9dc92a0028abc579bf6c85
Reviewed-on: https://go-review.googlesource.com/c/sys/+/269077
Run-TryBot: Jason A. Donenfeld <Jason@zx2c4.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
Trust: Jason A. Donenfeld <Jason@zx2c4.com>
diff --git a/windows/syscall_windows.go b/windows/syscall_windows.go
index 008ffc1..3b6c5ae 100644
--- a/windows/syscall_windows.go
+++ b/windows/syscall_windows.go
@@ -174,6 +174,7 @@
 //sys	FormatMessage(flags uint32, msgsrc uintptr, msgid uint32, langid uint32, buf []uint16, args *byte) (n uint32, err error) = FormatMessageW
 //sys	ExitProcess(exitcode uint32)
 //sys	IsWow64Process(handle Handle, isWow64 *bool) (err error) = IsWow64Process
+//sys	IsWow64Process2(handle Handle, processMachine *uint16, nativeMachine *uint16) (err error) = IsWow64Process2?
 //sys	CreateFile(name *uint16, access uint32, mode uint32, sa *SecurityAttributes, createmode uint32, attrs uint32, templatefile Handle) (handle Handle, err error) [failretval==InvalidHandle] = CreateFileW
 //sys	ReadFile(handle Handle, buf []byte, done *uint32, overlapped *Overlapped) (err error)
 //sys	WriteFile(handle Handle, buf []byte, done *uint32, overlapped *Overlapped) (err error)
diff --git a/windows/syscall_windows_test.go b/windows/syscall_windows_test.go
index 2f1513f..cb03e1d 100644
--- a/windows/syscall_windows_test.go
+++ b/windows/syscall_windows_test.go
@@ -5,6 +5,8 @@
 package windows_test
 
 import (
+	"debug/pe"
+	"errors"
 	"fmt"
 	"io/ioutil"
 	"os"
@@ -458,3 +460,29 @@
 		t.Errorf("ProcessMemoryLimit is wrong: want %v have %v", wantMemLimit, have)
 	}
 }
+
+func TestIsWow64Process2(t *testing.T) {
+	var processMachine, nativeMachine uint16
+	err := windows.IsWow64Process2(windows.CurrentProcess(), &processMachine, &nativeMachine)
+	if errors.Is(err, windows.ERROR_PROC_NOT_FOUND) {
+		maj, min, build := windows.RtlGetNtVersionNumbers()
+		if maj < 10 || (maj == 10 && min == 0 && build < 17763) {
+			t.Skip("not available on older versions of Windows")
+			return
+		}
+	}
+	if err != nil {
+		t.Fatalf("IsWow64Process2 failed: %v", err)
+	}
+	if processMachine == pe.IMAGE_FILE_MACHINE_UNKNOWN {
+		processMachine = nativeMachine
+	}
+	switch {
+	case processMachine == pe.IMAGE_FILE_MACHINE_AMD64 && runtime.GOARCH == "amd64":
+	case processMachine == pe.IMAGE_FILE_MACHINE_I386 && runtime.GOARCH == "386":
+	case processMachine == pe.IMAGE_FILE_MACHINE_ARMNT && runtime.GOARCH == "arm":
+	case processMachine == pe.IMAGE_FILE_MACHINE_ARM64 && runtime.GOARCH == "arm64":
+	default:
+		t.Errorf("IsWow64Process2 is wrong: want %v have %v", runtime.GOARCH, processMachine)
+	}
+}
diff --git a/windows/zsyscall_windows.go b/windows/zsyscall_windows.go
index d400c35..cc5d74f 100644
--- a/windows/zsyscall_windows.go
+++ b/windows/zsyscall_windows.go
@@ -248,6 +248,7 @@
 	procGetVolumePathNamesForVolumeNameW                     = modkernel32.NewProc("GetVolumePathNamesForVolumeNameW")
 	procGetWindowsDirectoryW                                 = modkernel32.NewProc("GetWindowsDirectoryW")
 	procIsWow64Process                                       = modkernel32.NewProc("IsWow64Process")
+	procIsWow64Process2                                      = modkernel32.NewProc("IsWow64Process2")
 	procLoadLibraryExW                                       = modkernel32.NewProc("LoadLibraryExW")
 	procLoadLibraryW                                         = modkernel32.NewProc("LoadLibraryW")
 	procLocalFree                                            = modkernel32.NewProc("LocalFree")
@@ -2055,6 +2056,18 @@
 	return
 }
 
+func IsWow64Process2(handle Handle, processMachine *uint16, nativeMachine *uint16) (err error) {
+	err = procIsWow64Process2.Find()
+	if err != nil {
+		return
+	}
+	r1, _, e1 := syscall.Syscall(procIsWow64Process2.Addr(), 3, uintptr(handle), uintptr(unsafe.Pointer(processMachine)), uintptr(unsafe.Pointer(nativeMachine)))
+	if r1 == 0 {
+		err = errnoErr(e1)
+	}
+	return
+}
+
 func LoadLibraryEx(libname string, zero Handle, flags uintptr) (handle Handle, err error) {
 	var _p0 *uint16
 	_p0, err = syscall.UTF16PtrFromString(libname)