例如,在我的程序中,我调用了函数foo().编译器和汇编器最终会写入jmp someaddr二进制文件.我知道虚拟内存的概念.该程序会认为它具有整个记忆,并且起始位置是0x000.通过这种方式,汇编程序可以计算foo()的位置.
但实际上直到运行时才决定这个?我必须运行该程序才能知道我将程序加载到哪里,因此该地址jmp.但是当程序实际运行时,操作系统是如何进入并更改地址的jmp?这些是直接的CPU指令吗?
这个问题一般无法回答,因为它完全取决于硬件和操作系统.然而,典型的答案是初始加载的程序可以按照您的说法进行编译:因为VM硬件为每个程序提供了自己的地址空间,所以在程序链接时可以修复所有地址.不需要在加载时重新计算地址.
动态加载库的事情变得更加有趣,因为同一个最初加载的程序使用的两个可能使用相同的基址编译,因此它们的地址空间重叠.
解决此问题的一种方法是在DLL中要求位置无关代码.在这样的代码中,所有地址都与代码本身有关.跳转通常是相对于PC的(尽管也可以使用代码段寄存器).数据也与某些数据段或基址寄存器有关.要选择运行时位置,PIC代码本身不需要更改.在每个DLL例程的前奏中,只需要设置段或基址寄存器.
PIC往往比位置相关代码慢一点,因为有额外的地址算法,PC和/或基址寄存器可以阻塞处理器的指令流水线.
所以另一种方法是让加载器在必要时重新绑定DLL代码以消除地址空间重叠.为此,DLL必须包含代码中所有绝对地址的表.加载程序计算假定代码和数据库地址与实际之间的偏移量,然后遍历表,在程序复制到VM时将偏移量添加到每个绝对地址.
DLL还有一个入口点表,以便调用程序知道库过程的起始位置.这些也必须调整.
重新定位对性能也不是很好.它减慢了装载速度.而且,它会破坏DLL代码的共享.每个rebase偏移量至少需要一个副本.
由于这些原因,作为Windows一部分的DLL是故意使用非重叠的VM地址空间编译的.这加快了加载速度并允许共享.如果您注意到第三方DLL压缩磁盘并缓慢加载,而像C运行时库这样的MS DLL加载速度很快,那么您将看到Windows中的rebase的影响.
您可以通过阅读目标文件格式来推断有关此主题的更多信息. 这是一个例子.
| 归档时间: |
|
| 查看次数: |
303 次 |
| 最近记录: |