C全局静态变量初始化是由链接器完成的?
C global static variable initialization is done by linker?
假设我们有:
f1.c
#include <stdio.h>
static int x = 10;
void f1() {
printf("f1.c : %d\n", x);
}
main.c
extern void f1();
int main(int argc, char **argv) {
f1();
return 0;
}
我们将编译和读取两个 ELF 文件符号表(rel.ELF 和 exec ELF):
$> gcc -c *.c
$> readelf -s f1.o | grep x
Num: Value Size Type Bind Vis Ndx Name
5: 0000000000000000 4 OBJECT LOCAL DEFAULT 3 x
$> gcc *.o
$> readelf -s a.out | grep x
Num: Value Size Type Bind Vis Ndx Name
38: 0000000000601038 4 OBJECT LOCAL DEFAULT 25 x
通过读取可重定位目标文件f1.o
,可以看出全局静态变量x
所在的Value
(也称为地址)是0000000000000000
。
这意味着我们还没有初始化它,因为它仍然是一个 rel。 ELF 目标文件和链接器将处理此问题。
那么我的问题是,如果链接器是在链接 0000000000601038
之后将 x
设置为已知地址值 10 的链接器,它是如何做到的?链接器从哪里获得信息将值设置为10以及谁提供这些信息(f1.o
?)?
值0000000000000000
(在目标文件f1.o
中)是一个相对地址(静态变量),所以是一个偏移量,并且文件还包含与之相关的 relocation 指令。获取要打印的参数 x
的代码也有一些重定位(在某些加载机器指令上)。
在该目标文件中,您可能有一个 .data
部分。该部分应以包含 10.
的单词(具有您在 f1.o
中观察到的 0 偏移量)开头
详细了解 linkers (I recommend Levine's Linkers and loaders book). The linking process (to get the ELF executable) is processing relocation directives. Read also more about the ELF format, starting with elf(5) (after having read the ELF wikipage). Study also the ABI specifications (for Linux x86-64 see here from this 答案),其中详细说明了可能的重定位指令。
你可能想用 gcc -Wall -S -fverbose-asm -O1 f1.c
编译你的 f1.c
然后查看发出的汇编文件 f1.s
您可能还想使用 readelf(1) and objdump(1) 等各种工具检查目标文件 f1.o
和 ELF 可执行文件 a.out
。两者都接受许多选项(特别是 objdump
的 -r
选项以显示重定位指令)。
Dynamic linking (of the C standard library libc.*.so
) introduces some additional complexity in the ELF executable. See also ld-linux(8) (which does some linking job at start of runtime) and vdso(7). You may also want to read Drepper's How To Write Shared Libraries论文.
免费提供的教科书 Operating Systems: Three Easy Pieces could also be worthwhile to read (it explains what a process 及其执行过程。
这个存储具有特定值的静态存储持续时间变量的段称为 .data
(这是 ELF 标准使用的名称,但其他链接器也倾向于使用完全相同的名称)。
如何设置这些变量取决于目标系统。
- 在基于 RAM 的系统(例如 PC)上,整个
.data
段作为可执行文件的一部分预先初始化并与程序一起加载到 RAM。
- 在基于 ROM 的系统(例如带闪存的微控制器)上,
.data
无法提前初始化。相反,它在调用 main() 之前通过一些启动代码 ("CRT") 从 ROM 复制到 RAM。所以它实际上设置在 运行 时间,这意味着在这样的系统上程序启动总是有延迟。为了消除延迟,通常有一个非标准的启动替代方案 ("minimal"),它完全跳过静态存储持续时间变量的初始化。
假设我们有:
f1.c
#include <stdio.h>
static int x = 10;
void f1() {
printf("f1.c : %d\n", x);
}
main.c
extern void f1();
int main(int argc, char **argv) {
f1();
return 0;
}
我们将编译和读取两个 ELF 文件符号表(rel.ELF 和 exec ELF):
$> gcc -c *.c
$> readelf -s f1.o | grep x
Num: Value Size Type Bind Vis Ndx Name
5: 0000000000000000 4 OBJECT LOCAL DEFAULT 3 x
$> gcc *.o
$> readelf -s a.out | grep x
Num: Value Size Type Bind Vis Ndx Name
38: 0000000000601038 4 OBJECT LOCAL DEFAULT 25 x
通过读取可重定位目标文件f1.o
,可以看出全局静态变量x
所在的Value
(也称为地址)是0000000000000000
。
这意味着我们还没有初始化它,因为它仍然是一个 rel。 ELF 目标文件和链接器将处理此问题。
那么我的问题是,如果链接器是在链接 0000000000601038
之后将 x
设置为已知地址值 10 的链接器,它是如何做到的?链接器从哪里获得信息将值设置为10以及谁提供这些信息(f1.o
?)?
值0000000000000000
(在目标文件f1.o
中)是一个相对地址(静态变量),所以是一个偏移量,并且文件还包含与之相关的 relocation 指令。获取要打印的参数 x
的代码也有一些重定位(在某些加载机器指令上)。
在该目标文件中,您可能有一个 .data
部分。该部分应以包含 10.
f1.o
中观察到的 0 偏移量)开头
详细了解 linkers (I recommend Levine's Linkers and loaders book). The linking process (to get the ELF executable) is processing relocation directives. Read also more about the ELF format, starting with elf(5) (after having read the ELF wikipage). Study also the ABI specifications (for Linux x86-64 see here from this 答案),其中详细说明了可能的重定位指令。
你可能想用 gcc -Wall -S -fverbose-asm -O1 f1.c
编译你的 f1.c
然后查看发出的汇编文件 f1.s
您可能还想使用 readelf(1) and objdump(1) 等各种工具检查目标文件 f1.o
和 ELF 可执行文件 a.out
。两者都接受许多选项(特别是 objdump
的 -r
选项以显示重定位指令)。
Dynamic linking (of the C standard library libc.*.so
) introduces some additional complexity in the ELF executable. See also ld-linux(8) (which does some linking job at start of runtime) and vdso(7). You may also want to read Drepper's How To Write Shared Libraries论文.
免费提供的教科书 Operating Systems: Three Easy Pieces could also be worthwhile to read (it explains what a process 及其执行过程。
这个存储具有特定值的静态存储持续时间变量的段称为 .data
(这是 ELF 标准使用的名称,但其他链接器也倾向于使用完全相同的名称)。
如何设置这些变量取决于目标系统。
- 在基于 RAM 的系统(例如 PC)上,整个
.data
段作为可执行文件的一部分预先初始化并与程序一起加载到 RAM。 - 在基于 ROM 的系统(例如带闪存的微控制器)上,
.data
无法提前初始化。相反,它在调用 main() 之前通过一些启动代码 ("CRT") 从 ROM 复制到 RAM。所以它实际上设置在 运行 时间,这意味着在这样的系统上程序启动总是有延迟。为了消除延迟,通常有一个非标准的启动替代方案 ("minimal"),它完全跳过静态存储持续时间变量的初始化。