不在dlclose上调用共享库中的全局静态变量的析构函数

Inf*_*ity 7 c++ gcc shared-libraries dynamic-linking dlopen

在主程序中,我dlopendlclose(LoadLibrary以及FreeLibrary分别)一个共享库.共享库包含一个静态变量,该变量在实例化dlopen和销毁时被实例化dlclose.这种行为在MSVC 2008和2013,GCC 3.4.6和Sunstudio 12.1上是一致的.但是,使用GCC 4.9.1和GCC 5.2.1,析构函数不再被调用dlclose.相反,它在程序退出之前被调用.

静态变量类的特殊性在于,在构造函数中,调用模板化函数get(全局范围)返回本地静态变量.

我能够使用链接到共享库的以下一个cpp文件重现此行为:

#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;
Run Code Online (Sandbox Code Playgroud)

为了让析构函数在dlclose上调用,我研究了可以进行的调整,并得出以下结论:

  • 如果函数get没有模板化
  • 否则,如果函数get中的变量i 不是静态的
  • 否则,如果函数get是静态的

主程序的代码如下:

#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;
}
Run Code Online (Sandbox Code Playgroud)

我使用以下命令构建了二进制文件:

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
Run Code Online (Sandbox Code Playgroud)

在程序退出之前调用析构函数时得到的输出如下:

Calling Constructor
DLL unloaded
Calling Destructor
Run Code Online (Sandbox Code Playgroud)

在dlclose上调用析构函数时得到的输出如下:

Calling Constructor
Calling Destructor
DLL unloaded
Run Code Online (Sandbox Code Playgroud)

问题:

  • 如果GCC版本之间的行为改变不是一个bug,你能否解释为什么dlclose上没有调用析构函数?
  • 你可以解释每个调整:为什么在这种情况下析构函数调用dlclose?

Inf*_*ity 5

无法保证卸载(调用析构函数)发生在 dlclose 上。在musl(与 glibc 相对)上,构造函数仅在库第一次运行时运行,而析构函数仅在退出时运行。对于可移植代码,不能假设 dlclose 立即卸载符号。

在进行动态链接时,卸载行为取决于 glibc 的符号绑定,并且与 GCC 无关。

静态变量get::i具有STB_GNU_UNIQUE绑定。对于内联函数中的静态变量,ELF 链接器确保对象的唯一性。但是,对于动态加载,动态链接器通过标记符号来确保唯一性STB_GNU_UNIQUE。因此,另一次尝试通过其他代码打开相同的共享库将查找符号并发现它是唯一的,并从唯一符号表中返回存在的符号。无法卸载具有唯一绑定的符号。

-fno-gnu-unique如果不需要,可以禁用唯一绑定。

参考

我向 GCC 提出的错误

STB_GNU_UNIQUE