gcc 理解__libc_init_array

e5nqia27  于 2023-05-29  发布在  其他
关注(0)|答案(4)|浏览(703)

我从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”进行搜索,结果一无所获。
我在哪里可以找到这些符号的规格?

tjvv9vkg

tjvv9vkg1#

这些符号与在main()之前/之后调用的C / C构造函数和析构函数启动和拆除代码有关。名为.init.ctors.preinit_array.init_array的部分用于初始化C/C对象,而.fini.fini_array.dtors部分用于拆除。开始和结束符号定义与此类操作相关的代码部分的开始和结束,并且可以从运行时支持代码的其他部分引用。
.preinit_array.init_array部分包含指向将在初始化时调用的函数的指针数组。.fini_array是一个函数数组,将在析构时调用。大概开始和结束标签用于遍历这些列表。
使用这些符号的代码的一个很好的例子是initfini.clibc source。可以看到,在启动时,__libc_init_array()被调用,它首先通过引用开始和结束标签来调用.preinit_array部分中的所有函数指针。然后调用.init部分中的_init()函数。最后,它调用.init_array部分中的所有函数指针。在main()完成之后,对__libc_fini_array()的teardown调用会导致.fini_array中的所有函数都被调用,然后才最终调用_fini()。请注意,在计算拆卸时要调用的函数的计数时,这段代码中似乎存在剪切和粘贴错误。据推测,他们正在处理一个真实的微控制器操作系统,从来没有遇到过这一节。

kkbh8khc

kkbh8khc2#

来自@Robotbugs的答案很有趣,但我发现了一些可能满足其他人好奇心的额外信息。
The System V Application Binary Interface似乎适用于gcc产生的可执行文件(我猜还有其他一些编译器--我想到了clang)。
特殊章节章节规定(仅相关部分,由我重新排序):
.preinit_array:
此节保存函数指针数组,这些指针构成包含此节的可执行文件或共享对象的单个预初始化数组。
.init_array
此节保存函数指针数组,这些指针构成包含此节的可执行文件或共享对象的单个初始化数组。
.fini_array
此节保存函数指针数组,这些指针构成包含此节的可执行文件或共享对象的单个终止数组。
The file init.c from newlib包括:

/* 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数组来构造!

hwazgwia

hwazgwia3#

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

vwhgwdsa

vwhgwdsa4#

这些对象的规范是elf头文件格式的规范。至少他们为什么在那里。
它们不是以任何方式、形状、手段或形式使用的,除非你打算重写glic库和它所涉及的一切。简而言之,elf头需要一个_start函数。它不会发射一个没有的二进制。
libc库的很大一部分是用汇编语言编写的,而不是用C语言编写的,C语言没有考虑到这一点。pre数组函数是添加这个头的一种方法。
查看glibcteeny-efl.git中的gnu-csu文件夹以获取示例。它还将数组设置为斜杠格式的字符串。将argv中的数组和init_array这两个元素都设置为static。它将稍后检查以确保它们匹配。它还需要比您应该添加到此类函数的代码更多的代码来中断此过程或执行任何与其意图无关的其他操作。

相关问题