dlclose上不调用共享库中全局静态变量的析构函数 [英] Destructor of a global static variable in a shared library is not called on dlclose

查看:1291
本文介绍了dlclose上不调用共享库中全局静态变量的析构函数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在一个主程序中,我使用了dlopen和dlclose(分别是LoadLibrary和FreeLibrary)共享库。共享库包含一个静态变量,该变量在dlopen上实例化,并在dlclose时销毁。此行为在MSVC 2008和2013,GCC 3.4.6和Sunstudio 12.1上一致。
然而,GCC 4.9.1和GCC 5.2.1不再在dlclose上调用析构函数。



静态变量的类的特殊性在于,在构造函数中,调用了模板化函数 get (全局作用域),它返回一个本地的 static 变量。

我能够用下面的一个cpp文件链接到一个共享库:

  #include< iostream> 

模板< typename T> //在我的实际代码中,我是T类型的,但是,这没有任何效果
int get()
{
static int i = 0;
返回i;


class Dictionary {
public:
Dictionary()
{
std :: cout<< 调用构造函数<<的std :: ENDL;
get< int>();
}
〜Dictionary(){
std :: cout<< 调用析构函数<<的std :: ENDL;
}

private:
Dictionary(const Dictionary&);
Dictionary& operator =(const Dictionary&);
};
static Dictionary d;

我研究了为了让dlclose调用析构函数所做的调整,并得出结论以下内容:


  • 如果 get 函数没有被模板化

  • else如果函数 get 中的变量i不是静态的

  • 否则如果函数 get 变为static



  • 主程序的代码如下:

     #包括< 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<< 加载字典库失败原因:<< dlerror()<<的std :: ENDL;
    返回libInstance;


    bool UnloadLib(void * libInstance)
    {
    int ret = dlclose(libInstance);
    if(ret == -1)
    {
    std :: cout<< 卸载字典库失败原因:<< dlerror()<<的std :: ENDL;
    返回false;
    }
    返回true;
    }

    int main()
    {
    void * instance = LoadLib(dll);
    assert(instance!= 0);

    assert(UnloadLib(instance));
    std :: cout<< DLL未加载<<的std :: ENDL;
    }

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

      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

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

     调用构造函数
    卸载DLL
    调用析构函数

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

     调用构造函数
    调用析构函数
    DLL被卸载

    问题:


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


    解方案

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



    卸载行为取决于执行动态链接时的glibc符号绑定,并且独立于GCC。 p>

    静态变量get :: i有一个STB_GNU_UNIQUE绑定。对于内联函数中的静态变量,该对象的唯一性由ELF链接器保证。但是,对于动态加载,动态链接器通过标记符号STB_GNU_UNIQUE来确保唯一性。因此,另一个尝试通过其他代码来共享相同的共享库将查找符号,并发现它是唯一的,并从唯一符号表中返回现有符号。如果不需要,可以使用-fno-gnu-unique来禁用唯一绑定。



    参考文献

    我向GCC提出的错误



    STB_GNU_UNIQUE


    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?

    解决方案

    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

    这篇关于dlclose上不调用共享库中全局静态变量的析构函数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