consider the following code for a dynamic loaded library built with g++-4.7 on linux, -fPIC
and linked with -rdynamic
option:
typedef std::vector< void* > cbRegister_t;
struct Wrapper
{
cbRegister_t instance;
Wrapper() : instance() { HDebugLog("Wrapper CTOR!");}
~Wrapper() { HDebugLog("Wrapper DESTRUCTOR!"); }
};
inline cbRegister_t& getLibraryUnregisterMap()
{
static Wrapper unregisterLibraryMap;
HDebugLog("getLibraryUnregisterMap: we have " <<unregisterLibraryMap.instance.size() << " elements. the address of the map is " << &unregisterLibraryMap.instance);
return unregisterLibraryMap.instance;
}
void registerLibrary(void* p)
{
auto& map = getLibraryUnregisterMap();
map.push_back(p);
}
void unregisterLibrary()
{
auto& map = getLibraryUnregisterMap();
}
void __attribute__ ((constructor)) library_init()
{
static SomeData cbContainer;
HDebugLog("Library constructor: address of static cbContainer is: " << &cbContainer );
registerLibrary( &cbContainer);
}
void __attribute__ ((destructor)) library_fini()
{ unregisterLibrary(); }
This code loads fine from a client with dlopen
and the RTLD_NOW
flag. The library constructor is called. The problem happens when i call dlclose
on the handle. It gives status zero, meaning that it was successful. But the library destructor library_fini
is not called. dlopen
is called on a single place so the reference count should not be a problem, but in order to be absolutely sure that there are really no references dangling
i tried doing the dlclose
several times:
int result = dlclose(handle);
HDebugLog("Library::dynamicLibraryClose: closing library: " << libraryPath);
HDebugLog("Library::dynamicLibraryClose: dlclose 1 failed with error: " << result << " => " );
result = dlclose(handle);
HDebugLog("Library::dynamicLibraryClose: dlclose 2 failed with error: " << result << " => " );
result = dlclose(handle);
HDebugLog("Library::dynamicLibraryClose: dlclose 3 failed with error: " << result << " => " );
result = dlclose(handle);
HDebugLog("Library::dynamicLibraryClose: dlclose 4 failed with error: " << result << " => " );
result = dlclose(handle);
HDebugLog("Library::dynamicLibraryClose: dlclose 5 failed with error: " << result << " => " );
result = dlclose(handle);
HDebugLog("Library::dynamicLibraryClose: dlclose 6 failed with error: " << result << " => ");
result = dlclose(handle);
HDebugLog("Library::dynamicLibraryClose: dlclose 7 failed with error: " << result << " => ");
result = dlclose(handle);
HDebugLog("Library::dynamicLibraryClose: dlclose 8 failed with error: " << result << " => " );
HAssertMsg( !libraryPath.empty(), "library path is not set");
HAssertMsg( 0 == dlopen(libraryPath.c_str(), RTLD_NOLOAD) , "library is still loaded");
All those debug logs show that each time i call dlclose
the status result is zero. Success!
The last assertion, which doesn't fail, confirms that the library is not resident anymore . But at this point library_fini
has not been called yet!
This behavior is definitely a bug, either on gcc or ld.so
(or whatever is using linux/ubuntu to dynamically load libraries these days). This behavior clearly is not what the standard says, because library destructors should be guaranteed to be run if the reference count is zero before dlclose returns. library destructors happen to be run after the Wrapper.instance
is destroyed, which renders the library destructor completely useless, as is not able to do any finalization of the data
This works with glibc 2.17, gcc 4.8.0 or icc 13.1 or icc 12.1:
icc -std=c99 -nostdlib -shared test.c -o /tmp/test
against:
Also tested with glibc 2.10, glibc 2.12. And all
RTLD_*
flags.Edit:
Using an actual Ubuntu system (gcc (Ubuntu/Linaro 4.7.2-2ubuntu1) 4.7.2), GNU C Library (Ubuntu EGLIBC 2.15-0ubuntu20), I must say that above code works there too. So maybe after all it's not about the compiler and/or glibc.