The global static variable destructor in the shared library is not called on dlclose

In the main program, I dlopen and dlclose (LoadLibrary and FreeLibrary respectively) are a shared library. The shared library contains a static variable that is created on dlopen and destroyed on dlclose. This behavior is consistent with MSVC 2008 and 2013, GCC 3.4.6 and Sunstudio 12.1. However, with GCC 4.9.1 and GCC 5.2.1, the destructor was no longer called on dlclose. Instead, it is called before the program exits.

A feature of the static class of variables is that the constructor has a call to the get template function (global scope), which returns the local static variable.

I was able to reproduce this behavior with the following single cpp file linked in a shared library:

#include <iostream> template <typename T> // In my actual code, i is of type T, however, this has no effect int get() { static int i = 0; return i; } class Dictionary { public: Dictionary() { std::cout << "Calling Constructor" << std::endl; get<int>(); } ~Dictionary(){ std::cout << "Calling Destructor" << std::endl; } private: Dictionary(const Dictionary&); Dictionary& operator=(const Dictionary&); }; static Dictionary d; 

I explored the tricks that can be done to have a destructor called dlclose and concluded the following:

  • If the get function has not been templated
  • else if the variable i in the get function was not static
  • else if the get function is static

The main program code is as follows:

 #include <dlfcn.h> #include <cassert> #include <string> #include <iostream> void* LoadLib(std::string name) { void* libInstance; name = "lib" + name + ".so"; libInstance = dlopen(name.c_str(), RTLD_NOW); if ( ! libInstance ) std::cout << "Loading of dictionary library failed. Reason: " << dlerror() << std::endl; return libInstance; } bool UnloadLib(void* libInstance) { int ret = dlclose(libInstance); if (ret == -1) { std::cout << "Unloading of dictionary library failed. Reason: " << dlerror() << std::endl; return false; } return true; } int main() { void* instance = LoadLib("dll"); assert(instance != 0); assert(UnloadLib(instance)); std::cout << "DLL unloaded" << std::endl; } 

I created binaries with the following commands:

 g++ -m64 -g -std=c++11 -shared -fPIC dll.cpp -o libdll.so g++ -m64 -g -std=c++11 -ldl main.cpp -o main.out 

The output that I get after calling the destructor before exiting the program is as follows:

 Calling Constructor DLL unloaded Calling Destructor 

The output that I get when calling the destructor in dlclose is this:

 Calling Constructor Calling Destructor DLL unloaded 

Questions:

  • If changing behavior between versions of GCC is not an error, can you explain why the destructor is not called on dlclose?
  • Can you explain for each setting: why is the destructor called in dlclose in this case?
+5
source share
1 answer

There is no guarantee that unloading (destructors are called) occurs on dlclose. On musl (unlike glibc), constructors run only when the library is first started, and destructors only run when they exit. For portable code, dlclose cannot be accepted to immediately unload characters.

The unloading behavior depends on the binding of the glibc character during dynamic linking and is not dependent on GCC.

The static variable get :: I have a STB_GNU_UNIQUE binding. For static variables in built-in functions, the uniqueness of an object is guaranteed by the ELF linker. However, for dynamic loading, the dynamic linker provides uniqueness by marking the STB_GNU_UNIQUE symbol. Therefore, another attempt to deploy the same shared library with different code will look for a character and find that it is unique and returns the existing unique characters from the table. A character with a unique binding cannot be unloaded.

Unique bindings can be disabled using "-fno-gnu-unique" if this is not required.

References

The error I raised in GCC

STB_GNU_UNIQUE

+3
source

All Articles