Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Destructor of a global static variable in a shared library is not called on dlclose

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

The particularity of the static variable's class, is that in the constructor, there is a call to a templated function get (of global scope) that returns a local static variable.

I was able to reproduce this behavior with the following one cpp file linked into 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 investigated the tweaks that can be made in order to have the destructor called on dlclose, and concluded the following:

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

The main program's code is the following:

#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 built the 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 I get when the destructor is called before program exit is the following:

Calling Constructor
DLL unloaded
Calling Destructor

The output I get when the destructor is called on dlclose is the following:

Calling Constructor
Calling Destructor
DLL unloaded

Questions:

  • If the change of behavior between the versions of GCC is not a bug, can you please explain why is the destructor not called on dlclose?
  • Can you please explain for each tweak: Why is the destructor called on dlclose in this case?
like image 492
Infinity Avatar asked Jul 21 '16 17:07

Infinity


1 Answers

There is no guarantee that unloading (destructors are invoked) happens on dlclose. On musl (as opposed to glibc), constructors only run the first time a library is run, and destructors only run on exit. For portable code, dlclose cannot be assumed to unload the symbols immediately.

The unload behavior depends on glibc's symbol binding when doing dynamic linking, and is independent of GCC.

The static variable get::i has a STB_GNU_UNIQUE binding. For static variables in inline functions, the uniqueness of the object is assured by the ELF linker. However, for dynamic loading, the dynamic linker assures uniqueness by marking the symbol STB_GNU_UNIQUE. Hence, another attempt to dlopen the same shared library by some other code will lookup the symbol and find that it is unique and return the existent one from the unique symbols table. A symbol with unique binding cannot be unloaded.

Unique binding can be disabled with -fno-gnu-unique if not needed.

References

Bug that I raised to GCC

STB_GNU_UNIQUE

like image 113
Infinity Avatar answered Sep 28 '22 20:09

Infinity