windows: add process module related syscalls

Add EnumProcessModules, EnumProcessModulesEx, GetModuleInformation, GetModuleFileNameEx and GetModuleBaseName.

https://docs.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-enumprocessmodules
https://docs.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-enumprocessmodulesex
https://docs.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-getmoduleinformation
https://docs.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-getmodulefilenameexw
https://docs.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-getmodulebasenamew

Change-Id: I75b062daec7a2cc9685e803dfa851825acee32b6
GitHub-Last-Rev: 7c208d6d3eedfbcd536470c6dbde4f4aecdae61e
GitHub-Pull-Request: golang/sys#116
Reviewed-on: https://go-review.googlesource.com/c/sys/+/350870
Trust: Alex Brainman <alex.brainman@gmail.com>
Trust: Minux Ma <minux@golang.org>
Run-TryBot: Alex Brainman <alex.brainman@gmail.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Alex Brainman <alex.brainman@gmail.com>
diff --git a/windows/syscall_windows.go b/windows/syscall_windows.go
index 1215b2a..ff2d45d 100644
--- a/windows/syscall_windows.go
+++ b/windows/syscall_windows.go
@@ -398,6 +398,11 @@
 
 // Process Status API (PSAPI)
 //sys	EnumProcesses(processIds []uint32, bytesReturned *uint32) (err error) = psapi.EnumProcesses
+//sys	EnumProcessModules(process Handle, module *Handle, cb uint32, cbNeeded *uint32) (err error) = psapi.EnumProcessModules
+//sys	EnumProcessModulesEx(process Handle, module *Handle, cb uint32, cbNeeded *uint32, filterFlag uint32) (err error) = psapi.EnumProcessModulesEx
+//sys	GetModuleInformation(process Handle, module Handle, modinfo *ModuleInfo, cb uint32) (err error) = psapi.GetModuleInformation
+//sys	GetModuleFileNameEx(process Handle, module Handle, filename *uint16, size uint32) (err error) = psapi.GetModuleFileNameExW
+//sys	GetModuleBaseName(process Handle, module Handle, baseName *uint16, size uint32) (err error) = psapi.GetModuleBaseNameW
 
 // NT Native APIs
 //sys	rtlNtStatusToDosErrorNoTeb(ntstatus NTStatus) (ret syscall.Errno) = ntdll.RtlNtStatusToDosErrorNoTeb
diff --git a/windows/syscall_windows_test.go b/windows/syscall_windows_test.go
index fd75c09..bc11750 100644
--- a/windows/syscall_windows_test.go
+++ b/windows/syscall_windows_test.go
@@ -701,3 +701,79 @@
 	}
 
 }
+
+func TestProcessModules(t *testing.T) {
+	process, err := windows.GetCurrentProcess()
+	if err != nil {
+		t.Fatalf("unable to get current process: %v", err)
+	}
+	// NB: Assume that we're always the first module. This technically isn't documented anywhere (that I could find), but seems to always hold.
+	var module windows.Handle
+	var cbNeeded uint32
+	err = windows.EnumProcessModules(process, &module, uint32(unsafe.Sizeof(module)), &cbNeeded)
+	if err != nil {
+		t.Fatalf("EnumProcessModules failed: %v", err)
+	}
+
+	var moduleEx windows.Handle
+	err = windows.EnumProcessModulesEx(process, &moduleEx, uint32(unsafe.Sizeof(moduleEx)), &cbNeeded, windows.LIST_MODULES_DEFAULT)
+	if err != nil {
+		t.Fatalf("EnumProcessModulesEx failed: %v", err)
+	}
+	if module != moduleEx {
+		t.Fatalf("module from EnumProcessModules does not match EnumProcessModulesEx: %v != %v", module, moduleEx)
+	}
+
+	exePath, err := os.Executable()
+	if err != nil {
+		t.Fatalf("unable to get current executable path: %v", err)
+	}
+
+	modulePathUTF16 := make([]uint16, len(exePath)+1)
+	err = windows.GetModuleFileNameEx(process, module, &modulePathUTF16[0], uint32(len(modulePathUTF16)))
+	if err != nil {
+		t.Fatalf("GetModuleFileNameEx failed: %v", err)
+	}
+
+	modulePath := windows.UTF16ToString(modulePathUTF16)
+	if modulePath != exePath {
+		t.Fatalf("module does not match executable for GetModuleFileNameEx: %s != %s", modulePath, exePath)
+	}
+
+	err = windows.GetModuleBaseName(process, module, &modulePathUTF16[0], uint32(len(modulePathUTF16)))
+	if err != nil {
+		t.Fatalf("GetModuleBaseName failed: %v", err)
+	}
+
+	modulePath = windows.UTF16ToString(modulePathUTF16)
+	baseExePath := filepath.Base(exePath)
+	if modulePath != baseExePath {
+		t.Fatalf("module does not match executable for GetModuleBaseName: %s != %s", modulePath, baseExePath)
+	}
+
+	var moduleInfo windows.ModuleInfo
+	err = windows.GetModuleInformation(process, module, &moduleInfo, uint32(unsafe.Sizeof(moduleInfo)))
+	if err != nil {
+		t.Fatalf("GetModuleInformation failed: %v", err)
+	}
+
+	peFile, err := pe.Open(exePath)
+	if err != nil {
+		t.Fatalf("unable to open current executable: %v", err)
+	}
+	defer peFile.Close()
+
+	var peSizeOfImage uint32
+	switch runtime.GOARCH {
+	case "amd64", "arm64":
+		peSizeOfImage = peFile.OptionalHeader.(*pe.OptionalHeader64).SizeOfImage
+	case "386", "arm":
+		peSizeOfImage = peFile.OptionalHeader.(*pe.OptionalHeader32).SizeOfImage
+	default:
+		t.Fatalf("unable to test GetModuleInformation on arch %v", runtime.GOARCH)
+	}
+
+	if moduleInfo.SizeOfImage != peSizeOfImage {
+		t.Fatalf("module size does not match executable: %v != %v", moduleInfo.SizeOfImage, peSizeOfImage)
+	}
+}
diff --git a/windows/types_windows.go b/windows/types_windows.go
index 17f0331..88e0ce5 100644
--- a/windows/types_windows.go
+++ b/windows/types_windows.go
@@ -243,6 +243,14 @@
 )
 
 const (
+	// flags for EnumProcessModulesEx
+	LIST_MODULES_32BIT   = 0x01
+	LIST_MODULES_64BIT   = 0x02
+	LIST_MODULES_ALL     = 0x03
+	LIST_MODULES_DEFAULT = 0x00
+)
+
+const (
 	// filters for ReadDirectoryChangesW and FindFirstChangeNotificationW
 	FILE_NOTIFY_CHANGE_FILE_NAME   = 0x001
 	FILE_NOTIFY_CHANGE_DIR_NAME    = 0x002
@@ -2773,3 +2781,9 @@
 
 // Flag for QueryFullProcessImageName.
 const PROCESS_NAME_NATIVE = 1
+
+type ModuleInfo struct {
+	BaseOfDll   uintptr
+	SizeOfImage uint32
+	EntryPoint  uintptr
+}
diff --git a/windows/zsyscall_windows.go b/windows/zsyscall_windows.go
index 2083ec3..e282e24 100644
--- a/windows/zsyscall_windows.go
+++ b/windows/zsyscall_windows.go
@@ -377,7 +377,12 @@
 	procCoTaskMemFree                                        = modole32.NewProc("CoTaskMemFree")
 	procCoUninitialize                                       = modole32.NewProc("CoUninitialize")
 	procStringFromGUID2                                      = modole32.NewProc("StringFromGUID2")
+	procEnumProcessModules                                   = modpsapi.NewProc("EnumProcessModules")
+	procEnumProcessModulesEx                                 = modpsapi.NewProc("EnumProcessModulesEx")
 	procEnumProcesses                                        = modpsapi.NewProc("EnumProcesses")
+	procGetModuleBaseNameW                                   = modpsapi.NewProc("GetModuleBaseNameW")
+	procGetModuleFileNameExW                                 = modpsapi.NewProc("GetModuleFileNameExW")
+	procGetModuleInformation                                 = modpsapi.NewProc("GetModuleInformation")
 	procSubscribeServiceChangeNotifications                  = modsechost.NewProc("SubscribeServiceChangeNotifications")
 	procUnsubscribeServiceChangeNotifications                = modsechost.NewProc("UnsubscribeServiceChangeNotifications")
 	procGetUserNameExW                                       = modsecur32.NewProc("GetUserNameExW")
@@ -3225,6 +3230,22 @@
 	return
 }
 
