理解__libc_init_array

时间:2013-03-07 07:20:16

标签: gcc startup elf linker-scripts newlib

我从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(我使用的是代码源g ++ lite)的文档中使用密钥“init_array”进行搜索,但却什么都没得到。

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

4 个答案:

答案 0 :(得分:12)

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

.preinit_array.init_array部分包含指向初始化时将调用的函数的指针数组。 .fini_array是一系列将在销毁时调用的函数。据推测,开始和结束标签用于走这些列表。

initfini.c __libc_init_array()可以找到使用这些符号的代码的一个很好的示例。您可以看到在启动时,.preinit_array被调用,并且它首先通过引用开始和结束标签来调用_init()部分中的所有函数指针。然后它调用.init部分中的.init_array函数。最后,它调用main()部分中的所有函数指针。 __libc_fini_array()完成后,对.fini_array的拆除调用会导致调用_fini()中的所有函数,最后调用ac = { league_id = 1 ,user_id = 3 ,score1 = 14 ,score2 = 10 }; 。请注意,当计算要在拆卸时调用的函数数时,此代码中似乎存在剪切和粘贴错误。据推测,他们正在处理实时微控制器操作系统,从未遇到过这一部分。

答案 1 :(得分:4)

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

The System V Application Binary Interface似乎适用于gcc生成的可执行文件(我想还有其他一些编译器-想到了clang)。

The special sections chapter状态(仅相关部分,由我重新排序):

.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执行the System V Application Binary Interfacepreinitinit指定的数组函数所必需的符号。这似乎是C ++中静态构造函数的标准解决方案。并且fini将对应于静态析构函数。

当然,此故事中最具有讽刺意味的部分是,使用不带Construct On First Use Idiom的静态C ++对象是获取static initialization order problem的最佳方法!即实际上,绝对不要通过上面的preinit / init数组构造C ++对象!

答案 2 :(得分:3)

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

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

答案 3 :(得分:1)

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

他们 NOT 以任何方式使用形状手段或形式,除非您计划重写glic lib及其所涉及的一切。简而言之,elf标头需要_start函数。没有一个二进制文件它不会启动它。

libc库的很大一部分是用汇编而不是C编写的,不考虑这个。预数组函数是添加此标头的一种方法。

查看 glibc 中的 gnu-csu 文件夹或 teeny-efl.git 中的示例。它还将数组设置为斜杠格式化字符串。将两个元素设置为static,argv中的数组和init_array。稍后会检查以确保它们匹配。它还需要比你应该添加的代码更多的代码来打破这个过程,或者除了它的意思之外还要做任何事情。去玩你的冰箱吧。