我刚刚读到 init and fini sections在 ELF 文件中尝试一下:
#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/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
就足够了吗? (如果我这样做,gcc 会提示多重定义。)
最佳答案
不要那样做;让您的编译器和链接器按照他们认为合适的方式填充这些部分。
相反,使用适当的 function attributes 标记您的函数, 以便编译器和链接器将它们放入正确的部分。
例如,
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()
消息例。)
关于c - 执行 init 和 fini,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32700494/