Recently ran into an issue where a Windows process was repeatedly loading/unloading the same DLL. The OS loader deferred unloading the initial DLL and the next DLL load succeeded. However, the global variable was not reset between reloads. For example, when calling this OpenSSL function EVP_EncryptInit_ex()
, it directly affects how the DLL is unloaded.
Enabling the call to EVP_EncryptInit_ex()
testdll.c: Loading openssl.dll
openssl.c: DLL_PROCESS_ATTACH
testdll.c: g_MyGlobal = 42
testdll.c: Calling TestEVPEncryptInitEx
openssl.c: Updated g_MyGlobal = 43
testdll.c: g_MyGlobal = 43
testdll.c: Unloading openssl.dll
testdll.c: Reloading openssl.dll
testdll.c: g_MyGlobal = 43 <--- Notice the global variable retains the old modified value.
testdll.c: Unloading openssl.dll
openssl.c: DLL_PROCESS_DETACH
Disabling the call to EVP_EncryptInit_ex()
testdll.c: Loading openssl.dll
openssl.c: DLL_PROCESS_ATTACH
testdll.c: g_MyGlobal = 42
testdll.c: Calling TestEVPEncryptInitEx
openssl.c: Updated g_MyGlobal = 43
testdll.c: g_MyGlobal = 43
testdll.c: Unloading openssl.dll
openssl.c: DLL_PROCESS_DETACH
testdll.c: Reloading openssl.dll
openssl.c: DLL_PROCESS_ATTACH
testdll.c: g_MyGlobal = 42 <--- Notice the global variable is reset to the initial value.
testdll.c: Unloading openssl.dll
openssl.c: DLL_PROCESS_DETACH
openssl.c
#include <windows.h>
#include <openssl/evp.h>
#include <stdio.h>
#pragma comment(lib, "libeay32_3.0.13_W32.lib")
#pragma comment(lib, "crypt32.lib")
#pragma comment(lib, "WS2_32.lib")
#define __FILENAME__ (strrchr(__FILE__, '\\') ? strrchr(__FILE__, '\\') + 1 : __FILE__)
__declspec(dllexport) int g_MyGlobal = 42;
// Exported function that initializes an EVP cipher context.
__declspec(dllexport) int TestEVPEncryptInitEx(void)
{
EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
// ENABLE/DISABLE this OpenSSL function for testing purposes...
EVP_EncryptInit_ex(ctx, EVP_aes_128_gcm(), NULL, NULL, NULL);
EVP_CIPHER_CTX_free(ctx);
g_MyGlobal = 43;
printf("%s: Updated g_MyGlobal = %d\n", __FILENAME__,g_MyGlobal);
return 0;
}
BOOL APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
printf("%s: DLL_PROCESS_ATTACH\n", __FILENAME__);
break;
case DLL_PROCESS_DETACH:
printf("%s: DLL_PROCESS_DETACH\n", __FILENAME__);
break;
}
return TRUE;
}
testdll.c
#include <windows.h>
#include <stdio.h>
#define __FILENAME__ (strrchr(__FILE__, '\\') ? strrchr(__FILE__, '\\') + 1 : __FILE__)
__declspec(dllimport) extern int g_MyGlobal;
typedef int (*TestFuncType)(void);
int main(void)
{
printf("%s: Loading openssl.dll\n", __FILENAME__);
HMODULE hModule = LoadLibraryA("openssl.dll");
LPINT pGlobal = (LPINT)GetProcAddress(hModule, "g_MyGlobal");
printf("%s: g_MyGlobal = %d\n", __FILENAME__,*pGlobal);
TestFuncType pTestFunc = (TestFuncType)GetProcAddress(hModule, "TestEVPEncryptInitEx");
printf("%s: Calling TestEVPEncryptInitEx\n", __FILENAME__);
pTestFunc();
printf("%s: g_MyGlobal = %d\n", __FILENAME__, *pGlobal);
printf("%s: Unloading openssl.dll\n", __FILENAME__);
FreeLibrary(hModule);
printf("%s: Reloading openssl.dll\n", __FILENAME__);
HMODULE hModule1 = LoadLibraryA("openssl.dll");
LPINT pGlobal1 = (LPINT)GetProcAddress(hModule1, "g_MyGlobal");
printf("%s: g_MyGlobal = %d\n", __FILENAME__,*pGlobal1);
printf("%s: Unloading openssl.dll\n", __FILENAME__);
FreeLibrary(hModule1);
return 0;
}
For windows, it's possible that globals are not reset (as seen above) when the same DLL is loaded/unloaded repeatedly within the same process because the OS may defer unloading it completely. Seems kind of similar to linux: "globals are not reinitialized when a Linux shared object is loaded and unloaded repeatedly. Once the global variable is set, it will remain the same until it is manually changed."
The workaround I'm using for now is to explicitly reset the global variables rather than relying upon the DLL loader.
Question
Is this a known issue with Windows DLLs whereby global variables are potentially not reset between reloads and what are the best practices?