【问题标题】:Dynamic loaded libraries and shared global symbols动态加载的库和共享的全局符号
【发布时间】:2011-03-01 13:33:50
【问题描述】:

由于我在动态加载的库中观察到全局变量的一些奇怪行为,因此我编写了以下测试。

首先我们需要一个静态链接库:标题test.hpp

#ifndef __BASE_HPP
#define __BASE_HPP

#include <iostream>

class test {
private:
  int value;
public:
  test(int value) : value(value) {
    std::cout << "test::test(int) : value = " << value << std::endl;
  }

  ~test() {
    std::cout << "test::~test() : value = " << value << std::endl;
  }

  int get_value() const { return value; }
  void set_value(int new_value) { value = new_value; }
};

extern test global_test;

#endif // __BASE_HPP

以及来源test.cpp

#include "base.hpp"

test global_test = test(1);

然后我写了一个动态加载的库:library.cpp

#include "base.hpp"

extern "C" {
  test* get_global_test() { return &global_test; }
}

以及加载此库的客户端程序:client.cpp

#include <iostream>
#include <dlfcn.h>
#include "base.hpp"

typedef test* get_global_test_t();

int main() {
  global_test.set_value(2); // global_test from libbase.a
  std::cout << "client:        " << global_test.get_value() << std::endl;

  void* handle = dlopen("./liblibrary.so", RTLD_LAZY);
  if (handle == NULL) {
    std::cout << dlerror() << std::endl;
    return 1;
  }

  get_global_test_t* get_global_test = NULL;
  void* func = dlsym(handle, "get_global_test");
  if (func == NULL) {
    std::cout << dlerror() << std::endl;
    return 1;
  } else get_global_test = reinterpret_cast<get_global_test_t*>(func);

  test* t = get_global_test(); // global_test from liblibrary.so
  std::cout << "liblibrary.so: " << t->get_value() << std::endl;
  std::cout << "client:        " << global_test.get_value() << std::endl;

  dlclose(handle);
  return 0;
}

现在我用

编译静态加载的库
g++ -Wall -g -c base.cpp
ar rcs libbase.a base.o

动态加载的库

g++ -Wall -g -fPIC -shared library.cpp libbase.a -o liblibrary.so

和客户

g++ -Wall -g -ldl client.cpp libbase.a -o client 

现在我观察到:客户端和动态加载的库拥有不同版本的变量global_test。但在我的项目中,我使用的是 cmake。构建脚本如下所示:

CMAKE_MINIMUM_REQUIRED(VERSION 2.6)
PROJECT(globaltest)

ADD_LIBRARY(base STATIC base.cpp)

ADD_LIBRARY(library MODULE library.cpp)
TARGET_LINK_LIBRARIES(library base)

ADD_EXECUTABLE(client client.cpp)
TARGET_LINK_LIBRARIES(client base dl)

分析创建的makefiles我发现cmake构建客户端是用

g++ -Wall -g -ldl -rdynamic client.cpp libbase.a -o client

这最终会导致一个稍微不同但致命的行为:客户端的global_test 和动态加载的库是相同的,但会在程序结束时被销毁两次。

我是否以错误的方式使用 cmake?客户端和动态加载的库是否有可能使用相同的global_test,但没有这种双重破坏问题?

【问题讨论】:

  • 我的第一反应是质疑这个全局变量的必要性。
  • 好的,在我的原始程序中,这个全局变量是一个静态链接库提供的常量。但是在cmake版本中还是会被破坏两次
  • 同样的问题适用于任何单例模式,所以我看不出全局有问题

标签: c++ linux g++ shared-libraries cmake


【解决方案1】:
g++ -Wall -g -ldl -rdynamic client.cpp libbase.a -o client

CMake 添加了-rdynamic 选项,允许加载的库解析加载可执行文件中的符号......所以你可以看到这是你不想要的。如果没有这个选项,它只是偶然错过了这个符号。

但是......你不应该在那里做任何类似的事情。您的库和可执行文件应该 不要共享符号,除非它们确实应该共享。

始终将动态链接视为静态链接。

