什么是ELF文件
ELF文件头部格式
ELF在计算机科学中,是一种用于二进制文件、可执行文件、目标代码、共享库和核心转储格式文件。是UNIX系统实验室(USL)作为应用程序二进制接口(Application Binary Interface,ABI)而开发和发布的,也是Linux的主要可执行文件格式。
ELF文件种类
ELF文件组织格式
ELF文件由4部分组成,分别是ELF头(ELF header)、程序头表(Program header table)、节(Section)和节头表(Section header table)。实际上,一个文件中不一定包含全部内容,而且他们的位置也未必如同所示这样安排,只有ELF头的位置是固定的,其余各部分的位置、大小等信息由ELF头中的各项值来决定。
ELF与PE的差异
ELF与编译链接
ELF关键section
.interp: 动态链接器,通常为/lib/ld-linux.so.2
.dynamic:该段中保存了动态链接器所需要的基本信息。
.got:存放模块动态重定位符号的地址信息。
.got.plt:用于存储外部函数地址(初始化的时候直接或间接指向__do_runtime_resolve)。
.plt:主要用于linux下,PLT延迟加载技术使用。
.rodata:主要存放程序的只读数据部分。
.init_array:用于存放先于main函数执行的函数和功能(不仅限于ctors,构造函数)。
.fini_array:用于存放main函数退出后的函数和功能(不仅限于dtors,析构函数)。
.symtab:符号表。
.strtab:字符串符号表, .symtab的辅助段。
.dynsym: 该段保存与动态链接相关的符号。
.dynstr:该段是 .dynsym 段的辅助段。
.rel.dyn:对数据引用的修正,其所修正的位置位于 “.got”以及数据段(类似重定位段 "rel.data")
.rel.plt:对函数引用的修正,其所修正的位置位于 “.got.plt”
.bss:通常是指用来存放程序中未初始化的全局变量和静态变量的一块内存区域。
.data:通常是指用来存放程序中已初始化的全局变量的一块内存区域。
.text:指用来存放程序执行代码的一块存储区域。
.heap:malloc类型函数分配内存的段
.stack:局部变量,函数调用需要使用的存储区域
静态链接
一、静态链接:静态链接就是,在生成可执行程序的时候,把目标文件.o 和 静态库 .a ,使用 ld 链接器,链接生成一个可执行程序(在链接阶段重定位符号表)。
二、静态链接的缺点很明显,一是浪费空间,因为每个可执行程序中对所有需要的目标文件都要有一份副本;另一方面就是更新比较困难,因为每当库函数的代码修改了,这个时候就需要重新进行编译链接形成可执行程序。但是静态链接的优点就是,在可执行程序中已经具备了所有执行程序所需要的任何东西,在执行的时候运行速度快。
三、链接器在链接静态链接库的时候是以目标文件为单位的。比如我们引用了静态库中的printf()函数,那么链接器就会把库中包含printf()函数的那个目标文件链接进来,如果很多函数都放在一个目标文件中,很可能很多没用的函数都被一起链接进了输出结果中。
动态解释器
从编译/链接和运行的角度看,应用程序有两种不同的ELF格式映像。一种是静态链接的,在装入/启动其运行时无需装入函数库映像、也无需进行动态连接。另一种是动态连接,需要在装入/启动其运行时同时装入函数库映像并进行动态链接。Linux内核既支持静态链接的ELF映像,也支持动态链接的ELF映像,而且装入/启动ELF映像必需由内核完成,而动态连接的实现则既可以在内核中完成,也可在用户空间完成。因此,GNU把对于动态链接ELF映像的支持作了分工:把ELF映像的装入/启动入在Linux内核中;而把动态链接的实现放在用户空间(glibc),并为此提供一个称为“解释器”(ld-linux.so.2)的工具软件,而解释器的装入/启动也由内核负责,这在后面我们分析ELF文件的加载时就可以看到。
当执行一个用户程序的时候,控制权是先交到解释器,由解释器加载动态库,然后控制权才会到用户程序。而动态库的加载过程,大致的过程就是将每一个依赖的动态库都加载到内存,并形成一个链表,后面的符号解析、地址重定向过程主要就是在这个链表中搜索符号的定义。
动态链接
一、动态链接的基本思想是把程序按照模块拆分成各个相对独立部分,在程序运行时才将它们链接在一起形成一个完整的程序,而不是像静态链接一样把所有程序模块都链接成一个单独的可执行文件。
二、动态链接的优点显而易见,就是即使需要每个程序都依赖同一个库,但是该库不会像静态链接那样在内存中存在多分,副本,而是这多个程序在执行时共享同一份副本;另一个优点是,更新也比较方便,更新时只需要替换原来的目标文件,而无需将所有的程序再重新链接一遍。当程序下一次运行时,新版本的目标文件会被自动加载到内存并且链接起来,程序就完成了升级的目标。但是动态链接也是有缺点的,因为把链接推迟到了程序运行时,所以每次执行程序都需要进行链接,所以性能会有一定损失。
据估算,动态链接和静态链接相比,性能损失大约在5%以下。经过实践证明,这点性能损失用来换区程序在空间上的节省和程序构建和升级时的灵活性是值得的。
三、动态链接是在程序装载的时候进行符号表的重定位的。
四、动态链接时的函数覆盖规则:先加载的函数覆盖后加载的通类型函数。
PLT延迟加载技术
针对动态链接慢的原因,提出了延迟绑定(Lazy Binding)的要求:即函数第一次被用到时才进行绑定。通过延迟绑定大大加快了程序的启动速度。而 ELF 则使用了PLT(Procedure Linkage Table,过程链接表)的技术来实现延迟绑定。
address hook技术
一、什么是address hook技术?与inline hook技术有什么区别?
二、address hook与inline hook的实现方式:ptrace调试技术(linux gdb的实现方式)
三、LD_PRELOAD的作用(动态打桩技术)
四、-finstrument-functions、-pg(mcount), 静态打桩技术
C语言运行启动过程
参考文献
《程序员的自我修养-动态链接与库》
重定位标志说明:http://bbs.chinaunix.net/thread-4256422-1-1.html
ELF大概说明:https://blog.csdn.net/trochiluses/article/details/10373921