When we write 156KB worth of key:value pairs to the WMI using IWbemQualifierSet::Put
this method creates 2GB of dangling allocations. I've never encountered a bug like this in a Microsoft library so I'm not really sure what to do about it.
Is there some way we can mitigate it by manipulating the call stack? Or by pre-allocating the memory for the class in some kind of quarantine container to make sure it gets completely freed after the class object is destroyed?
You can skip to "Step 3" in the code to see the leak, it occurs irrespective of data type or format, including using only C types and manually managing the memory.
Update: Even if we create and destroy the class object in the loop, the leak still occurs.
Update: The size of the leak is the same irrespective of input data size. Even if we write strings that are 4000 bytes long, or 1 byte long, the amount of memory leaked is the same.
main.cpp:
// MemorLeakMinimalExample.cpp : This file contains the 'main' function. Program execution begins and ends there.
#include <string>
#include <Windows.h>
#include "wmi_connection.h"
// Memory leak in this method
// Consumes 2GB of RAM to write 32MB of data
int main()
{
// ------------------------------------------------------
// Step 1. Connect to WMI -------------------------------
wmi_connection wmi;
wmi.connect_to_wmi();
// ------------------------------------------------------
// Step 2. Create a new WMI class -----------------------
IWbemClassObject* pNewClass = NULL;
VARIANT vVariant;
VariantInit(&vVariant);
// Our new class
HRESULT hres = wmi.pSvc->GetObject(
0,
0,
NULL,
&pNewClass,
NULL
);
// Set class name
vVariant.vt = VT_BSTR;
vVariant.bstrVal = SysAllocString(L"TestClass");
hres = pNewClass->Put(
_bstr_t("__CLASS"),
0,
&vVariant,
0
);
// Create the "Test" key property for the new class
BSTR KeyProp = SysAllocString(L"TestProperty");
hres = pNewClass->Put(
KeyProp,
0,
NULL,
CIM_STRING);
//Attach the Key standard qualifier to the test property
IWbemQualifierSet* pQualSet = NULL;
vVariant.vt = VT_BOOL;
vVariant.boolVal = TRUE;
hres = pNewClass->GetPropertyQualifierSet(KeyProp, &pQualSet);
hres = pQualSet->Put(L"Key", &vVariant, 0);
// --------------------------------------------------------------------
// Step 3. Write 20,000 key:value pairs -------------------------------
// Key
wchar_t key[32] = { 0 };
wchar_t* pKey = key;
// Value
VARIANT value;
VariantInit(&value);
value.vt = VT_BSTR;
// Value is always 1234
value.bstrVal = SysAllocString(L"1234");
printf("Breakpoint\n"); // Inspect memory usage here
for (unsigned long long i = 0; i <= 20000; i )
{
// Key name is formatted "key0", "key1", "key2", ...
swprintf_s(pKey, 32, L"key%llu", i);
// Write a key:value pair
pQualSet->Put(pKey, &value, 0); // <-- memory leak
}
printf("Breakpoint\n"); // Inspect memory usage here
// --------------------------------------------------------------------
// // Step 4. Save class for viewing in sys utility Wbemtest.exe ------
// Uncomment if you want the class written to the wmi database
// (its exactly the size it should be for our key:value data in)
/*
hres = wmi.pSvc->PutClass(pNewClass, WBEM_FLAG_CREATE_OR_UPDATE, NULL, NULL);
if (FAILED(hres))
{
printf("Failed to save class changes. Error code = 0x%X \n", hres);
}
*/
return 0;
}
wmi_connection.h:
#pragma once
#include <Windows.h>
#include <comdef.h>
#include <wbemcli.h>
class wmi_connection
{
public:
// uninteresting parts
int connect_to_wmi()
{
// Initialize
hres = CoInitializeEx(0, COINIT_MULTITHREADED);
if (FAILED(hres))
{
printf("Failed to initialize COM library. Error code = 0x%llx", (unsigned long long)hres);
return FALSE; // Program has failed.
}
// Set general COM security levels
hres = CoInitializeSecurity(
NULL,
-1, // COM authentication
NULL, // Authentication services
NULL, // Reserved
RPC_C_AUTHN_LEVEL_DEFAULT, // Default authentication
RPC_C_IMP_LEVEL_IMPERSONATE, // Default Impersonation
NULL, // Authentication info
EOAC_NONE, // Additional capabilities
NULL // Reserved
);
// Get the class factory for the WbemLocator object
IClassFactory* pClassFactory = NULL;
hres = CoGetClassObject(CSLSID_WbemLocator, CLSCTX_INPROC_SERVER, NULL, SIID_IClassFactory, (void**)&pClassFactory);
// Create an instance of the WbemLocator object
IUnknown* pUnk = NULL;
hres = pClassFactory->CreateInstance(NULL, SIID_IUnknown, (void**)&pUnk);
hres = pUnk->QueryInterface(SIID_IWbemLocator, (void**)&pLoc);
pUnk->Release();
pClassFactory->Release();
// Set security levels on the proxy
hres = CoSetProxyBlanket(
pSvc, // Indicates the proxy to set
RPC_C_AUTHN_WINNT, // RPC_C_AUTHN_xxx
RPC_C_AUTHZ_NONE, // RPC_C_AUTHZ_xxx
NULL, // Server principal name
RPC_C_AUTHN_LEVEL_CALL, // RPC_C_AUTHN_LEVEL_xxx
RPC_C_IMP_LEVEL_IMPERSONATE, // RPC_C_IMP_LEVEL_xxx
NULL, // client identity
EOAC_NONE // proxy capabilities
);
// Connect to WMI through the IWbemLocator::ConnectServer method
hres = pLoc->ConnectServer(
_bstr_t(L"root\\cimv2"), // Object path of WMI namespace
NULL, // User name
NULL, // User password
0, // Locale
NULL, // Security flags
0, // Authority
0, // Context object
&pSvc // pointer to IWbemServices proxy
);
if (FAILED(hres))
{
printf("Could not connect. Error code = 0x%lx\n", hres);
return 1;
}
printf("Connected to root\cimv2 WMI namespace\n");
return 0;
}
IWbemServices* pSvc;
private:
HRESULT hres = NULL;
IWbemLocator* pLoc = NULL;
GUID CSLSID_WbemLocator = { 0x4590f811, 0x1d3a, 0x11d0, 0x89, 0x1f, 0x00, 0xaa, 0x00, 0x4b, 0x2e, 0x24 };
GUID SIID_IClassFactory = { 0x00000001, 0x0000, 0x0000, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46 };
GUID SIID_IUnknown = { 0x00000000, 0x0000, 0x0000, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46 };
GUID SIID_IWbemLocator = { 0xdc12a687, 0x737f, 0x11cf, 0x88, 0x4d, 0x00, 0xaa, 0x00, 0x4b, 0x2e, 0x24 };
};
CodePudding user response:
I turned i
into 40000000. I found that the memory footprint has indeed been increasing from Task Manager.
And show this continuously.
I added Sleep(50);
in the “for” loop, “Exception thrown….” disappeared. But the memory footprint still was increasing. The process memory increased to 565MB in 6mins.
There is a conflict between the “for” loop with Put(pKey, &value, 0);
So, using multithreading is necessary.
I modified the code, it runs well. No effect on the loop and process memory is normal.
Here is the code, it still has many flaws, and you are welcome to correct them.
#include <string>
#include <Windows.h>
#include "wmi_connection.h"
wchar_t key[32] = { 0 };
wchar_t* pKey = key;
BSTR KeyProp = SysAllocString(L"TestProperty");
IWbemQualifierSet* pQualSet = NULL;
VARIANT value;
DWORD WINAPI ThreadProc1(LPVOID lpParameter)
{
wmi_connection wmi;
wmi.connect_to_wmi();
IWbemClassObject* pNewClass = NULL;
VARIANT vVariant;
VariantInit(&vVariant);
HRESULT hres = wmi.pSvc->GetObject(
0,
0,
NULL,
&pNewClass,
NULL
);
vVariant.vt = VT_BSTR;
vVariant.bstrVal = SysAllocString(L"TestClass");
hres = pNewClass->Put(
_bstr_t("__CLASS"),
0,
&vVariant,
0
);
hres = pNewClass->Put(
KeyProp,
0,
NULL,
CIM_STRING);
vVariant.vt = VT_BOOL;
vVariant.boolVal = TRUE;
hres = pNewClass->GetPropertyQualifierSet(KeyProp, &pQualSet);
hres = pQualSet->Put(L"Key", &vVariant, 0);
VARIANT value;
VariantInit(&value);
value.vt = VT_BSTR;
value.bstrVal = SysAllocString(L"1234");
printf("Breakpoint\n");
pQualSet->Put(pKey, &value, 0);
return 2;
}
DWORD WINAPI ThreadProc2(LPVOID lpParameter)
{
for (unsigned long long i = 0; i <= 400000000; i )
{
swprintf_s(pKey, 32, L"key%llu", i);
}
printf("Breakpoint\n");
return 0;
}
int main()
{
wmi_connection wmi;
wmi.connect_to_wmi();
IWbemClassObject* pNewClass = NULL;
VARIANT vVariant;
VariantInit(&vVariant);
// Our new class
HRESULT hres = wmi.pSvc->GetObject(
0,
0,
NULL,
&pNewClass,
NULL
);
vVariant.vt = VT_BSTR;
vVariant.bstrVal = SysAllocString(L"TestClass");
hres = pNewClass->Put(
_bstr_t("__CLASS"),
0,
&vVariant,
0
);
BSTR KeyProp = SysAllocString(L"TestProperty");
hres = pNewClass->Put(
KeyProp,
0,
NULL,
CIM_STRING);
IWbemQualifierSet* pQualSet = NULL;
vVariant.vt = VT_BOOL;
vVariant.boolVal = TRUE;
hres = pNewClass->GetPropertyQualifierSet(KeyProp, &pQualSet);
hres = pQualSet->Put(L"Key", &vVariant, 0);
HANDLE hThread[2];
DWORD dWResult1;
DWORD dWResult2;
hThread[0] = CreateThread(NULL, 0, ThreadProc1, NULL, 0, NULL);
hThread[1] = CreateThread(NULL, 0, ThreadProc2, NULL, 0, NULL);
WaitForMultipleObjects(2, hThread, true, INFINITE);
GetExitCodeThread(hThread[0], &dWResult1);
GetExitCodeThread(hThread[1], &dWResult2);
printf("Thread execution completed \n");
getchar();
CloseHandle(hThread[0]);
CloseHandle(hThread[1]);
SysFreeString((BSTR) KeyProp);//add
printf("Breakpoint\n");
return 0;
}