内容当然包括代码、数据,还包括链接时所必须的信息:符号表、调试信息、字符串
一般把这些信息按照不同的属性,以节(Section)的形式存储,有时候也叫做段(Segment)
编译后的代码放在代码段:.code
或 .text
全局变量和局部静态变量数据放在数据段 .data
未初始化的全局变量和局部静态变量放在 .bss
(Block Started by Symbol) 因为他们都是 0,所以放在 data 段占内存没必要,做一个预留内存的标记就行了,这样也没占多少空间
为什么要把程序的指令和数据分开放?
当程序被装载的时候,数据和指令分别映射到两个虚拟内存的区域,可以分别设置权限防止程序指令被改写等等提升安全性
对提高 CPU 缓存命中有提升
最重要的原因:当系统运行着多个程序的的副本的时候,他们的指令都是一样的,所以内存中只要保存一份指令就行了,只要数据不同就行,这对内存的节省提升是巨大的
只读数据段 .rodata
还有一些其他常用的段名
- rel.text:一个 .text 节中位置的列表,当链接器把这个目标文件和其他文件组合时,需要修改这些位置。一般而言,任何调用外部函数或者引用全局变量的指令都需要修改。另一方面,调用本地函数的指令则不需要修改。注意,可执行目标文件中并不需要重定位信息,因此通常省略,除非用户显式地指示链接器包含这些信息。
- rel.data:被模块引用或定义的所有全局变量的重定位信息。一般而言,任何已初始化的全局变量,如果它的初始值是一个全局变量地址或者外部定义函数的地址,都需要被修改。
gcc 提供了一个拓展机制,可以让程序员制定变量所处的段:
__attribute__((section("FOO"))) int global = 42;
__attribute__((section("BAR"))) void foo(){}
解析一个 elf 可执行文件,通过 elf 文件头可以找到段表的偏移和段表的字符表在段表里的下标,通过段表可以解析出整个 elf 文件段结构
readelf -s main.o
Symbol table '.symtab' contains 14 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS main.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1 .text
3: 0000000000000000 0 SECTION LOCAL DEFAULT 3 .data
4: 0000000000000000 0 SECTION LOCAL DEFAULT 4 .bss
5: 0000000000000000 0 SECTION LOCAL DEFAULT 6 .rodata
6: 0000000000000000 0 SECTION LOCAL DEFAULT 7 FOO
7: 0000000000000000 4 OBJECT LOCAL DEFAULT 3 static_var.1
8: 0000000000000004 4 OBJECT LOCAL DEFAULT 4 static_var2.0
9: 0000000000000000 4 OBJECT GLOBAL DEFAULT 5 global_init_var
10: 0000000000000000 4 OBJECT GLOBAL DEFAULT 4 global_uninit_var
13: 0000000000000000 51 FUNC GLOBAL DEFAULT 1 main
ndx 是代码所属的段