Linux Elf装载过程
装载
装载到哪里?内存,进程上看,是映射到了虚拟进程空间.
装载谁? 程序运行的实体代码,数据,来自 elf,共享库,OS 等.
怎么装载?利用程序局部性
原理: 运行某段程序时,很可能也要运行最近的 1 个代码块,于是先提前装载到内存.
内存肯定不够用,不过通过页映射
,需要时(缺页异常),再从磁盘 load 到内存,做替换.
管理这个装载过程的,就是 os 的存储管理器(MMU).
具体过程:
- 建立虚拟进程空间,
- 读取 elf 文件头,建立好 elf 与虚拟空间映射关系,(为后续缺页装载准备).虚拟进程空间有个区域叫 VMA, 对应映射的是 elf 的.text 段
- cpu pc 跳转到入口,开始运行.
- 发现空页,缺页异常,正式装载磁盘页到映射好的物理内存,虚拟进程空间也看的到
两个视图
链接的角度,elf 的段按Section
划分,(链接试图), 从装载的角度,elf 按Segment
(执行试图), 后者为了装载方便,不浪费内存,可能进行了段的合并优化等.
这就是 elf 中program header
的由来,描述 elf 该如何被 os 装载到进程空间.
➜ compile-link-lib readelf -l ab
Elf file type is DYN (Shared object file)
Entry point 0x5a0
There are 9 program headers, starting at offset 64
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000000040 0x0000000000000040
0x00000000000001f8 0x00000000000001f8 R 0x8
INTERP 0x0000000000000238 0x0000000000000238 0x0000000000000238
0x000000000000001c 0x000000000000001c R 0x1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
LOAD 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000950 0x0000000000000950 R E 0x200000
LOAD 0x0000000000000db0 0x0000000000200db0 0x0000000000200db0
0x0000000000000264 0x0000000000000268 RW 0x200000
DYNAMIC 0x0000000000000dc0 0x0000000000200dc0 0x0000000000200dc0
0x00000000000001f0 0x00000000000001f0 RW 0x8
NOTE 0x0000000000000254 0x0000000000000254 0x0000000000000254
0x0000000000000044 0x0000000000000044 R 0x4
GNU_EH_FRAME 0x00000000000007e0 0x00000000000007e0 0x00000000000007e0
0x0000000000000044 0x0000000000000044 R 0x4
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 0x10
GNU_RELRO 0x0000000000000db0 0x0000000000200db0 0x0000000000200db0
0x0000000000000250 0x0000000000000250 R 0x1
Section to Segment mapping:
Segment Sections...
00
01 .interp
02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr
.gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .plt.got
.text .fini .rodata .eh_frame_hdr .eh_frame
03 .init_array .fini_array .dynamic .got .data .bss
04 .dynamic
05 .note.ABI-tag .note.gnu.build-id
06 .eh_frame_hdr
07
08 .init_array .fini_array .dynamic .got
Segment 的内容:
segment类型有LOAD,DYNAMIC,INTERP,GNU_XXX等;
Offset是segment在文件里的偏移;
Virtual Addr就是映射到的虚拟进程空间.
Phy Addr是物理装载地址,一般与上面的一致.
Flag: R,W,X
Align: 对齐属性.(k bytes)
- 对齐是为了页处理的方便,但是可能带来空间浪费.
其他 VMA
VMA 不止有映射代码的,还有映射堆,栈,数据,内核等.
程序实现的视图,还是来自这篇.
段地址对齐
1 个例子,3 个段的 Segment 情况:
合并前,映射了 5 个物理内存页:
合并后,仅映射 3 个物理内存页,(但是虚拟空间多了映射):