Reputation: 131
I have a go application that calling the Windows Management Infrastructure (MI).
After some amount of time, I notice that the amount of handle is quite high. I inspect the process with the handles program which reports tons of opened events handle.
The main leak comes from Operation.GetInstance()
. Even after closing the operation, session and application, the event handle are still present.
I have no idea, what I can do to indicate the root cause.
For debugging reasons, I move all core logic to a single go file that everyone can reproduce it on they own.
//go:build windows
package main
import (
"fmt"
"log"
"syscall"
"unsafe"
"golang.org/x/sys/windows"
)
var (
modkernel32 = windows.NewLazySystemDLL("kernel32.dll")
modMi = windows.NewLazySystemDLL("mi.dll")
procGetProcessHandleCount = modkernel32.NewProc("GetProcessHandleCount")
procMIApplicationInitialize = modMi.NewProc("MI_Application_InitializeV1")
)
func GetProcessHandleCount(handle windows.Handle) uint32 {
var count uint32
r1, _, err := procGetProcessHandleCount.Call(
uintptr(handle),
uintptr(unsafe.Pointer(&count)),
)
if r1 != 1 {
panic(err)
} else {
return count
}
}
// Application represents the MI application.
// https://learn.microsoft.com/de-de/windows/win32/api/mi/ns-mi-mi_application
type Application struct {
reserved1 uint64
reserved2 uintptr
ft *ApplicationFT
}
// ApplicationFT represents the function table of the MI application.
// https://learn.microsoft.com/de-de/windows/win32/api/mi/ns-mi-mi_applicationft
type ApplicationFT struct {
Close uintptr
NewSession uintptr
NewHostedProvider uintptr
NewInstance uintptr
NewDestinationOptions uintptr
NewOperationOptions uintptr
NewSubscriptionDeliveryOptions uintptr
NewSerializer uintptr
NewDeserializer uintptr
NewInstanceFromClass uintptr
NewClass uintptr
}
// Session represents a session.
//
// https://learn.microsoft.com/en-us/windows/win32/api/mi/ns-mi-mi_session
type Session struct {
reserved1 uint64
reserved2 uintptr
ft *SessionFT
}
// SessionFT represents the function table for Session.
//
// https://learn.microsoft.com/en-us/windows/win32/api/mi/ns-mi-mi_session
type SessionFT struct {
Close uintptr
GetApplication uintptr
GetInstance uintptr
ModifyInstance uintptr
CreateInstance uintptr
DeleteInstance uintptr
Invoke uintptr
EnumerateInstances uintptr
QueryInstances uintptr
AssociatorInstances uintptr
ReferenceInstances uintptr
Subscribe uintptr
GetClass uintptr
EnumerateClasses uintptr
TestConnection uintptr
}
// Operation represents an operation.
// https://learn.microsoft.com/en-us/windows/win32/api/mi/ns-mi-mi_operation
type Operation struct {
reserved1 uint64
reserved2 uintptr
ft *OperationFT
}
// OperationFT represents the function table for Operation.
// https://learn.microsoft.com/en-us/windows/win32/api/mi/ns-mi-mi_operationft
type OperationFT struct {
Close uintptr
Cancel uintptr
GetSession uintptr
GetInstance uintptr
GetIndication uintptr
GetClass uintptr
}
type Instance struct {
ft *InstanceFT
classDecl uintptr
serverName *uint16
nameSpace *uint16
_ [4]uintptr
}
type InstanceFT struct {
Clone uintptr
Destruct uintptr
Delete uintptr
IsA uintptr
GetClassName uintptr
SetNameSpace uintptr
GetNameSpace uintptr
GetElementCount uintptr
AddElement uintptr
SetElement uintptr
SetElementAt uintptr
GetElement uintptr
GetElementAt uintptr
ClearElement uintptr
ClearElementAt uintptr
GetServerName uintptr
SetServerName uintptr
GetClass uintptr
}
// Application_Initialize initializes the MI [Application].
// It is recommended to have only one Application per process.
//
// https://learn.microsoft.com/en-us/windows/win32/api/mi/nf-mi-mi_application_initializev1
func Application_Initialize() (*Application, error) {
application := &Application{}
r0, _, _ := procMIApplicationInitialize.Call(0, 0, 0, uintptr(unsafe.Pointer(application)))
if r0 != 0 {
return nil, fmt.Errorf("failed: %d", r0)
}
return application, nil
}
// Close deinitializes the management infrastructure client API that was initialized through a call to Application_Initialize.
//
// https://learn.microsoft.com/en-us/windows/win32/api/mi/nf-mi-mi_application_close
func (application *Application) Close() error {
r0, _, _ := syscall.SyscallN(application.ft.Close, uintptr(unsafe.Pointer(application)))
if r0 != 0 {
return fmt.Errorf("failed: %d", r0)
}
return nil
}
// NewSession creates a session used to share connections for a set of operations to a single destination.
//
// https://learn.microsoft.com/en-us/windows/win32/api/mi/nf-mi-mi_application_newsession
func (application *Application) NewSession() (*Session, error) {
session := &Session{}
r0, _, _ := syscall.SyscallN(
application.ft.NewSession, uintptr(unsafe.Pointer(application)), 0, 0, 0, 0, 0, uintptr(unsafe.Pointer(session)),
)
if r0 != 0 {
return nil, fmt.Errorf("failed: %d", r0)
}
return session, nil
}
// Close closes a session and releases all associated memory.
//
// https://learn.microsoft.com/en-us/windows/win32/api/mi/nf-mi-mi_session_close
func (s *Session) Close() error {
r0, _, _ := syscall.SyscallN(s.ft.Close, uintptr(unsafe.Pointer(s)), 0, 0)
if r0 != 0 {
return fmt.Errorf("failed: %d", r0)
}
return nil
}
// QueryInstances queries for a set of instances based on a query expression.
//
// https://learn.microsoft.com/en-us/windows/win32/api/mi/nf-mi-mi_session_queryinstances
func (s *Session) QueryInstances(namespaceName, queryDialect, queryExpression string) (*Operation, error) {
namespaceNameUTF16, err := windows.UTF16PtrFromString(namespaceName)
if err != nil {
return nil, err
}
queryDialectUTF16, err := windows.UTF16PtrFromString(queryDialect)
if err != nil {
return nil, err
}
queryExpressionUTF16, err := windows.UTF16PtrFromString(queryExpression)
if err != nil {
return nil, err
}
operation := &Operation{}
r0, _, _ := syscall.SyscallN(
s.ft.QueryInstances,
uintptr(unsafe.Pointer(s)),
0,
0,
uintptr(unsafe.Pointer(namespaceNameUTF16)),
uintptr(unsafe.Pointer(queryDialectUTF16)),
uintptr(unsafe.Pointer(queryExpressionUTF16)),
0,
uintptr(unsafe.Pointer(operation)),
)
if r0 != 0 {
return nil, fmt.Errorf("failed: %d", r0)
}
return operation, nil
}
// Close closes an operation handle.
//
// https://learn.microsoft.com/en-us/windows/win32/api/mi/nf-mi-mi_operation_close
func (o *Operation) Close() error {
r0, _, _ := syscall.SyscallN(o.ft.Close, uintptr(unsafe.Pointer(o)))
if r0 != 0 {
return fmt.Errorf("failed: %d", r0)
}
return nil
}
func (o *Operation) GetInstance() (*Instance, bool, error) {
var (
instance *Instance
errorDetails *Instance
moreResults uint8
instanceResult uint32
errorMessageUTF16 *uint16
)
r0, _, _ := syscall.SyscallN(
o.ft.GetInstance,
uintptr(unsafe.Pointer(o)),
uintptr(unsafe.Pointer(&instance)),
uintptr(unsafe.Pointer(&moreResults)),
uintptr(unsafe.Pointer(&instanceResult)),
uintptr(unsafe.Pointer(&errorMessageUTF16)),
uintptr(unsafe.Pointer(&errorDetails)),
)
if instanceResult != 0 {
return nil, false, fmt.Errorf("instance result: %d (%s)", instanceResult, windows.UTF16PtrToString(errorMessageUTF16))
}
if r0 != 0 {
return nil, false, fmt.Errorf("failed: %d", r0)
}
return instance, moreResults == 1, nil
}
func (instance *Instance) GetElement(elementName string) (uintptr, error) {
elementNameUTF16, err := windows.UTF16PtrFromString(elementName)
if err != nil {
return 0, fmt.Errorf("failed to convert element name %s to UTF-16: %w", elementName, err)
}
var value uintptr
r0, _, _ := syscall.SyscallN(
instance.ft.GetElement,
uintptr(unsafe.Pointer(instance)),
uintptr(unsafe.Pointer(elementNameUTF16)),
uintptr(unsafe.Pointer(&value)),
0,
0,
0,
)
if r0 != 0 {
return 0, fmt.Errorf("failed: %d", r0)
}
return value, nil
}
func main() {
log.Printf("1 Process handle count: %d", GetProcessHandleCount(windows.CurrentProcess()))
app, err := Application_Initialize()
if err != nil {
panic(err)
}
log.Printf("2 Process handle count: %d", GetProcessHandleCount(windows.CurrentProcess()))
session, err := app.NewSession()
if err != nil {
panic(err)
}
log.Printf("3 Process handle count: %d", GetProcessHandleCount(windows.CurrentProcess()))
for range 1000 {
operation, err := session.QueryInstances("root/cimv2", "WQL",
"SELECT Architecture, DeviceId, Description, Family, L2CacheSize, L3CacheSize, Name, ThreadCount, NumberOfCores, NumberOfEnabledCore, NumberOfLogicalProcessors FROM Win32_Processor",
)
if err != nil {
panic(err)
}
for {
instance, moreResults, err := operation.GetInstance()
if err != nil {
panic(err)
}
description, err := instance.GetElement("Description")
if err != nil {
panic(err)
}
name, err := instance.GetElement("Name")
if err != nil {
panic(err)
}
log.Printf("Description: %s, Name: %s",
windows.UTF16PtrToString((*uint16)(unsafe.Pointer(description))),
windows.UTF16PtrToString((*uint16)(unsafe.Pointer(name))),
)
if !moreResults {
break
}
}
if err = operation.Close(); err != nil {
panic(err)
}
log.Printf("4 Process handle count: %d", GetProcessHandleCount(windows.CurrentProcess()))
}
log.Printf("5 Process handle count: %d", GetProcessHandleCount(windows.CurrentProcess()))
if err = session.Close(); err != nil {
panic(err)
}
if err = app.Close(); err != nil {
panic(err)
}
log.Printf("6 Process handle count: %d", GetProcessHandleCount(windows.CurrentProcess()))
}
results into:
2024/10/20 18:49:08 1 Process handle count: 88
2024/10/20 18:49:08 2 Process handle count: 130
2024/10/20 18:49:08 3 Process handle count: 152
2024/10/20 18:49:08 Description: AMD64 Family 25 Model 33 Stepping 2, Name: AMD Ryzen 9 5900X 12-Core Processor
2024/10/20 18:49:08 4 Process handle count: 205
2024/10/20 18:49:08 Description: AMD64 Family 25 Model 33 Stepping 2, Name: AMD Ryzen 9 5900X 12-Core Processor
2024/10/20 18:49:08 4 Process handle count: 207
2024/10/20 18:49:08 Description: AMD64 Family 25 Model 33 Stepping 2, Name: AMD Ryzen 9 5900X 12-Core Processor
2024/10/20 18:49:08 4 Process handle count: 209
2024/10/20 18:49:08 Description: AMD64 Family 25 Model 33 Stepping 2, Name: AMD Ryzen 9 5900X 12-Core Processor
2024/10/20 18:49:08 4 Process handle count: 211
2024/10/20 18:49:08 Description: AMD64 Family 25 Model 33 Stepping 2, Name: AMD Ryzen 9 5900X 12-Core Processor
2024/10/20 18:49:08 4 Process handle count: 212
2024/10/20 18:49:08 Description: AMD64 Family 25 Model 33 Stepping 2, Name: AMD Ryzen 9 5900X 12-Core Processor
2024/10/20 18:49:08 4 Process handle count: 213
[...]
2024/10/20 18:49:08 Description: AMD64 Family 25 Model 33 Stepping 2, Name: AMD Ryzen 9 5900X 12-Core Processor
2024/10/20 18:49:10 4 Process handle count: 263
2024/10/20 18:49:10 Description: AMD64 Family 25 Model 33 Stepping 2, Name: AMD Ryzen 9 5900X 12-Core Processor
2024/10/20 18:49:10 4 Process handle count: 263
2024/10/20 18:49:10 Description: AMD64 Family 25 Model 33 Stepping 2, Name: AMD Ryzen 9 5900X 12-Core Processor
2024/10/20 18:49:10 4 Process handle count: 263
2024/10/20 18:49:10 5 Process handle count: 263
2024/10/20 18:49:10 6 Process handle count: 237
Process finished with the exit code 0
On a Windows system:
//go:build windows
in a empty directory and name it main.gomodule main
go get
from command linego run main.go
. It runs the code and logs the amount of handles. Even after closing the application, event handles are still open.Upvotes: 1
Views: 89