17

我从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”进行搜索,但一无所获。

我在哪里可以找到这些符号的规格?

4

4 回答 4

19

这些符号与 C/C++ 构造函数和析构函数的启动和拆除之前/之后调用的代码有关main()。名为.init.ctors.preinit_array.init_array的节用于初始化 C/C++ 对象,节.fini.fini_array、 和.dtors用于拆除。开始和结束符号定义与此类操作相关的代码段的开始和结束,并且可能从运行时支持代码的其他部分引用。

.preinit_arrayand.init_array部分包含指向将在初始化时调用的函数的指针数组。.fini_array是一个将在销毁时调用的函数数组。大概开始和结束标签用于遍历这些列表。

使用这些符号的代码的一个很好的例子可以在这里找到libc source for initfini.c. 你可以看到,在启动时,__libc_init_array()被调用,这首先.preinit_array通过引用开始和结束标签来调用部分中的所有函数指针。然后它调用该部分_init()中的函数.init。最后它调用 section 中的所有函数指针.init_array。After main()is complete the teardown call to__libc_fini_array()导致调用中的所有函数.fini_array,然后最终调用_fini(). 请注意,此代码在计算拆解时调用的函数计数时似乎存在剪切和粘贴错误。大概他们正在处理实时微控制器操作系统并且从未遇到过这部分。

于 2015-06-22T05:10:06.510 回答
10

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

System V 应用程序二进制接口似乎适用于 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 应用程序二进制接口preinit和指定的数组函数所需的符号init。这似乎是 C++ 中静态构造函数的标准解决方案。并且fini将对应于静态析构函数。

当然,这个故事中最讽刺的部分是使用静态 C++ 对象而不使用Construct On First Use Idiom是解决静态初始化顺序问题的最佳方法!即 C++ 对象实际上不应该通过上面的preinit/init数组来构造!

于 2020-02-11T22:03:02.383 回答
4

这些特殊符号最终会被PT_DYNAMIC生成库的部分引用。PT_DYNAMIC定义使动态链接成功所需的各种资源(库依赖项、导出符号、符号哈希表、init/fini 数组等)。

因此,这些列表中的任何函数最终都将链接到该PT_DYNAMIC部分,并在动态链接过程中的适当时间被调用。您可能需要查阅来源以ldd获取更多信息。

于 2013-03-07T07:27:38.570 回答
1

这些对象的规范是elf头文件格式的规范。至少他们为什么在那里。

除非您计划重写 glic 库及其与之通信的所有内容,否则它们不会以任何方式使用形状手段或形式。简而言之,elf 标头需要一个 _start 函数。没有它就不会启动二进制文件。

libc 库的很大一部分是用汇编而不是 C 编写的,C 没有考虑到这一点。pre 数组函数是添加此标头的一种方式。

查看glibcteeny-efl.git中的gnu- csu文件夹以获取示例。它还将数组设置为斜线格式的字符串。将两个元素设置为静态,argv 中的数组和 init_array。稍后它将检查以确保它们匹配。它还需要比你应该添加到这种函数中的更多代码来破坏这个过程或做任何其他事情,而不是让它独自一人。去玩你的冰箱。

于 2018-01-17T14:09:13.020 回答