我刚刚读了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_INIT
和DT_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 $