理解 __libc_init_array

22
我查看了http://newlib.sourcearchive.com/documentation/1.18.0/init_8c-source.html中的__libc_init_array源代码。
但我不太理解这个函数的作用。

我知道这些符号

/* These magic symbols are provided by the linker.  */
extern void (*__preinit_array_start []) (void) __attribute__((weak));
extern void (*__preinit_array_end []) (void) __attribute__((weak));
extern void (*__init_array_start []) (void) __attribute__((weak));
extern void (*__init_array_end []) (void) __attribute__((weak));
extern void (*__fini_array_start []) (void) __attribute__((weak));
extern void (*__fini_array_end []) (void) __attribute__((weak));

在链接脚本中定义。
链接脚本的一部分可能如下所示:

  .preinit_array     :
  {
    PROVIDE_HIDDEN (__preinit_array_start = .);
    KEEP (*(.preinit_array*))
    PROVIDE_HIDDEN (__preinit_array_end = .);
  } >FLASH
  .init_array :
  {
    PROVIDE_HIDDEN (__init_array_start = .);
    KEEP (*(SORT(.init_array.*)))
    KEEP (*(.init_array*))
    PROVIDE_HIDDEN (__init_array_end = .);
  } >FLASH
  ...

然后我在ELF-v1.1,gcc 4.7.2,ld和codesourcery(我使用的是codesourcery g ++ lite)的文档中使用关键字“init_array”进行搜索,但没有找到任何内容。

我应该在哪里找到这些符号的规范?


2
https://maskray.me/blog/2021-11-07-init-ctors-init-array - Arto Bendiken
问题中新的newlib源链接的存档URL(现在已经失效):https://web.archive.org/web/20161113155513/http://newlib.sourcearchive.com/documentation/1.18.0/init_8c-source.html - AJM
4个回答

23
这些符号与C/C++构造函数和析构函数的启动和清除代码有关,这些代码在main()之前/之后被调用。名为.init.ctors.preinit_array.init_array的部分与C/C++对象的初始化有关;而.fini.fini_array.dtors部分则是用于拆卸操作。起始和结束符号定义了与这些操作相关的代码段的开始和结束,并可能在运行时支持代码的其他部分中被引用。 .preinit_array.init_array部分包含指向在初始化期间将被调用的函数指针数组。.fini_array是一个将在拆卸时调用的函数数组。起始和结束标签可能用于遍历这些列表。
使用这些符号的代码的良好示例可以在此处找到libc源代码 initfini.c。您可以看到在启动时,将调用__libc_init_array(),这首先通过引用起始和结束标签来调用部分.preinit_array中的所有函数指针。然后它调用.init部分中的_init()函数。最后它调用部分.init_array中的所有函数指针。在完成main()之后,拆卸调用__libc_fini_array()使得在最后调用_fini()之前调用.fini_array中的所有函数。请注意,在计算拆卸时要调用的函数数时,此代码似乎存在复制和粘贴错误。可能他们正在处理实时微控制器操作系统并从未遇到过这个部分。

另外,在__libc_fini_array中,i最好是有符号的,以便循环可以终止(它可能没有)。 - starblue

13

来自@Robotbugs的回答很有趣,但我找到了一些额外的信息,可能会满足其他人的好奇心。

System V Application Binary Interface似乎适用于由gcc生成的可执行文件(我猜其他编译器也是如此-clang也是如此)。

特殊节章说明(仅相关部分,并由我重新排序):

.preinit_array:

此部分包含一个函数指针数组,对于包含该部分的可执行文件或共享对象,它贡献了一个单一的预初始化数组。

.init_array

此部分包含一个函数指针数组,对于包含该部分的可执行文件或共享对象,它贡献了一个单一的初始化数组。

.fini_array

此部分包含一个函数指针数组,对于包含该部分的可执行文件或共享对象,它贡献了一个单一的终止数组。

newlib的init.c文件包括:

/* Iterate over all the init routines.  */
void
__libc_init_array (void)
{
    size_t count;
    size_t i;

    count = __preinit_array_end - __preinit_array_start;
    for (i = 0; i < count; i++)
        __preinit_array_start[i] ();

#ifdef HAVE_INIT_FINI
    _init ();
#endif

    count = __init_array_end - __init_array_start;
    for (i = 0; i < count; i++)
    __init_array_start[i] ();
}

这对应于STM32处理器的规范链接脚本解决方案(以其为例):

.preinit_array     :
{
    PROVIDE_HIDDEN (__preinit_array_start = .);
    KEEP (*(.preinit_array*))
    PROVIDE_HIDDEN (__preinit_array_end = .);
} >FLASH
.init_array :
{
    PROVIDE_HIDDEN (__init_array_start = .);
    KEEP (*(SORT(.init_array.*)))
    KEEP (*(.init_array*))
    PROVIDE_HIDDEN (__init_array_end = .);
} >FLASH
.fini_array :
{
    PROVIDE_HIDDEN (__fini_array_start = .);
    KEEP (*(SORT(.fini_array.*)))
    KEEP (*(.fini_array*))
    PROVIDE_HIDDEN (__fini_array_end = .);
} >FLASH

那个链接脚本部分非常清晰:它定义了Newlib需要执行特殊符号,以便满足System V Application Binary Interface中对preinitinit数组函数的要求。这似乎是C++中静态构造函数的标准解决方案。而fini则对应于静态析构函数。

当然,这个故事最具讽刺意味的部分就是,使用静态C++对象而没有采用首次使用时构造惯用法,才是避免静态初始化顺序问题的最佳方式!也就是说,在实践中,C++对象不应通过上述preinit/init数组进行构造!


4
这些特殊符号最终将被生成的库的PT_DYNAMIC部分引用。 PT_DYNAMIC 定义了使动态链接成功所需的各种资源(库依赖项,导出符号,符号哈希表,init/fini数组等)。
因此,这些列表中包含的任何函数最终都将链接到PT_DYNAMIC部分,并在动态链接过程中适时调用。您可能需要查阅ldd的源代码以获取更多信息。

谢谢您的回复。我刚刚在谷歌上搜索了一下。.init_array部分是由System V ABI指定的特殊部分,我猜它是由gcc自动生成的。 - Pony279

1
这些对象的规范是 ELF 头文件格式的规范,至少解释了它们存在的原因。
它们不是用于任何形式的工作,除非您计划重写 glic lib 及其所有相关内容。简而言之,elf header 需要一个 _start 函数。否则,它将无法启动二进制文件。 libc 库的很大一部分是用汇编语言编写的,而不是 C,这一点没有考虑到。pre array 函数是添加此头的一种方式。
请查看 glibc 中的 gnu-csu 文件夹或 teeny-efl.git 中的示例。它还将数组设置为斜杠格式的字符串。将两个元素都设置为静态的,数组在 argv 和 init_array 中。稍后它将检查它们是否匹配。这也需要比您应该添加到此类函数中的更多代码来打破此过程或执行除其预定用途以外的任何操作,即保持不变。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接