全局对象初始化
查看如下C++代码:
1 | #include <cstdio> |
这段代码的运行结果是对象A的构造函数被调用,串口输出”construct ok”。根据全局对象的初始化规则,这个调用是发生在main函数之前的,而且可以是多个函数依次调用。那么,这种调用时如何实现的?
通过gcc生成arm64汇编,汇编代码如下:
1 | .arch armv8-a |
关键的汇编代码是以下几部分;
构造函数的具体实现如下:
1 | .LC0: |
梳理汇编中的bl指令,可以得出以下的调用过程:
_GLOBAL__sub_I_g_al -> _Z41__static_initialization_and_destruction_0ii -> _ZN1AC1Ev(_ZN1AC2Ev)
其中_ZN1AC1Ev是一个弱符号,最终解析成_ZN1AC2Ev,即class A的构造函数
1 | .weak _ZN1AC1Ev |
那现在问题就变成_GLOBAL__sub_I_g_al是谁调用的,查看汇编里的相关信息,如下:
1 | .LFE5: |
可以发现,_GLOBAL__sub_I_g_al存在一个单独的,名为”init_array”的section中。
关于这个section,ARM的官方文档中,描述如下:
The C++ Standard places certain requirements on the construction and destruction of objects with static storage duration.
The ARM C++ compiler uses the .init_array area to achieve this. This is a const data array of self-relative pointers to functions.
The linker collects each .init_array from the various translation units together. It is important that the .init_array is accumulated in the same order.
The library routine _cpp_initialize__aeabi is called from the C library startup code, __rt_lib_init, before main. _cpp_initialize__aeabi walks through the .init_array calling each function in turn. On exit, __rt_lib_shutdown calls __cxa_finalize.
Usually, there is at most one function for T::T(), mangled name _ZN1TC1Ev, one function for T::~T(), mangled name _ZN1TD1Ev, one sti function, and four bytes of .init_array for each translation unit. The mangled name for the function f() is _Z1fv. There is no way to determine the initialization order between translation units.
Function-local static objects with destructors are also handled using __aeabi_atexit.
.init_array sections must be placed contiguously within the same region for their base and limit symbols to be accessible. If they are not, the linker generates an error.
查看init_array里的信息
通过以下命令,可以解析出具体调用的函数信息:
objdump -D -j .init_array
在我的环境中,输出如下:
1 | a.out: file format elf64-littleaarch64 |
通过以下命令,将具体的地址,解析成相应的函数符号:
addr2line 0xc1000a68 -e
在我的环境中,输出如下:
1 | /tmp/tmp.KS3wdL1X4n/main.cpp:16 |
需要注意的是,这部分信息属于调试信息,编译的时候,加上”-g”,编译器才会生成这些额外信息。