编译过程
-
ELF header 描述了文件的总体信息,以及两个 table 的相关信息(偏移地址,表项个数,表项长度);
-
每一个 table 中,包括很多个表项 Entry,每一个表项都描述了一个 Section/Segment 的具体信息。
动态执行
静态执行
sys_execve
该函数主要用于执行一个新的程序,即执行我们想要执行的程序,会检查相应的 argv 以及 envp 等参数。
do_execve
该函数打开目标映像文件,并从目标文件的开始处读入指定长度的(目前为 128)字节来获取相应目标文件的基本信息。
search_binary_handler
该函数会搜索支持处理当前类型的二进制文件类型队列,以便于让各种可执行程序的处理程序进行相应的处理。
load_elf_binary
该函数的主要处理流程如下
-
检查并获取 elf 文件的头部信息。
-
如果目标文件采用动态链接,则使用. interp 节来确定 loader 的路径。
-
将 program header 中记录的相应的段映射到内存中。program header 中有以下重要信息
-
每一个段需要映射到的地址
- 每一个段相应的权限。
- 记录哪些节属于哪些段。
分情况处理
- 动态链接情况下,将 sys_execve 的返回地址改为 loader(ld.so) 的 entry point。
- 静态链接情况下,将 sys_execve 的返回地址改为程序的入口点。
ld.so
该文件有以下功能
- 主要用于载入 ELF 文件中 DT_NEED 中记录的共享库。
- 初始化工作
- 初始化 GOT 表。
- 将 symbol table 合并到 global symbol table。
_start
_start 函数会将以下项目交给 libc_start_main
- 环境变量起始地址
- .init
- 启动 main 函数前的初始化工作
- fini
- 程序结束前的收尾工作。
ELF文件格式
链接视图:
文件开始处是 ELF 头部( ELF Header),它给出了整个文件的组织情况。
如果程序头部表(Program Header Table)存在的话,它会告诉系统如何创建进程。用于生成进程的目标文件必须具有程序头部表,但是重定位文件不需要这个表。
节区部分包含在链接视图中要使用的大部分信息:指令、数据、符号表、重定位信息等等。
节区头部表(Section Header Table)包含了描述文件节区的信息,每个节区在表中都有一个表项,会给出节区名称、节区大小等信息。用于链接的目标文件必须有节区头部表,其它目标文件则无所谓,可以有,也可以没有。
执行视图:
其主要的不同点在于没有了 section,而有了多个 segment。其实这里的 segment 大都是来源于链接视图中的 section。
注意:
尽管图中是按照 ELF 头,程序头部表,节区,节区头部表的顺序排列的。但实际上除了 ELF 头部表以外,其它部分都没有严格的顺序。
静态链接
静态链接的文件中所使用的库文件或者第三方库都被静态绑定了,其引用已经被解析了。
动态链接
动态链接的文件中所使用的库文件或者第三方库只是单纯地被链接到可执行文件中。当可执行文件执行时使用到相应函数时,相应的函数地址才会被解析。
动态链接主要是在程序初始化时或者程序执行的过程中解析变量或者函数的引用。ELF 文件中某些节区以及头部元素就与动态链接有关。动态链接的模型由操作系统定义并实现。
动态链接器可以用来帮助加载应用所需要的库并解析库所导出的动态符号(函数和全局变量)。
当使用动态链接来构造程序时,链接编辑器会在可执行文件的程序头部添加一个 PT_INTERP 类型的元素,以便于告诉系统将动态链接器作为程序解释器来调用。
可执行程序和动态链接器会合作起来为程序创建进程镜像,具体的细节如下:
- 将可执行文件的内存段添加到进程镜像中。
- 将共享目标文件的内存段添加到进程镜像中。
- 为可执行文件以及共享目标文件进行重定位。
- 如果传递给了动态链接器一个文件描述符的话,就将其关闭。
- 将控制权传递给程序。这让我们感觉起来就好像程序直接从可执行文件处拿到了执行权限。
注意:
对于系统提供的动态链接器,不同的系统会不同。
为了安全性,对于set-user
以及 set-group
标识的程序,动态链接器忽略搜索环境变量(例如LD_LIBRARY_PATH
),仅仅搜索DT_RPATH
指定的目录和/usr/lib
。