主程序(可执行文件)的初始化机制
在非动态库(如主程序)中,.init
和 .init_array
的初始化函数是通过 C 运行时库(CRT) 的入口代码(如 _start
)直接调用的,而非依赖 .dynamic
段中的 DT_INIT
或 DT_INIT_ARRAY
标签。以下是详细分析:
1. 主程序的初始化流程
主程序的初始化由 CRT(C Runtime) 直接处理,具体步骤如下:
-
入口点
_start
可执行文件的入口地址(e_entry
)指向 CRT 提供的_start
函数(位于crt1.o
或Scrt1.o
中)。_start
负责初始化全局环境(如栈、寄存器),并调用__libc_start_main
。 -
__libc_start_main
的核心逻辑__libc_start_main
在调用main
函数前,会依次执行以下操作:- 调用
.init
节的代码(若有定义)。 - 遍历
.init_array
数组,依次执行其中的构造函数(如 C++ 全局对象的构造函数或__attribute__((constructor))
函数)。 - 初始化线程局部存储(TLS)、设置环境变量等。
- 调用
-
.init
和.init_array
的调用代码 CRT 通过 静态链接阶段获取的地址 直接访问.init
和.init_array
,无需通过.dynamic
段。例如,在glibc
的实现中,以下代码片段负责调用.init_array
:// 伪代码(glibc/csu/libc-start.c) void __libc_start_main(...) { // 调用 .init 节(若存在) if (__ehdr.ehdr->e_type == ET_EXEC && __init != NULL) __init(); // 调用 .init_array 中的函数 size_t init_array_size = __init_array_end - __init_array_start; for (size_t i = 0; i < init_array_size; i++) { __init_array_start[i](); } // 进入 main 函数 result = main(argc, argv, __environ); }
动态库
因为没有入口,所以要用 .dynamic
找到
typedef struct {
Elf32_Sword d_tag;
union {
Elf32_Word d_val;
Elf32_Addr d_ptr;
Elf32_Off d_off;
} d_un;
} Elf32_Dyn;//32位程序
typedef struct {
Elf64_Xword d_tag;
union {
Elf64_Xword d_val;
Elf64_Addr d_ptr;
} d_un;
} Elf64_Dyn;
其中 d_tag
决定这个是什么类别信息,以及该如何解析 d_un
内部变量。具体的定义在
https://docs.oracle.com/cd/E23824_01/html/819-0690/chapter6-42444.html
Name | Value | d_un | Executable | Shared Object |
---|---|---|---|---|
DT_INIT | 12 | d_ptr | Optional | Optional |
DT_FINI | 13 | d_ptr | Optional | Optional |
DT_FINI_ARRAY | 26 | d_ptr | Optional | Optional |
DT_INIT_ARRAYSZ | 27 | d_val | Optional | Optional |
DT_FINI_ARRAYSZ | 28 | d_val | Optional | Optional |
所以是有这些东西定位的,在 linker 的时候就加载了
区别:主程序 vs 动态库
特性 | 主程序(ET_EXEC) | 动态库(ET_DYN) |
---|---|---|
初始化触发者 | CRT 的 _start 和 __libc_start_main | 动态链接器(如 ld.so ) |
依赖的元数据 | 无(直接通过静态链接地址访问) | .dynamic 段中的 DT_INIT_ARRAY 标签 |
初始化代码位置 | .init 和 .init_array | 同左 |
是否需要 .dynamic | 不需要(除非动态链接) | 必须包含 .dynamic 段 |