因为工作原因,很久没有写博客,近期初略看了一个《链接装载与库》,书写得很好,但因本人功力有现,好些地方还是不明白。做个小结先。相信以后再读此书会有更多收获。
第二章 编译和链接
从原文件到可执行文件可心为为几部:预编译,编译,汇编,静态链接。
预编译:由预编译器完成。主要处理代码中以“#”开始的预编译指令。比如“include”"define"等,删除所有注释,插入文件标识。(生成.i文件)
编译:对预处理完的文件进行词法,语法,语义分析及优化,生成相应的汇编代码文件(生成.cod,.s文件)。
汇编:将汇编代码转变为机器可执行的指令。(生成目标文件.obj,.o文件)
链接:把目标文件组装的过各就是链接。主要的工作是:地址空间分配,符号决议和重定位。
库的本质:他是一组打包存放的目标文件。
重定位:地址修正的过程也叫重定位,每个要修正的地方叫重定位入口。
每三章 目标文件里有什么
文件格式
OMF-对象模型文件(Object Module File)
OMF是一大群IT巨头在n年制定的一种格式,在Windows平台上很常见。大家喜欢的Borland公司现在使用的目标文件就是这种格式。
COFF – 通用对象文件格式(Common Object File Format)
MS和Intel在n年前用的也是OMF格式,现在都改投异侧,用COFF格式了。
1.PE(Portable Executable)格式,是微软Win32环境可执行文件的标准格式。
2.ELF-可执行及连接文件格式(Executable and Linking Format)
ELF格式,是linux通用格式,在非Windows平台上使用得比较多,在Windows平台基本上没见过。
其它格式:Unix a.out和MS-Dos.COM格式。
这些格式文件归类
1.可重定位文件。
用途:连接成为执行文件或共享目标文件
实例:Linux的.o,Windows的.obj
2.可执行文件(一般没有文件名)
3.共享目标文件
用途:1.作为可执行文件一部分运行生成可执行文件。2生成新的目标文件。
例:linus .so, windows dll
4.核心转储文件(略)
用于保存运行信息。Linux 下core dump
(目的文件,动态静态链接库,可执行文件都按可执行文件格式存储)
目标文件中的段:
ELF Header:描述文件的基本属性,如程序入口,文件版本等。(正是这样,病毒可能修改程序的入口地址)
Section Table:描述各个段的信息,如段句,升序,在文件中的偏移,读写权限等。
.test:指信段
.data段:已经初始化了的全局中静态变量和局部静态变量
.bss段:未初始化的全局变量和局部静态变量。
.rodata段: 存放只读据,const修饰的变量和字符串常量
.rel.text段:重定位表。如对printf的调用。在链接时使用重定位表对地址进行修正。
符号表:函数和变量统称符号,函数或变量名就是符号名。符号值就是函数或变量的地址。特殊符号:_executable_start,_etext或 _etext或etext,_edata或edata,_end或end
字符串表
段表字符串表
.comment:编译器版本信息
符号修饰与函数签名
即函数的名称修改方法,用于识别不用的函数。C++为了实现以C的兼容,使用符号"extern "C""声明的符号会按C的方式进行符号修饰。
弱符号与强符号
规则:1.强符号不允许多次定义。2.如果一个符号在霜冻个目标文件中是强符号,在其它中是弱符号,那么选择强符号。3.如果都是弱符号,那么选择空间最大的一个(这样可能产生调用过程中的问题)。
弱引用与强引用
在链接时,没有找到符号定义就报错,称为强引用。
第四章 静态链接
连接器一般采用“两步链接”的方法进行链接。第一步进行空间和地址分配,第二步进行符号解析和重定位。
空间和地址分配
空间分配:这里的空间分配只关心虚拟地址的空间分配,而不关心可执行文件的空间分配。空间分配采用相似段合并的方法,合 并后某个段内部的布局是有输入文件的顺序决定的,当然收到连接脚本的控制。
地址分配:确定各个段的虚拟地址,包括BSS段。
空间分配需要的信息包括段的长度、属性和位置(偏移)。
空间和地址分配遍结束后:有一个全局符号表,记录所有目标文件的符号信息,包括符号定义和符号引用。合并后各个段的起始地址和长度确定
符号解析与重定位
符号解析:确定各个符号的地址。根据段的地址和符号在段中的偏移以及相同段的合并顺序,可以确定各个符号的准确地址,从而更新符号表。每个要重定位的段都有一个重定位表。所有被引用的符号都不能是未定义的。
重定位:根据各个目标文件的重定位表信息,可以准确的定位文件中哪些地方需要进行重定位。
重定位表的结构如下:
Typedef struct{
Elf32_Addr r_offset; //从定位入口的偏移,相对于作用段的偏移
Elf32_Word r_info; //入口的类型和符号
}Elf32_Rel;
符号解析结束,找到重定位入口,下一步做的就是指令修改。根据指令的寻址方式进行修改。
第六章 可执行文件装载与进程
32位虚拟地址空间0到2的32次方减1(0到0xFFFFFFFF).
从操作系统看可执行文件装载
进程的建立:一个进程的建立最关键的特征是拥有独立的虚拟地址空间。进程的建立分三步:
1 创建独立的虚拟地址空间:创建虚拟地址空间实际上是建立虚拟地址空间到物理空间的映射,在i386的linux实际上就是创建一个页目录,或者称为页表。
2 读取可执行文件头,建立虚拟空间与可执行文件的映射关系。当程序发生页错误时,操作系统将从内存中分配一个物理页,并将该缺页从磁盘读入内存。然后设置页的映射关系。显然,当缺页时,操作系统需要知道程序当前需要的页在磁盘中的哪个位置,此时指令的虚拟地址是知道的,这就需要建立一个虚拟地址到可执行文件之间的映射。这样的一种数据结构称为VMA,它记录了各个段对应的虚拟空间,并记录该段在文件中的偏移。
3 CPU指令寄存器设置成可执行程序入口,启动。
页错误:当CPU执行某条指令,如果该指令所在页是空页,则发生段错误。操作系统捕获,找到该指令所在的VMA,并计算出该页在文件中的偏移,分配一个物理页,将文件内容读入内存,设置虚拟地址也物理地址的映射关系,即页表。将控制权还给程序。
文件的链接视图和执行视图
对于相同的权限的段,合并到一起当作一个段进行映射。称为"Segment",和前面的section不一样。一个是从装的角度看(Segment),一个是链接的角度看(Section)。
段地址对齐
最简单的情况是,各个Segment在物理内存中都分别映射,Segment起始地址都是4096整数倍(每页4K)。这样容易造成物理空间的浪费。
第七章 动态链接
为什么要动态链接
.节省内存和磁盘空间(共享代码,但私用数据会有复本)。2.程序的升级。3.有利于程序的可扩展和兼容性
动态库参与链接
gcc –o Program1 Program1.c ./lib.so
Program1.c引用到lib.so中的foobar()函数,这里foobar定义在共享对象中,连接器会将这个符号的引用标记为一个动态链接的符号,不对其进行重定位,留到装载时再进行。动态库lib.so保存了完整的符号信息,把./lib.so作为连接的输入符号之一,连接器在解析符号时就知道foobar是定义在lib.so中的动态符号。如果foobar是定义在其他目标文件中的函数,链接器会按照静态链接的规则进行重定位。
固定装载地址的困扰
静态共享库:操作系统在某些特定的地区划分一些地址块,为那些已知的模块预留足够的空间。
装载时重定位(任意地址加载,方法一)
装载时重定位是首先想到的解决共享对象任意地址装载问题。在链接时,对所有绝对地址的引用都不尽兴重定位,而是把这一步推迟到装载时进行。
连接时重定位 vs 装载时重定位(基址重置)这种方法对共享对象并不是很合适,因为这种方法需要修改指令,而指令部分被多个进程共享,没法做到一份指令被多个进程共享,因为指令被重定位后对每个进程都是不同地。
但这种方法可以修改共享对象的数据部分,因为数据部分都是进程私有的。
地址无关代码(任意地址加载,方法二)
要想实现指令部分的进程共享,解决共享对象指令中对绝对地址的重定位问题,方法是将指令中那些需要被修改的部分分离出来,放在数据部分。这种方案称为地址无关代码(PIC, Position-Independent Code)技术。
模块中四种类型的地址引用:
(1)模块内部的函数调用、跳转
(2)模块内部的数据访问,全局变量?、静态变量
(3)模块外部的函数调用、跳转
(4)模块外部的数据访问,如其他模块定义的全局变量
延迟绑定
动态链接速度慢的原因有:
(1)对于全局变量和静态数据的访问都要通过GOT表定位,再间接寻址。
(2)对于模块间的调用也要先定位GOT,然后再进行间接跳转。
(3)动态链接的链接工作在运行时完成。
主要的原因是(2)和(3),因为模块间的全局变量访问比较少,多了耦合性就强。
延迟绑定是指函数在第一次用到时才进行绑定,包括符号查找、重定位等。如果没有用到则不进行绑定。这样就节省了(3)的时间。
ELF采用PLT(Procedure Linkage Table)的方法来实现延迟绑定。这种方法是在GOT之上增加一层间接跳转来实现的。每个外部函数在PLT中都有一个对应的项,调用函数并不是直接跳转的GOT中进行定位,而是先跳转到PLT中的对应项,如果是第一次调用该函数,PLT则先进行符号绑定,填充GOT表,再跳转过去。否则直接跳转到GOT进行定位。
第9章 Windows下的动态链接
Windes PE采用了与ELF不同的办法,它采用的是装载时重定位的方法。
第10章 内存
栈向低地址增长,堆向高地址增长,直到预留的空间被用完。
栈
对于windows来说,每个线程默认栈大小是1MB,可以在createThread时指定。
堆栈帧一般包含以下几个内容:
(1)传入的参数
(2)返回的地址
(3)保存的寄存器(上下文)
(4)临时变量
返回值<=4字节,值用eax返回,5-5字节,用eax和edx联合返回(eax低4,edx高4),大于8字节:为返回值开一个临时空间,把地址传给被调用函数,被调用函数为它赋值并用eax返回它的地址。(如果调用函数要用到返回值,就用eax找到临时对象,复制值)
堆
1.malloc是一个运行库的功能,系统“批发”给进程一块较大的空间,程序通过malloc进行内存的管理。在linux上用mmap和windows中的VirtualAlloc相似,向系统申请空间。对为x86来说,申请的大小必须是4096字节的整数倍。
2.winows堆管理函数:heapcreate,heapalloc,heapfree,heapdestroy.每个进程都有一个默认堆,默认大小为1M.
3.堆在内存中是通过空闲链表,位图,对象池等方式组织起来的。
第11章 运行库
入口函数和程序初始化
程序的入口点实际上是一个程序的初始化和结束部分,它往往是运行库的一部分。典型的程序运行步骤:
1.系统创建进程,把控制权交到程序入口,入口往往是运行库的某个入口函数。
2.穰函数对运行库和运行环境进行初始化,包括堆,I/O,线程,全局中变量构造等等。
3.初始完后,调用main,执行程序主体。
4.返回到入口函数,进行清理工作,包括全局变量,堆销毁,关闭I/O,然后系统调用结束进程。
MSVC CRT入口函数
MSVC CRT默认的入口函数名为mainCRTStartup, 它的总体流程是:
1.初始化和OS版本有关的全局变量。
2.初始化堆。alloc.
3.初始化I/O.
4.获取命令行参数和环境变量。
5.初始化C库一些数据。
6.调用main并记录返回值。
7.检查并并main返回值返回。
exit函数和return函数的主要区别是:
1)exit用于在程序运行的过程中随时结束程序,其参数是返回给OS的。也可以这么讲:exit函数是退出应用程序,并将应用程序的一个状态返回给OS,这个状态标识了应用程序的一些运行信息。
main函数结束时也会隐式地调用exit函数,exit函数运行时首先会执行由atexit()函数登记的函数,然后会做一些自身的清理工作,同时刷新所有输出流、关闭所有打开的流并且关闭通过标准I/O函数tmpfile()创建的临时文件。
exit是系统调用级别的,它表示了一个进程的结束,它将删除进程使用的内存空间,同时把错误信息返回父进程。通常情况:exit(0)表示程序正常, exit(1)和exit(-1)表示程序异常退出,exit(2)表示系统找不到指定的文件。在整个程序中,只要调用exit就结束。
运行库与I/O
现代系统系统对系统资源都有严格的控制。以文件操作举例:设置文件句柄可以防止用户写操作系统内核的文件对象,文件句柄总是和内核的文件对象相关联的。内核可以通过句柄来计算出内核文件对象的地址。
在内核中,每一个进程都有一个私有的”打开文件表“,这个表是一个数组,每一个元素都指向一个崔的打开文件对象。而打开文件得到的fd是这个表的下标。(p329)
C/C++语言运行库
支撑c语言运行的一系列函数所构成的集合称为运行时库。C语言运行库大致包含:启动与退出,C标准函数,堆,语言实现调试。
参数格式:int printf(const char* format, ...);
cdecl调用惯例保证参数的正确清除,它是由调用方负责清除堆栈。
局部跳转
C语言中一个争议机制,它可以实现从一个函数休向别一个函数体跳转。(p340)
glibc与MSVC CRT
C语言的运行库是c程序和和不同操作系统平台之间的抽象层,将不同的API抽象成相同的库函数。但象线程和谐这样的功能并不是标准c运行库的一部分,glibc有pthread库的pthread_create创建,而MSVCRT有_beginthread创建。所以事实上他们是标准C语言运行库的超集。
层次关系图(P402)
第12章 系统调用与API
系统调用原理
用户态的程序一般是通过中断来从用户态切换到内核态。windows中的运行库中通过调用windows api实现系统调用,windows API实质上是以DLL函数函数的形式暴露给应用程序开发者的。
SDK是Windows API DLL导出函数的声明头文件、导出库、相关文件和工具的集合。Windows在API之上建立了很多应用模块,如winine.dll。之所以引入API,是为了隔离硬件结构的不同而导致的程序的兼容性问题。