我正在尝试在嵌入式目标(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_initialization_and_destruction符号中完成的。
为什么.init_section中没有将指针添加到此符号?
答案 0 :(得分:26)
我知道问题已经差不多两年了,但我只是想弄清楚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
,所以我需要自己调用静态初始值设定项:< / p>
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裸机支持的一个原因。
答案 1 :(得分:4)
Timmmm,
我在nRF51822上遇到了同样的问题,并通过在北欧.ld文件库中的几行添加KEEP()来解决它:
KEEP(*(SORT(.init_array.*)))
KEEP(*(.init_array))
在此期间,我也对fini_array区域做了同样的事情。解决了我的问题,链接器仍然可以删除其他未使用的部分...
答案 2 :(得分:3)
由于gcc的-c参数,您只生成了一个目标文件。要创建.init部分,我相信您需要将.o链接到实际的可执行文件或共享库中。尝试删除-c参数并将输出文件重命名为“foo”,然后使用反汇编程序检查生成的可执行文件。
答案 3 :(得分:2)
如果仔细观察_Z41__static_initialization_and_destruction_0ii
将在全局构造函数中调用。在.init_array
部分(来自CodeSourcery的arm-none-eabi-
)或其他一些函数(__main()
,如果您使用的是Linux g ++)中将链接哪个。 ()这应该在启动时或main()
调用。
另请参阅this链接。
答案 4 :(得分:2)
我有一个类似的问题,我的构造函数没有被调用(带有GCC的nRF51822 Cortex-M0)。问题原来是由于这个链接器标志:
-Wl,--gc-sections
不要问我为什么!我以为它只删除了死代码。