~Linux C_22_ELF加载链接

 

Ref: ELF文件的加载和动态链接过程


 

【Linux内核】既支持静态链接的ELF映像,也支持动态链接的ELF映像,而且装入/启动ELF映像必需由内核完成,

而动态链接的实现则既可以在内核中完成,也可在用户空间完成

因此,GNU把对于动态链接ELF映像的支持作了分工:

  • "ELF映像的装入/启动" 在Linux内核中;
  • "动态链接的实现" 放在用户空间(glibc),并为此提供一个称为“解释器”(ld-linux.so.2)的工具软件,
  • "解释器的装入/启动" 也由内核负责

 

内核空间的加载过程

以我们的Hello World为例,gcc在编译时,除非显示的使用static标签,否则所有程序的链接都是动态链接的,也就是说需要解释器。【动态链接需要解释器】

控制权是先交到解释器,由解释器加载动态库,然后控制权才会到用户程序:

  1. 内核加载程序
  2. 内核跳到用户空间,控制权交给用户控件的解释器
  3. 解释器加载运行用户程序所需要的动态库(将每一个依赖的动态库都加载到内存,并形成一个链表)

 

Global Offset Table(GOT

在位置无关代码中,一般不能包含绝对虚拟地址(如共享库)。当在程序中引用某个共享库中的符号时,编译链接阶段并不知道这个符号的具体位置,只有等到动态链接器将所需要的共享库加载时进内存后,也就是在运行阶段,符号的地址才会最终确定。

因此,需要有一个数据结构来保存符号的绝对地址,这就是GOT表的作用,GOT表中每项保存程序中引用其它符号的绝对地址。这样,程序就可以通过引用GOT表来获得某个符号的地址。 

 

Procedure Linkage Table(PLT

过程链接表(PLT)的作用就是将位置无关的函数调用转移到绝对地址。

在编译链接时,链接器并不能控制执行从一个可执行文件或者共享文件中转移到另一个中(如前所说,这时候函数的地址还不能确定),

因此,链接器将控制转移到PLT中的某一项。而PLT通过引用GOT表中的函数的绝对地址,来把控制转移到实际的函数。

 

连接器:"调用时再定位函数地址;第二次调用时就不用了,因为已经记下了"

原来链接器在把所需要的共享库加载进内存后,并没有把共享库中的函数的地址写到GOT表项中,而是延迟到函数的第一次调用时,才会对函数的地址进行定位。

如果是第二次调用puts函数,那么就不需要这么复杂的过程,而只要通过GOT表中已经确定的函数地址直接进行跳转即可。

 

下图是前面过程的一个示意图,红色为第一次函数调用的顺序,蓝色为后续函数调用的顺序(第1步都要执行)。

 

执行有动态链接库的程序:

  1. (User-Mode)用户通过shell执行程序,shell通过exceve进入系统调用。
  2. (Kernel-Mode)sys_execve经过一系列过程,并最终通过ELF文件的处理函数load_elf_binary将用户程序和ELF解释器加载进内存,并将控制权交给解释器。
  3. (User-Mode)ELF解释器进行相关库的加载,并最终把控制权交给用户程序。由解释器处理用户程序运行过程中符号的动态解析。

 

 

 

Ref: C++编译链接全过程


 

可重定位文件与可执行文件的结构比较

 

可重定位文件(.obj)的组织布局和可执行文件(.exe)组织格式的比较

 

从obj 和exe的组织形式比较中发现,exe文件比obj多了一个program header 域,可使用readelf -l 可执行文件名 查看program header域的具体信息。

program header 域 告诉系统如何来创建一个进程的内存映象。

 

End.

posted @ 2019-06-30 13:31  郝壹贰叁  阅读(126)  评论(0)    收藏  举报