c - 执行 init 和 fini

标签 c linux gcc elf

我刚刚读到 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/

相关文章:

c - 使用 lccwin64 编译器链接到 GSL 库

python - 我可以使用 Linux LiveCD 安装任何东西(比如 Python)吗?

Python套接字: how to enable promiscuous mode in linux

regex - 清理文件名的简单脚本

linux - 如何强制链接来自特定库的符号?

c - 在C中对单链表中的元素进行排序

c - 当 pthread_attr_t 不为 NULL 时?

c - 释放 void 指针数组的元素

gcc - 无法在 AIX 6.1 和 GCC 上制作 REDIS 2.8.19

c - 暂时转义函数范围以在 C 中定义全局符号? (海湾合作委员会)