.init/.fini 未被弃用。它仍然是 ELF 标准的一部分,我敢说它将永远存在。 .init/.fini 中的代码在加载/卸载代码时由加载器/运行时链接器运行。 IE。 .init 中的每个 ELF 加载(例如共享库)代码都会运行。仍然可以使用该机制来实现与__attribute__((constructor))/((destructor)) 大致相同的事情。这是老式的,但它有一些好处。
.ctors/.dtors 机制例如需要 system-rtl/loader/linker-script 的支持。这远不能确定在所有系统上都可用,例如代码在裸机上执行的深度嵌入式系统。 IE。即使 GCC 支持__attribute__((constructor))/((destructor)),它也不确定它是否会运行,因为它取决于链接器来组织它和加载器(或者在某些情况下,引导代码)来运行它。要改用.init/.fini,最简单的方法是使用链接器标志:-init & -fini(即来自 GCC 命令行,语法为 -Wl -init my_init -fini my_fini)。
在支持这两种方法的系统上,一个可能的好处是.init 中的代码在.ctors 之前运行,.fini 中的代码在.dtors 之后运行。如果顺序是相关的,那至少是一种粗略但简单的区分初始化/退出函数的方法。
一个主要缺点是每个可加载模块不能轻易地拥有多个_init 和一个_fini 函数,并且可能不得不将代码片段化为更多.so 而不是动机。另一种是在使用上述链接器方法时,替换了原来的_init 和_fini 默认函数(由crti.o 提供)。这是通常发生各种初始化的地方(在 Linux 上,这是初始化全局变量赋值的地方)。描述了一种解决方法here
请注意,在上面的链接中,不需要级联到原始 _init(),因为它仍然存在。然而,内联程序集中的 call 是 x86 助记符,并且从程序集中调用函数对于许多其他体系结构(例如 ARM)来说看起来完全不同。 IE。代码不透明。
.init/.fini 和 .ctors/.detors 机制相似,但不完全一样。 .init/.fini 中的代码“按原样”运行。 IE。你可以在.init/.fini 中拥有几个函数,但是在语法上很难在纯 C 中完全透明地把它们放在那里而不破坏许多小的 .so 文件中的代码。
.ctors/.dtors 的组织方式不同于 .init/.fini。 .ctors/.dtors 部分都只是带有指向函数的指针的表,而“调用者”是系统提供的循环,它间接调用每个函数。 IE。循环调用者可以是特定于体系结构的,但由于它是系统的一部分(如果它存在的话)并不重要。
下面的 sn-p 将新的函数指针添加到.ctors 函数数组,主要与__attribute__((constructor)) 相同(方法可以与__attribute__((constructor))) 共存。
#define SECTION( S ) __attribute__ ((section ( S )))
void test(void) {
printf("Hello\n");
}
void (*funcptr)(void) SECTION(".ctors") =test;
void (*funcptr2)(void) SECTION(".ctors") =test;
void (*funcptr3)(void) SECTION(".dtors") =test;
还可以将函数指针添加到完全不同的自行发明的部分。在这种情况下,需要修改的链接描述文件和模拟加载程序.ctors/.dtors 循环的附加函数。但是有了它,可以更好地控制执行顺序,添加参数和返回代码处理等。 (例如,在 C++ 项目中,如果需要在全局构造函数之前或之后运行某些东西,这将很有用)。
如果可能,我更喜欢__attribute__((constructor))/((destructor)),这是一个简单而优雅的解决方案,即使感觉像是在作弊。对于像我这样的裸机程序员来说,这并不总是一种选择。
书中的一些很好的参考资料Linkers & loaders。