【讨论】:

    【解决方案2】:

    如果使用共享库,您必须使用 here 之类的宏定义要导出的内容。请参阅那里的 DLL_PUBLIC 宏定义。

    【讨论】:

    • 是通用宏。我在 GNU/Linux 和 Windows 上工作。请参阅声明中的#ifdef。
    【解决方案3】:

    默认情况下,链接器不会将基本可执行文件中的全局变量(“D”)与共享库中的全局变量组合。基本可执行文件是特殊的。使用 ld 读取的那些晦涩的控制文件之一可能有一种晦涩的方法来执行此操作,但我对此表示怀疑。

    --export-dynamic 将导致 a.out 'D' 符号可用于共享库。

    但是,请考虑过程。第 1 步:您从带有“U”的 .o 和带有“D”的 .a 创建一个 DSO。因此,链接器将符号合并到 DSO 中。第 2 步,在 .o 文件之一中创建带有“U”的可执行文件,在 .a 和 DSO 中创建带有“D”的可执行文件。它将尝试使用从左到右的规则来解决。

    与函数相反,变量在任何情况下都会给跨模块的链接器带来一定的困难。更好的做法是避免跨模块边界的全局 var 引用,并使用函数调用。但是,如果您将相同的函数放在基本可执行文件和共享库中,这仍然会失败。

    【讨论】:

    • 您的意思是链接客户端库与libbase.a 是一个坏主意吗?好的,所以您建议不要将库与libbase.a 链接,因为在运行时来自客户端的符号被用于库中的调用?它有效……但它合法吗?
    • 我进行了编辑以简化,因为从您的问题中我不能完全确定哪些文件在哪里结束。
    • 有一个常用的静态链接库(称为libbase.a),一个使用libbase.a 部分的动态加载库(一个插件)和一个加载插件并使用libbase.a 部分的客户端.
    【解决方案4】:

    我的第一个问题是,您是否有任何特殊原因需要静态和动态(通过 dlopen)链接相同的代码?

    对于您的问题:-rdynamic 将从您的程序中导出符号,并且可能发生的情况是动态链接器将对您的全局变量的所有引用解析为它在符号表中遇到的第一个符号。哪个是我不知道的。

    编辑:鉴于您的目的,我会以这种方式链接您的程序:

    g++ -Wall -g -ldl client.cpp -llibrary -L. -o client
    

    您可能需要修改订单。

    【讨论】:

    • 在一些函数中,我在静态链接库中定义了一些庞大的常量。动态加载的库是使用静态链接库提供的这些功能的客户端插件。
    • @phlipsy:您可能应该使用这些常量的方式是将静态库链接到主可执行文件,将主可执行文件与 -rdynamic 链接(就像 cmake 所做的那样),并且不要将插件与静态库链接.但是我不知道如何将您的插件与程序链接。您可以尝试将公共代码(静态库)分解为动态库,并将程序和插件链接到此。
    • 那么在运行时插件将使用位于客户端可执行文件中的已用符号的定义?这合法吗?
    • 抱歉,链接我的程序的建议方式不符合我的目标。该库应作为插件动态加载。
    • 没有什么能阻止你这样做。但是,您需要使用 dlsym 解析插件和主程序中的所有符号,并且您只需从链接命令中跳过 -llibrary。
    【解决方案5】:

    我建议使用dlopen(... RTLD_LAZY|RTLD_GLOBAL); 来合并全局符号表。

    【讨论】:

    • 在使用 dlopen 库从加载中捕获用户定义的异常时,我遇到了 SEGFAULT 的情况。使用 RTLD_GLOBAL 而不是 RTLD_LOCAL 解决了它。谢谢
    【解决方案6】:

    我建议使用 -fvisibility=hidden 参数编译您计划链接到动态库的任何 .a 静态库,因此:

    g++ -Wall -fvisibility=hidden -g -c base.cpp

    【讨论】:

    • 你能解释一下原因吗?
    • 很抱歉,我之前卷入这个问题已经很久了,我已经看不到上下文了。但是,我很感激我的建议很有帮助。不是吗? ;-)
    猜你喜欢
    • 1970-01-01
    • 2015-09-20
    • 1970-01-01
    • 2014-05-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多