全局构造函数调用不在.int_array节中

Global constructor call not in .init_array section

本文关键字:int array 节中 函数调用 全局      更新时间:2023-10-16

我正试图在嵌入式目标(ARM Cortex-M3)上添加全局构造函数支持。假设我有以下代码:

class foobar
{
    int i;
public:
    foobar()
    {
        i = 100;
    }
    void inc()
    {
        i++;
    }
};
foobar foo;
int main()
{
    foo.inc();
    for (;;);
}

我这样编译它:

arm-none-eabi-g++ -O0 -gdwarf-2 -mcpu=cortex-m3 -mthumb -c foo.cpp -o foo.o

当我查看带有objdump的.init_array部分时,它显示.init_section的大小为零。

我确实得到了一个名为_Z41__static_initialization_and_destruction_0ii的符号。当我反汇编对象文件时,我看到全局构造是在static_initization_and_disation符号中完成的。

为什么在.init_section中没有添加指向该符号的指针?

我知道这个问题已经问了将近两年了,但我只需要自己用GCC弄清楚裸金属C++初始化的机制,所以我想在这里分享细节。事实证明,网络上有很多过时或令人困惑的信息。例如,经常提到的collect2包装器似乎不用于ARM ELF目标,因为它的任意部分支持可以实现下面描述的方法。

首先,当我使用Sourcery CodeBench Lite 2012.09-63使用给定的命令行编译上面的代码时,我确实看到了正确的.init_array节大小4:

$ arm-none-eabi-objdump -h foo.o
foo.o:     file format elf32-littlearm
Sections:
Idx Name          Size      VMA       LMA       File off  Algn
...
 13 .init_array   00000004  00000000  00000000  0000010c  2**2
                  CONTENTS, ALLOC, LOAD, RELOC, DATA
...

当我查看部分内容时,它只包含0:

$ arm-none-eabi-objdump -j .init_array -s foo.o
Contents of section .init_array:
 0000 00000000                             ....

然而,也有一个重新定位部分将其正确设置为_GLOBAL__sub_I_foo:

$ arm-none-eabi-objdump -x foo.o
...
RELOCATION RECORDS FOR [.init_array]:
OFFSET   TYPE              VALUE
00000000 R_ARM_TARGET1     _GLOBAL__sub_I_foo

通常,.init_array指向所有_GLOBAL__sub_I_XXX初始值设定项存根,每个存根都调用自己的_Z41__static_initialization_and_destruction_0ii副本(是的,它是多重定义的),后者使用适当的参数调用构造函数。

因为我在构建中使用-nostdlib,所以我不能使用CodeSourcery的__libc_init_array来执行.init_array,所以我需要自己调用静态初始化程序:

extern "C"
{
    extern void (**__init_array_start)();
    extern void (**__init_array_end)();
    inline void static_init()
    {
        for (void (**p)() = __init_array_start; p < __init_array_end; ++p)
            (*p)();
    }
}

__init_array_start__init_array_end由链接器脚本定义:

. = ALIGN(4);
.init_array :
{
__init_array_start = .;
KEEP (*(.init_array*))
__init_array_end = .;
}

这种方法似乎既适用于CodeSourcery交叉编译器,也适用于本机ARM GCC,例如在Ubuntu 12.10 for ARM中。支持这两个编译器是使用-nostdlib而不依赖CodeSourcery CS3裸机支持的原因之一。

Timmmm,

我刚刚在nRF51822上遇到了同样的问题,并通过在股票Nordic.ld文件的几行周围添加KEEP()来解决:

KEEP(*(SORT(.init_array.*)))
KEEP(*(.init_array))

同时,我对fini_array区域也做了同样的操作。解决了我的问题,链接器仍然可以删除其他未使用的部分。。。

由于gcc的-c参数,您只生成了一个对象文件。要创建.init部分,我认为您需要将.o链接到一个实际的可执行文件或共享库中。尝试删除-c参数并将输出文件重命名为"foo",然后使用反汇编程序检查生成的可执行文件。

如果仔细观察,_Z41__static_initialization_and_destruction_0ii将在全局构造函数中调用。哪个inturn将在.init_array部分(在CodeSourcery的arm-none-eabi-中)或其他函数(如果使用Linux g++,则为__main())中链接。()这应该在启动时或main()时调用。另请参阅此链接。

我遇到了一个类似的问题,我的构造函数没有被调用(nRF51822 Cortex-M0 with GCC)。问题原来是由这个链接器标志引起的:

 -Wl,--gc-sections

别问我为什么!我以为它只是删除了死代码。