浅谈elf文件执行过程

来源:互联网 发布:阿里云客服平台 编辑:程序博客网 时间:2024/05/19 23:59
1 , ELF(Executable and Linking Format)文件.
ELF文件分为三种:
1) 可重定位的对象文件(Relocatable file),也就是.o文件.
2) 可执行的对象文件(Executable file)
3) 可被共享的对象文件(Shared object file)也就是动态库文件,即 .so 文件。

ELF文件结构是这样的:

+-------------------+
|   ELF文件头   |
|             |
+-------------------+
|     程序头     |
|   (c0h字节)   |
+-------------------+
|   程序节 #1   |
+-------------------+
|   程序节 #2   |
+-------------------+
.   .   .   .   .   .
.   .   .   .   .   .
.   .   .   .   .   .
+-------------------+
|   程序节 #n   |
+-------------------+
|     节表头     |
|   (n*20h字节)   |

其中涉及到三个结构体,定义在内核/include/linux/elf.h中.即elf文件头结构体Elf32_Ehdr;程序头结构体Elf32_Phdr;节结构体Elf32_Shdr;

这三个结构体就包含了elf文件的所有信息,信息量相当大.

对于这几个结构体的详细介绍,浏览:

http://hi.baidu.com/geekos/blog/item/b10c1751b53e991c367abe27.html


elf可执行文件执行过程也就是加载过程:
以helloworld程序为例子.
void main(){ printf("Hello world!\n");}
gcc -g helloworld.c -o hello(hello为可执行文件.)

1,可执行文件类型注册.
内核对所支持的每种可执行的程序类型都有个 struct linux_binfmt 的数据结构(详见program.c).这个结构体中有一个load_binary函数指针.其实根据elf文件格式
可以知道,.load_binary=.load_elf_binary.要支持 ELF 文件的运行,则必须向内核登记这个数据结构,加入到内核支持的可执行程序的队列中。内核提供两个函数来完成这个功能,一个注册,一个注销.
函数如下:
    int register_binfmt(struct linux_binfmt * fmt)
    int unregister_binfmt(struct linux_binfmt * fmt)
   当需要运行一个程序时,则扫描这个队列,让各个数据结构所提供的处理程序(ELF中即为 load_elf_binary)逐一前来认领,如果某个格式的处理程序发现相符后,便执行该格式映像的装入和启动。

2,内核空间的加载:
   内核中实际执行 execv()或 execve()系统调用的程序是 do_execve(),这个函数先打开目标映像文件,并从目标文件的头部(第一个字节开始)读入若干(当前 Linux 内核中是 128)字节
(实际上就是填充 ELF文件头),然后调用另一个函数search_binary_handler(),在此函数里面,它会搜索我们上面提到的 Linux 支持的可执行文件类型队列,让各种可执行程序的处理程序前来认领和处理。
如果类型匹配,则调用 load_binary函数指针所指向的处理函数来处理目标映像文件。在load_elf_binary之前,内核从elf文件的头128字节(即elf文件头)中获得了elf头文件的信息,包括文件类型,运行的机器,
版本,程序头个数和大小.节个数和大小,程序头和节的地址等.   接下来通过kernel_read读入整个程序头,然后在程序头中读入程序的"解释器"到缓冲区(此时的解释器只是一个字符串,这个字符串就是解释器的名称,
比如"/lib/ld-linux.so.2").接着执行程序根据目标镜像文件程序头中类型为PT_LOAD的段,因为只有PT_LOAD类型的段才是能加载的.在加载过程中,根据程序头给出的p_vaddr确定装入地址后,根据可加载段的大小.
通过elf_map()函数在用户空间中建立用户空间虚拟地址于目标镜像文件中某个连续区间的联系.elf_map()函数的返回值就是实际映射的起始地址.
   对于用户空间的入口地址,假如有解释器的加入,则把入口地址设置为load_elf_interp()的返回值.即解释器的镜像入口地址.但若不装入解释器,那么这个用户空间的入口地址就是目标镜像本身的入口地址.
   从此,程序的控制权就交给了解释器.然后解释器加载动态库,将每一个有依赖关系的动态库都加载到内存,形成一个链表,后面的符号解析过程主要就是在这个链表中搜索符号的定义.

3,ELF文件中符号的动态解析过程:
    在helloworld.c编译成汇编代码后,其中的printf()函数,对应的汇编代码为:call 80482bc<puts@plt>.
    而puts@plt这个标号就将在刚才动态链接库加载生成的符号链表中,从而得到printf函数的绝对地址,载入内存,运行程序: