红联Linux门户
Linux帮助

Linux下ELF可执行文件装载与运行

发布时间:2016-07-17 15:00:14来源:linux网站作者:Jasonchin221
一、Linux下装载ELF可执行文件的过程
1、创建子进程
内核创建task_struct数据结构,继承父进程的虚拟地址空间(Virtual Memory Space,VMS)。
2、调用execve()系统调用执行指定的ELF文件
(1)调用内核态函数sys_execve(),动态申请一个linux_binprm数据结构,并用ELF可执行文件的数据填充这个结构;
(2)调用prepare_binprm()函数用位于inode中的数据填充linux_binprm数据结构;
(3)调用search_binary_handler()函数遍历formats链表依次应用其load_binary的方法。对于ELF文件会找到elf_format节点,调用load_elf_binary()函数进行如下工作:
1)检查存放在文件前128字节中的魔数以确认可执行格式及其有效性;
2) 读取可执行文件的首部;
3)从可执行文件的.interp段获得动态链接器(Dynamic Linker,DL)的路径名;
4)将DL的前128字节copy到缓冲区;
5)对DL类型进行一致性检查;
6)调用flush_old_exec()函数释放前一次计算占用的几乎所有资源(应该是从父进程继承过来的);
7)调用setup_new_exec()函数,进行为新的进程建立进程线性区布局等工作;
8)调用setup_arg_pages()函数为进程的用户态栈分配一个新的线性区描述符,并把那个线性区插入到进程的地址空间。
setup_arg_pages()函数还把命令行参数和环境变量串所在的页框分配给新的线性区。
 9)创建新的线性区映射ELF中的各个LOAD段,包括代码段和数据段;
线性区中的虚拟地址(Virtual Memory Address, VMA)与ELF中的加载地址(Load Memory Address, LMA)正常情况下是一样的,但在有些嵌入式系统中是不一样的。VMA与ELF中的大多数段(这些段在映射到VMA时都需要建立VMA到文件的映射关系)都是完全对应的,但有一些特殊的段(如.bss)不需要映射到文件,它们与VMA中的地址并不是完全对应的。在对应的段中,其地址与VMS中的地址完全一致。链接器会负责将各个权限相同的段合并到一起,以减少装载并进行页映射时产生的内存浪费。
10)调用load_elf_interp()函数装载DL;
11)调用start_thread()函数修改保存在内核态堆栈但属于用户态寄存器的EIP和ESP的值,以使它们分别指向DL的入口(如果没有获得DL则指向ELF的入口)和新的用户态栈的栈顶;
12)返回0值(成功)。
3、execve()系统调用返回后,新的程序会从EIP寄存器指向的地址开始执行。如果ELF没有链接动态库,则开始执行ELF文件的代码;如果ELF链接了动态库,则执行动态链接;
(1)启动动态链接器本身;
(2)装载所有需要的共享对象;
依次找到所有共享对象的名字、位置,打开相应的文件,将其代码段和数据段映射到进程的地址空间中。可执行文件与动态库的映射都是通过内存映射完成的。在映射时内核为进程创建一个vm_area_struct数据结构,将其与文件的数据结构关联起来。内存映射之后内核并没有立即分配页框给它,当进程试图访问映射的页时会产生一个缺页异常。内核会检查请求页是否在高速缓存中,如果不在则分配一个新的页框,把它追加到高速缓存,并安排一个I/O操作从磁盘读入该页内容;如果请求页在高速缓存中,则将页框与vm_area_struct关联,并将页框的引用计数加1。这样多个使用同一动态库的进程就能共享动态库中的只读的代码段(通过PIC(位置无关代码)技术可以增加很多能共享的只读代码),从而实现节约内存的目的。而动态库中的数据段会被映射为私有的,当进程想要写入数据时,内核会把相应的页复制,并在进程页表中用复制的页替换原来的页(即写时复制)。虽然原来的页框仍然在高速缓存中,但已经不属于这个内存映射,复制的页页不会被插入到页高速缓存中,这样就实现的进程数据的私有。
(3)重定位和初始化。
4、动态链接器将进程的控制权交给程序并从其入口并开始执行。
 
二、64位系统支持运行32位ELF程序的方法
Linux内核在编译时先用64位ELF对应的elf_phdr结构体编译load_elf_binary()相关代码,如果内核开启刘32bit compat编译选项,则再用32位ELF对应的elf_phdr结构体编译load_elf_binary()相关代码,生成一个compat相关的模块;当运行32位ELF文件时内核会调用compat相关的代码装载32位ELF。并通过SET_PERSONALITY宏将进程的执行域设置为32位,从而可以模拟执行32位ELF。
注:出于安全考虑,共享库必须既可读又可执行。
 
本文永久更新地址:http://www.linuxdiyf.com/linux/22455.html