执行init和fini

时间:2015-09-21 16:45:58

标签: c linux gcc elf

我刚刚阅读了ELF文件中的init and fini sections并尝试了一下:

#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并运行结果,那么&#34; init&#34;部分未打印:

$ ./a.out
main
fini

init部分没有运行,或者它无法以某种方式打印?

是否有任何&#34;官员&#34;关于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抱怨多重定义。)

2 个答案:

答案 0 :(得分:11)

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

相反,使用适当的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()消息。)< / p>

答案 1 :(得分:6)

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

<小时/> Thisld添加选项-init-fini的提交。 程序的_init函数不是由动态链接器的DT_INIT条目从文件glibc-2.21/elf/dl-init.c:58调用,而是从文件glibc-2.21/csu/elf-init.c:83中的__libc_csu_init调用。主要的可执行文件

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

如果使用-static进行编译,则不会调用fini

绝对不应使用

DT_INITDT_FINI,因为它们是old-style, see line 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 a.c
$ ./a.out
preinit
init
main
fini
$