当一个 ELF(Executable and Linkable Format)文件加载到内存后,它的各个段会根据文件中的描述被映射到内存的不同区域。ELF 文件被广泛用于 Unix/Linux 系统中的可执行文件、共享库和目标文件。典型的 ELF 文件包含多个段(sections),这些段被加载到内存中用于不同的目的,例如代码、数据、堆栈等。
ELF 文件的段(Section)与内存布局
ELF 文件在磁盘上有多个段,这些段在被加载到内存后,操作系统将它们映射到不同的虚拟内存区域。不同的段有不同的功能,它们通常包括代码段、数据段和其他需要加载的内容。
ELF 文件的主要段和内存中的分布
在内存中,ELF 文件通常会被分为以下几个关键段:
-
.text 段(代码段)
- 功能:该段存放可执行的程序代码,即编译后的指令。
- 权限:一般标记为只读和可执行(
r-x
)。 - 内存位置:通常位于程序映射的最低虚拟地址附近,且通常是页面对齐的(比如 4KB 对齐)。
- 映射到内存后:该段加载到内存后,系统将其标记为可执行的内存区域,而不会允许写入操作。
内存布局示意图:
0x08048000: (低地址) ↓ .text 段(代码段) 存储程序的可执行代码 [r-x] (只读 + 可执行) ↑
-
.rodata 段(只读数据段)
- 功能:该段存储程序的只读数据,如字符串常量、全局常量等。
- 权限:标记为只读(
r--
),防止运行时修改。 - 内存位置:通常紧随
.text
段之后加载到内存中。
内存布局示意图:
0x08049000: ↓ .rodata 段(只读数据段) 存储只读数据(例如字符串常量) [r--] (只读) ↑
-
.data 段(数据段)
- 功能:该段存储初始化的全局变量和静态变量。
- 权限:标记为可读和可写(
rw-
),因为这些变量在程序运行过程中可能会被修改。 - 内存位置:通常位于
.rodata
段之后。
内存布局示意图:
0x0804A000: ↓ .data 段(数据段) 存储初始化的全局变量 [rw-] (可读 + 可写) ↑
-
.bss 段(未初始化数据段)
- 功能:该段存储未初始化的全局变量和静态变量。在 ELF 文件中,
.bss
段并不占据磁盘空间(只在内存中分配),其大小在 ELF 文件头中描述。 - 权限:标记为可读和可写(
rw-
),因为这些变量可能会被修改。 - 内存位置:通常位于
.data
段之后。
内存布局示意图:
0x0804B000: ↓ .bss 段(未初始化数据段) 存储未初始化的全局变量 [rw-] (可读 + 可写) ↑
- 功能:该段存储未初始化的全局变量和静态变量。在 ELF 文件中,
-
堆(Heap)
- 功能:堆是动态分配内存的区域,用于程序在运行时通过
malloc
等动态分配函数申请的内存。 - 权限:标记为可读和可写(
rw-
)。 - 内存位置:堆区域位于
.bss
段之后,通常会随着程序的运行动态增长,操作系统通过系统调用(如brk
和sbrk
)来调整堆的大小。
内存布局示意图:
0x0804C000: ↓ 堆 动态内存分配区域 [rw-] (可读 + 可写) ↑
- 功能:堆是动态分配内存的区域,用于程序在运行时通过
-
栈(Stack)
- 功能:栈是用于函数调用时存储局部变量、返回地址等信息的区域。
- 权限:标记为可读和可写(
rw-
),并具有自动增长的特点(通常栈向下增长)。 - 内存位置:栈通常位于虚拟地址空间的高地址处,并向低地址方向增长。
内存布局示意图:
高地址: ↓ 栈(Stack) [rw-] (可读 + 可写) ↑
ELF 文件的段加载和布局示意图
内存中 ELF 文件加载后的典型布局示例如下:
内存高地址
+----------------------------+
| 栈段 (Stack) | ← 栈从高地址向低地址增长
+----------------------------+
| ... |
+----------------------------+
| 堆段 (Heap) | ← 堆从低地址向高地址增长
+----------------------------+
| .bss 段 | ← 未初始化数据
+----------------------------+
| .data 段 | ← 已初始化的全局变量
+----------------------------+
| .rodata 段 | ← 只读数据
+----------------------------+
| .text 段 | ← 可执行代码
+----------------------------+
内存低地址
- 栈:位于高地址,向低地址方向增长。栈用于存储函数调用的局部变量和返回地址等信息。
- 堆:位于
.bss
段之后,随着动态内存分配不断增长。堆用于存储运行时动态分配的内存。 - .bss 段:存储未初始化的全局变量和静态变量。
- .data 段:存储已初始化的全局变量和静态变量。
- .rodata 段:存储只读数据(如常量字符串、常量数组等)。
- .text 段:存储程序的可执行代码。
各个段的权限说明
- .text 段:只读且可执行,存放程序的指令。
- .rodata 段:只读,存放只读数据。
- .data 段:可读可写,存放已初始化的全局变量和静态变量。
- .bss 段:可读可写,存放未初始化的全局变量和静态变量。
- 堆:可读可写,用于动态内存分配。
- 栈:可读可写,用于函数调用时存储局部变量、返回地址等。
段加载的过程
-
解析 ELF 文件头:加载器首先读取 ELF 文件头,了解文件的格式、段的数量和位置等信息。
-
加载各个段:根据程序头表(Program Header Table)中描述的段信息,加载器将各个段(如
.text
、.data
、.bss
)从磁盘加载到内存,并设置相应的访问权限。 -
内核映射内存区域:操作系统的加载器为 ELF 文件中的各个段分配虚拟地址空间,并通过页表将虚拟地址映射到实际的物理内存。还会为堆和栈分配内存。
-
权限设置:根据段的类型和需求,加载器为每个段设置合适的权限。例如,
.text
段被标记为可执行但不可写,.data
段可读可写等。
总结
ELF 文件加载到内存后,各个段的分布和权限如下:
- .text 段:存放可执行代码,通常位于内存低地址,具有只读和可执行权限。
- .rodata 段:存放只读数据,具有只读权限。
- .data 段:存放初始化的全局变量,具有读写权限。
- .bss 段:存放未初始化的全局变量,