Linux加载启动可执行程序的过程(一)内核空间加载ELF的过程

linux内核启动时将ELF格式注册到内核可支持的文件格式链表中,也就是通过register_binfmt 函数将定义的elf_format结构体添加到链表中。该结构体如下:

Linux加载启动可执行程序的过程(一)内核空间加载ELF的过程

当我们执行一个可执行程序的时候, 内核会list_for_each_entry遍历所有注册的linux_binfmt对象, 对其调用load_binrary方法来尝试加载, 直到加载成功为止。上面代码可以看倒,ELF中加载程序即为load_elf_binary,内核中已经注册的可运行文件结构linux_binfmt会让其所属的加载程序load_binary逐一前来认领需要运行的程序binary,如果某个格式的处理程序发现相符后,便执行该格式映像的装入和启动。

接下来,我们具体分析load_elf_binary函数。

第一步,填充并且检查目标程序ELF头部。

Linux加载启动可执行程序的过程(一)内核空间加载ELF的过程

首先是填充映像的文件头,使用了内核之前对bprm->buf填充的128个字节信息。然后比较了文件头的前4个字节,查看是否是标准的ELF文件魔数(”\177ELF”),然后还需要确认该文件是可执行文件还是动态链接库文件,也就是代码中的ET_EXEC和ET_DYN

第二步,通过load_elf_phdrs加载目标程序的程序头表。

Linux加载启动可执行程序的过程(一)内核空间加载ELF的过程

该函数有两个参数,elf_ex表示需要程序头表需要被加载的二进制映像的ELF头部;elf_file表示这个打开的ELF二进制映像文件。

函数首先检查该文件是否包含至少一个段,且所有段的大小之和是否超过64k。如果符合条件,调用kernel_read读入程序头表。

第三步,处理解释器段。

Linux加载启动可执行程序的过程(一)内核空间加载ELF的过程

通过遍历每个段,找到PT_INTERP类型的段,也即解释器段,找到就说明运行过程中需要动态链接。同样也是通过kernel_read函数将解释器段的内容读入缓冲区。readelf命令可以查看到程序的解释器段其实就是一个字符串,也就是解释器的文件名,比如“/lib/ld-linux.so.2”。再调用open_exec()函数根据这个文件名打开解释器文件,和前面一样,再读入128个字节,也就是解释器映像的头部。

第四步,检查并读取解释器的程序头表。

Linux加载启动可执行程序的过程(一)内核空间加载ELF的过程

可以看到加载解释器,其实原理和前面加载ELF可执行程序一样,也是线检查解释器头部信息,然后通过load_elf_phdrs加载解释器的程序头表。

完成了解释器初始化工作,并加载了目标执行执行的程序头表后,开始加载程序的段信息。

第六步,装入可执行文件的PT_LOAD段。

Linux加载启动可执行程序的过程(一)内核空间加载ELF的过程

先遍历每个段,找到类型为PT_LOAD的段,检查地址和页面的信息,确定装入地址后,通过elf_map()建立用户空间虚拟地址与目标映像文件中某个连续区间的映射,返回值就是实际映射的起始地址。

第七步,填入程序的入口地址

Linux加载启动可执行程序的过程(一)内核空间加载ELF的过程

前面的步骤已经完成了目标映像和解释器的加载,并且将目标程序的各个段家在近内存,但是,一个程序成功执行,操作系统还需要知道程序的入口地址,才能开始执行加载好的映像。

如果需要动态链接,就通过load_elf_interp装入解释器映像, 并把将来进入用户空间的入口地址设置成load_elf_interp()的返回值,即解释器映像的入口地址。

而若不需要装入解释器,那么这个入口地址就是目标映像本身的入口地址。

第八步,填写目标文件的参数环境变量等必要信息

Linux加载启动可执行程序的过程(一)内核空间加载ELF的过程

通过create_elf_tables,为目标映像和解释器准备一些有关的信息,包括argc、envc等,这些信息需要复制到用户空间,使它们在CPU进入解释器或目标映像的程序入口时出现在用户空间堆栈上。

最后一步,通过start_thread准备进入新的程序入口。

Linux加载启动可执行程序的过程(一)内核空间加载ELF的过程

参考:https://blog.****.net/gatieme/article/details/51628257