执行init和fini

我刚刚读了ELF文件中的init和fini部分 ,并试了一下:

#include <stdio.h> int main(){ puts("main"); return 0; } void init(){ puts("init"); } void fini(){ puts("fini"); } 

如果我执行gcc -Wl,-init,init -Wl,-fini,fini foo.c并运行结果,则不会打印“init”部分:

 $ ./a.out main fini 

init部分没有运行,还是无法打印?

有没有关于init / fini的任何“官方”文档?

man ld说:

  -init=name When creating an ELF executable or shared object, call NAME when the executable or shared object is loaded, by setting DT_INIT to the address of the function. By default, the linker uses "_init" as the function to call. 

这不应该是,只要命名init函数_init就足够了。 (如果我做gcc抱怨多重定义。)

不要那样做; 让你的编译器和链接器填写他们认为合适的部分。

相反,用适当的函数属性标记你的函数,这样编译器和链接器将把它们放在正确的部分。

例如,

 static void before_main(void) __attribute__((constructor)); static void after_main(void) __attribute__((destructor)); static void before_main(void) { /* This is run before main() */ } static void after_main(void) { /* This is run after main() returns (or exit() is called) */ } 

你也可以分配一个优先级(比如__attribute__((constructor (300))) ),一个101到65535之间的整数,优先级小的函数先运行。

请注意,为了说明,我将这些功能标记为static 。 也就是说,这些函数在文件范围之外是不可见的。 这些功能不需要被导出的符号自动调用。


为了测试,我建议保存在一个单独的文件,如tructor.c

 #include <unistd.h> #include <string.h> #include <errno.h> static int outfd = -1; static void wrout(const char *const string) { if (string && *string && outfd != -1) { const char *p = string; const char *const q = string + strlen(string); while (p < q) { ssize_t n = write(outfd, p, (size_t)(q - p)); if (n > (ssize_t)0) p += n; else if (n != (ssize_t)-1 || errno != EINTR) break; } } } void before_main(void) __attribute__((constructor (101))); void before_main(void) { int saved_errno = errno; /* This is run before main() */ outfd = dup(STDERR_FILENO); wrout("Before main()\n"); errno = saved_errno; } static void after_main(void) __attribute__((destructor (65535))); static void after_main(void) { int saved_errno = errno; /* This is run after main() returns (or exit() is called) */ wrout("After main()\n"); errno = saved_errno; } 

所以你可以编译和链接它作为任何程序或库的一部分。 要将其编译为共享库,请使用例如

 gcc -Wall -Wextra -fPIC -shared tructor.c -Wl,-soname,libtructor.so -o libtructor.so 

你可以插入任何动态链接命令或二进制使用

 LD_PRELOAD=./libtructor.so some-command-or-binary 

这些函数保持errno不变,尽管在实践中不要紧,使用低级的write()系统调用来输出消息到标准错误。 最初的标准错误被复制到一个新的描述符中,因为在许多情况下,标准错误本身在最后一个全局析构函数 – 我们的析构函数 – 运行之前被关闭。

(一些偏执的二进制文件(通常是安全敏感的二进制文件)会关闭所有他们不知道的描述符,所以在所有情况下你都不会看到After main()消息。)

这不是在ld中的错误,而是在主要可执行文件的glibc启动代码中。 对于共享对象,由-init选项设置的函数被调用。


这是提交添加选项-init-fini


程序的_init函数不是由动态链接器的DT_INIT条目从文件glibc-2.21/elf/dl-init.c:58调用,而是从文件glibc-2.21/csu/elf-init.c:83 __libc_csu_init glibc-2.21/csu/elf-init.c:83 __libc_csu_init调用glibc-2.21/csu/elf-init.c:83由主可执行程序。

也就是说,程序的DT_INIT中的函数指针被启动忽略。

如果你用-static编译, fini也不会被调用。

DT_INITDT_FINI绝对不能使用,因为它们是旧式的,见255行 。

以下工作:

 #include <stdio.h> static void preinit(int argc, char **argv, char **envp) { puts(__FUNCTION__); } static void init(int argc, char **argv, char **envp) { puts(__FUNCTION__); } static void fini(void) { puts(__FUNCTION__); } __attribute__((section(".preinit_array"), used)) static typeof(preinit) *preinit_p = preinit; __attribute__((section(".init_array"), used)) static typeof(init) *init_p = init; __attribute__((section(".fini_array"), used)) static typeof(fini) *fini_p = fini; int main(void) { puts(__FUNCTION__); return 0; } 

 $ gcc -Wall ac $ ./a.out preinit init main fini $