Linux共享库全局构造函数相互依赖
操作系统 Centos 5.6 i686 2.6.18-53.1.4.el5vm。
gcc 版本 4.1.2 20080704(红帽 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 有一个标头,通过它公开功能 Ah,代码中的 B.so 有一个 Bh 标头,其中包含 Ah 和 B 功能。 C.so代码中包含Bh
Ah 定义了一个类型的静态变量 K,当且仅当初始化 A.so 的静态内存管理器时才可以使用该变量。变量 K 直接在 header 中的 Ah 中定义,因此它的初始化会在组成 B.so 和 C.so 的所有对象的全局构造函数中传播。
我这样链接所有内容:
gcc“所有 B 模块”-lstdc++ -pthread -ldl -lrt --no-relocate -Wl,-rpath,$SO_DIR -L$SO_DIR A.so
gcc“所有 C 模块”-lstdc++ -pthread -ldl -lrt --no-relocate -Wl,-rpath,$SO_DIR -L$SO_DIR B.so
gcc“所有 ElfExec 模块”-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“所有 ElfExec1 模块”-lstdc++ -pthread -ldl -lrt --no-relocate -Wl,-rpath,$SO_DIR -L$SO_DIR B.so
这工作正常。
在 ElfExec1 的情况下,链接器发现需要先调用 A.so 中的全局构造函数,然后再调用 B.so 中的全局构造函数。
对于 ElfExec 来说,这种情况不会发生。
我的解决方案是像这样链接 C.so: gcc“所有 C 模块”-lstdc++ -pthread -ldl -lrt --no-relocate -Wl,-rpath,$SO_DIR -L$SO_DIR A.so B.so
这使得 C.so 直接依赖于 A.so。
是否有另一种方法可以告诉链接器全局构造函数调用的顺序?
Operating system Centos 5.6 i686 2.6.18-53.1.4.el5vm.
gcc version 4.1.2 20080704 (Red Hat 4.1.2-48)
ld version 2.17.50.0.6-6.el5 20061020
I compile in this way:
gcc -c -fnon-call-exceptions -fexceptions -Wall -DUNICODE -D_UNICODE -D_REENTRANT -I.
and link this way:
gcc -lstdc++ -pthread -ldl -lrt --no-relocate -Wl,-rpath,$SO_DIR -L$SO_DIR $LIBRARIES
I have 3 libraries and an executable: A.so, B.so, C.so, ElfExec
B.so depends on A.so.
C.so depends on B.so.
In code A.so has a header through which it exposes functionality A.h, B.so in code has a B.h header which includes A.h and B functionality. C.so in code includes B.h.
A.h has defined a static variable K of a type that can be used if and only if a static memory manager from A.so is initialized. Variable K is directly defined in A.h, in the header, because of this its initialization is propagated in the global constructors of all objects composing B.so and C.so.
I link everything like this:
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
I also tried:
gcc "ALL ElfExec MODULES" -lstdc++ -pthread -ldl -lrt --no-relocate -Wl,-rpath,$SO_DIR -L$SO_DIR A.so B.so C.so
When run ElfExec gets a SIGSEGV because it tries to initialize the variable K before the static memory manager from A.so is initialized.
This is because the global constructors from C.so are called before the ones from A.so.
If I make an application ElfExec2 that only need B.so
gcc "ALL ElfExec1 MODULES" -lstdc++ -pthread -ldl -lrt --no-relocate -Wl,-rpath,$SO_DIR -L$SO_DIR B.so
this works correctly.
In the case of ElfExec1 the linker sees that global constructors from A.so are needed to be called first before the ones from B.so.
This does not happed in the case of ElfExec.
My solution is to link C.so like this:
gcc "ALL C MODULES" -lstdc++ -pthread -ldl -lrt --no-relocate -Wl,-rpath,$SO_DIR -L$SO_DIR A.so B.so
This puts a direct dependency in the C.so to A.so.
Is there another way to tell the linker the order of global constructors calling?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
正如您所发现的,不要相信链接器比您更了解。如果顺序很重要,则需要以编程方式指定顺序。不要只是试图欺骗链接器。
如果这些不是图书馆,你就会这么做,对吧?以正确的顺序相互调用的构造函数/初始化函数?
我的第一选择是将库设计为不具有或不使用全局变量。
如果你做不到这一点,我的第二个选择是为每个需要初始化全局变量的库提供一个 init 方法。库的使用者需要先调用该 init 方法,然后才能执行任何操作,并且库必须尝试阻止使用/构造,直到正确完成 init。也许将它们设置为 init 方法的静态,然后设置指向它们的全局指针 (K* k) 可能会有助于该实现。这应该足以使初始化链按正确的顺序组合在一起。
最后,如果让任何库的用户(A 表示 B,B 表示 C,C 表示应用程序)调用 init 方法存在障碍,您可以使用 gcc 之类的语言扩展:
来执行您需要的操作自动加载库。这牺牲了一些可移植性。可能会牺牲更多,较新版本的 gcc 支持构造函数(优先级)语法。
我的最后选择是使用 dlopen 手动加载库的复杂步骤。
最适合您的选择的设计会更好,而最适合您的选择则更糟糕。
As you found, don't trust the linker to know better than you. If the order is important, you need to programmatically specify the order. Don't just try to trick the linker.
That's what you'd do if these weren't libraries, right? Contructors/initialization functions that called each other in the right order?
My first choice would be to design the library to not have or use globals.
If you can't do that, my second choice is for each library that needs to initialize globals to have an init method. Consumers of the library need to call that init method before they can do anything, and the library must try to prevent use/construction until init has been properly done. Perhaps making them static to the init method and then setting global pointers to them (K* k) might help in that implementation. This should be sufficient to make the initialization chain together in the right order.
Finally, if there is an obstacle to having the user of any library (meaning B for A, or C for B, application for C) call the init method, you can use language extensions such as this for gcc:
to do what you need automatically on library load. This sacrifices some portability. Possibly sacrificing more, newer versions of gcc support a constructor(priority) syntax.
My last choice would be the complicated steps to manually load libraries with dlopen.
Design is better with the foremost choice that works for you and worse for the later ones.