原创 C++应用程序在Windows下的编译、连接(四)动态连接

4动态连接

4.1概述

在静态连接阶段,连接器为PE文件生成了导入表,导出表,符号表,并调整了Call指令后面的操做数,在程序调用的时候,可以直接地或者间接地定位到IAT中的某个位置,在PE文件中,该位置包含符号的名称,当PE文件加载到内存之后,该位置应该修正为符号的地址。这些已有的信息和已经完成的工做是后续动态连接的基础。程序员

动态连接的任务是:在程序的加载或者运行阶段,执行各个模块的基址重定位工做,并将IAT中的符号名称修正为动态连接库中被调用的符号的地址。windows

动态连接分为隐式动态连接和显式动态连接,不管是隐式动态连接仍是显式动态连接,都会涉及到对WindowsAPI函数:LoadLibrary(),GetProcAddress(),FreeLibrary()的调用。它们之间的区别是:在执行隐式动态连接的时候,由Windows加载器负责完成对这些Windows API的调用;而在显式动态连接的时候,这些Windows API函数必须由程序员编写代码主动地调用。数组

隐式动态连接在程序加载到内存的时候完成,而显式动态连接则将这一过程推迟到程序运行的过程当中。数据结构

动态连接的流程以下图所示:函数

由上图能够看出,动态连接的两个主要任务:动态连接库加载完毕之后的基址重定位,以及对导入表中函数名称的修正,即:将函数名称转换成函数的地址。布局

4.2程序加载及基址重定位

PE文件具备段结构,包含的主要的段有:代码段,数据段,导入表,导出表,符号表,基址重定位表等。当PE文件存储在文件中或者被加载到内存中的时候,这些段都须要遵循某个对齐规则。操作系统

PE中规定了三类对齐:数据在内存中的对齐,数据在文件中的对齐,资源数据在资源文件中的对齐。调试

数据在内存中的对齐。在内存中,PE文件之内存页的大小做为对齐粒度。在Windows操做系统中,内存以分页的方式进行管理。在32位Windows下,内存页的大小是4KB;在64位Windows下,内存页的大小是8KB。当PE文件被加载到内存之后,各段的起始地址必须是内存页大小的整数倍。blog

数据在文件中的对齐。在文件中,PE文件以一个扇区的大小做为对齐粒度。一个扇区的大小为512字节,十六进制表示为200h。在文件存储中,各段的地址偏移必须是200h的整数倍。内存

资源数据在资源文件中的对齐。在资源文件中,资源字节码以4字节做为对齐粒度。

因为在内存中PE文件以4KB做为对齐粒度,而在文件中以512字节做为对齐粒度,所以当PE文件被加载到内存之后,PE文件的尺寸要大于该文件在硬盘上的尺寸。在执行加载的时候,操做系统会读取PE文件的头信息,去除不须要加载到内存的部分,如:调试信息等,而后操做系统将整个PE文件映射到内存空间中。在将PE文件加载到内存之后,PE文件的结构和布局不会被改变。所以,PE文件在硬盘上的数据结构与在内存中的数据结构是相同的。具体的对比状况以下图所示:

当PE文件被windows加载器加载到内存之后,在内存中的版本被称为模块,每个模块的起始内存地址被称为HMODULE。经过这个HMODULE能够得到该模块的数据内容。

当可执行程序被加载到内存之后,Windows会查询PE文件中的相关信息,得到该可执行程序所依赖的动态连接库,而后Windows将这些动态连接库也加载到内存中。若是某个要被加载地动态连接库已经位于内存中,那么操做系统就增长这个动态连接库的引用计数。当可执行程序和它所依赖的动态连接库都被加载到内存之后,Windows加载器开始执行基址重定位工做。Windows加载器加载可执行程序以及动态连接库的流程以下图所示:

当可执行文件以及它所依赖的动态连接库文件被加载到内存之后,若是该文件被加载的内存位置不是基于默认内存地址的位置(可执行程序是0x00400000h,动态连接库是0x10000000),那么windows加载器就必须执行基址重定位工做。该工做是在基址重定位表的支持下完成的。对于每个须要进行重定位的内存地址,加载器都会给它加上一个差值做为修正。该差值为模块当前加载到内存的地址 – 模块默认加载位置的地址。具体的操做流程以下图所示:

当完成基址重定位工做之后,导出地址表(EAT)中的地址也被修正完毕。在PE文件中,这些地址是基于默认加载位置的地址,若是当前加载位置发生了变化,那么这些地址是要被修正的。参见2.4.7节。

4.3符号解析

符号解析是动态连接中最重要的一环,在该阶段,加载器读取PE文件中的导入地址表(IAT)的信息,取得每个函数的名称,而后用该函数的名称去相关动态连接库中查询函数的地址,用得到的地址替换该函数名称。这一步工做是将函数名称替换为函数地址的过程。具体过程以下图所示:

因为一个可执行程序可能会依赖多个动态连接库,那么在导入表数组中就会包含多个数组元素,因此在符号解析的时候是一个循环处理。加载器对导入表数组执行循环,取得每个数组元素,而后根据得到的Dll名称查找到相关的动态连接库的位置。得到了相关动态连接库的信息之后,加载器从导入地址表中取得一个符号的名称,以该符号名称为条件去对应的动态连接库中查询。通过对符号名称表,名称序号对应表,以及符号地址表的查询,最后得到符号的地址,将此地址写回到导入地址表原来符号名称的位置。

    嗯,那个恭喜你,看完了。为了写这个东西,也把我累的够呛,哈哈。

相关文章
相关标签/搜索