+func EnumProcessModules(process Handle, module *Handle, cb uint32, cbNeeded *uint32) (err error) {
+	r1, _, e1 := syscall.Syscall6(procEnumProcessModules.Addr(), 4, uintptr(process), uintptr(unsafe.Pointer(module)), uintptr(cb), uintptr(unsafe.Pointer(cbNeeded)), 0, 0)
+	if r1 == 0 {
+		err = errnoErr(e1)
+	}
+	return
+}
+
+func EnumProcessModulesEx(process Handle, module *Handle, cb uint32, cbNeeded *uint32, filterFlag uint32) (err error) {
+	r1, _, e1 := syscall.Syscall6(procEnumProcessModulesEx.Addr(), 5, uintptr(process), uintptr(unsafe.Pointer(module)), uintptr(cb), uintptr(unsafe.Pointer(cbNeeded)), uintptr(filterFlag), 0)
+	if r1 == 0 {
+		err = errnoErr(e1)
+	}
+	return
+}
+
 func EnumProcesses(processIds []uint32, bytesReturned *uint32) (err error) {
 	var _p0 *uint32
 	if len(processIds) > 0 {
@@ -3237,6 +3258,30 @@
 	return
 }
 
+func GetModuleBaseName(process Handle, module Handle, baseName *uint16, size uint32) (err error) {
+	r1, _, e1 := syscall.Syscall6(procGetModuleBaseNameW.Addr(), 4, uintptr(process), uintptr(module), uintptr(unsafe.Pointer(baseName)), uintptr(size), 0, 0)
+	if r1 == 0 {
+		err = errnoErr(e1)
+	}
+	return
+}
+
+func GetModuleFileNameEx(process Handle, module Handle, filename *uint16, size uint32) (err error) {
+	r1, _, e1 := syscall.Syscall6(procGetModuleFileNameExW.Addr(), 4, uintptr(process), uintptr(module), uintptr(unsafe.Pointer(filename)), uintptr(size), 0, 0)
+	if r1 == 0 {
+		err = errnoErr(e1)
+	}
+	return
+}
+
+func GetModuleInformation(process Handle, module Handle, modinfo *ModuleInfo, cb uint32) (err error) {
+	r1, _, e1 := syscall.Syscall6(procGetModuleInformation.Addr(), 4, uintptr(process), uintptr(module), uintptr(unsafe.Pointer(modinfo)), uintptr(cb), 0, 0)
+	if r1 == 0 {
+		err = errnoErr(e1)
+	}
+	return
+}
+
 func SubscribeServiceChangeNotifications(service Handle, eventType uint32, callback uintptr, callbackCtx uintptr, subscription *uintptr) (ret error) {
 	ret = procSubscribeServiceChangeNotifications.Find()
 	if ret != nil {