Linux共享库全局构造函数的相互依赖性

时间:2011-07-11 12:48:53

标签: linux gcc linker ld

操作系统Centos 5.6 i686 2.6.18-53.1.4.el5vm。
gcc版本4.1.2 20080704(Red Hat 4.1.2-48)
ld版本2.17.50.0.6-6.el5 20061020

我用这种方式编译:
gcc -c -fnon-call-exceptions -fexceptions -Wall -DUNICODE -D_UNICODE -D_REENTRANT -I。
以这种方式联系:
gcc -lstdc ++ -pthread -ldl -lrt --no-relocate -Wl,-rpath,$ SO_DIR -L $ SO_DIR $ LIBRARIES

我有3个库和一个可执行文件:A.so,B.so,C.so,ElfExec
B.so取决于A.so. C.so取决于B.so.
在代码A.so中有一个标题,它通过它来公开功能A.h,B.so在代码中有一个B.h标题,其中包括A.h和B功能。 C.so代码包括B.h.
A.h定义了一个类型的静态变量K,当且仅当初始化来自A.so的静态内存管理器时才能使用该变量。变量K直接在头文件中的A.h中定义,因此它的初始化在组成B.so和C.so的所有对象的全局构造函数中传播。

我将这样的一切联系起来:
gcc“ALL B MODULES”-lstdc ++ -pthread -ldl -lrt --no-relocate -Wl,-rpath,$ SO_DIR -L $ SO_DIR A.so
gcc“ALL C MODULES”-lstdc ++ -pthread -ldl -lrt --no-relocate -Wl,-rpath,$ SO_DIR -L $ SO_DIR B.so
gcc“ALL ElfExec MODULES”-lstdc ++ -pthread -ldl -lrt --no-relocate -Wl,-rpath,$ SO_DIR -L $ SO_DIR C.so
我也尝试过:
gcc“ALL ElfExec MODULES”-lstdc ++ -pthread -ldl -lrt --no-relocate -Wl,-rpath,$ SO_DIR -L $ SO_DIR A.so B.so C.so

运行时ElfExec获取SIGSEGV,因为它尝试在初始化A.so的静态内存管理器之前初始化变量K.
这是因为C.so的全局构造函数在A.so之前被调用。
如果我创建一个只需要B.so的应用程序ElfExec2 gcc“ALL ElfExec1 MODULES”-lstdc ++ -pthread -ldl -lrt --no-relocate -Wl,-rpath,$ SO_DIR -L $ SO_DIR B.so
这工作正常。

在ElfExec1的情况下,链接器看到来自A.so的全局构造函数需要先在B.so.之前调用。 在ElfExec的情况下,这不会发生。

我的解决方案是将C.so链接为: gcc“ALL C MODULES”-lstdc ++ -pthread -ldl -lrt --no-relocate -Wl,-rpath,$ SO_DIR -L $ SO_DIR A.so B.so
这将C.so直接依赖于A.so。

是否有另一种方法可以告诉链接器调用全局构造函数的顺序?

1 个答案:

答案 0 :(得分:4)

正如您所发现的,不要相信链接器比您更了解。如果订单很重要,则需要以编程方式指定订单。不要只是试图欺骗链接器。

如果这些不是图书馆,那就是你要做的,对吧?构造函数/初始化函数以正确的顺序相互调用?

我的第一选择是将库设计为不具有或使用全局变量。

如果你不能这样做,我的第二个选择是每个需要初始化全局变量以获得init方法的库。库的消费者需要在他们可以执行任何操作之前调用该init方法,并且库必须尝试在init正确完成之前阻止使用/构造。也许让它们对init方法保持静态,然后设置它们的全局指针(K * k)可能有助于实现。这应该足以使初始化链以正确的顺序组合在一起。

最后,如果让任何库的用户有障碍(意思是B代表A,或C代表B代表C代理)调用init方法,你可以使用语言扩展名,如gcc:

extern "C" __attribute__ ((constructor)) void A_lib_ctor()
{
    // ....
}
extern "C" __attribute__ ((destructor)) void A_lib_dtor()
{
    // ....
}

在库加载时自动执行您需要的操作。这牺牲了一些便携性。可能牺牲更多,更新版本的gcc支持构造函数(优先级)语法。

我的最后一个选择是使用dlopen手动加载库的复杂步骤。

设计更好,最重要的选择适合你,而后者更糟糕。