1.程序的构建
在构建程序的时候,链接器都会给程序设置一个默认的加载地址,即首选基地址,它表示该模块被映射到进程地址空间时最佳的内存地址。默认情况下,对于EXE程序而言,windows链接器会将它的首选基地址设置为0X400000(四十万),而DLL程序的首选基地址则被设置为0X10000000(1千万),然后链接器将该地址以及一些相关数据和代码的地址写入到PE文件中。首选基地址的是为了系统程序加载器设计的,作用是告诉加载器把程序优选加载到该首选基地址,然后就可以直接将其他的数据和代码加载到内存中。
由于程序中会有代码和数据,而这些代码所引用的数据地址在程序构建完成后就已经确定了,但是这些数据地址是相对地址,是相对于设置好的首选基地址。对于某个EXE文件,假如代码中有一条指令:
MOV EAX, [0X52050]
假如该EXE被加载到首选基地址(0X400000,一般而言,EXE都会加载的首选基地址的,因为EXE都有自己的独立地址空间),那么该条指定实际上是把该进程空间内的地址为0X52050+0X400000=0X452050处的内容移到寄存器EAX中,但是如果该EXE不是加载到首选基地址,那么该条指令的数据地址就不再是0X452050了,而是EXE实际加载的基地址+0X52050,这个就涉及到程序的加载和地址重定位的问题了。
2.程序的加载
a) 由于每次重定位需要对重定位段中的每一项做重定位操作,需要修改对应的代码,影响程序的初始化效率;b) 当加载程序写入模块的代码页面时,由于它们具有写时复制属性,写时复制机制会导致系统从页交换文件中分配空间来容纳这些修改后的页面。
对于EXE文件而言,系统首先给该EXE创建进程空间,然后将该EXE映射到内存中,接着检查该EXE的导入段,把需要加载的DLL加载过来,在加载DLL的时候检查该DLL的导入段,将DLL对应的附加DLL也加载过来,当所有的DLL加载完毕后需要修复EXE的导入函数表。此时,检查对应DLL的实际记载的基地址和对于函数的相对偏移地址(RVA),计算出函数在进程内的虚拟地址(VA)=基地址+RVA,然后用该地址修复对应的导入符号的引用即可。
所谓的模块绑定就是说在运行之前,所有导入符号在进程地址空间的地址已经获得,不需要加载时在计算出来这节省初始化时间,另外将导入符号的虚拟地址写入exe模块的导入段,也会由于写时复制机制将要修改的页面以系统页交换文件为后备存储器。这会遇到与基址重定位相似的问题。所以模块绑定对提高系统性能的提高是显著的。
对于DLL而言,因为其一般是加载到其他进程空间的,如果加载到目标进程空间内不需要重定位,那么其加载过程和EXE的加载类似,如果需要重定位,则首选需要对模块做重定位,然后再映射到目标进程内,其后续的过程也和EXE的加载类似。
3.VS工具
a) 它会模拟创建一个地址空间。b) 它会打开这一组映像文件,并得到它们的大小和首选基地址。c) 它会在模拟的地址空间对模块重定位的过程进行模拟,以便各模块没有交叉。d) 对于每个需要重定位的模块,它会解析模块的重定位段,并修改模块在磁盘文件中的代码。e) 将每个模块新的首选基地址写入各个模块磁盘文件中。
a) 它会打开模块的导入段。b) 对导入段列出的每个DLL,它会检查该DLL文件的文件头,来确定该DLL的首选基地址。c) 它会在DLL的导出段查看每个符号。d) 取得符号的RAV,并将其与模块的首选基地址相加。得到导入符号的虚拟地址(VA)。e) 在映像文件的导入段中添加额外信息。这些信息包括映像文件被绑定的各DLL的名称,以及各模块的时间戳。
因为该工具假设DLL会加载到首选基地址,所以如果程序在加载时出现了地址重定位问题,那么模块绑定是无效的,该工具有效的时候需要满足的条件是:
a) 进程初始化时所需的DLL都被映射到了它们的首选基地址。b) 绑定完成之后,DLL导出段所列出的符号的位置没有发生改变。这可以通过检查每个DLL的时间戳来保证。
如果上述假设有一个不成立。加载程序必须像绑定之前一样,手动修正可执行文件导入段。如果都成立加载程序就可以不用做这些工作了。