例如在我的程序中,我调用了函数foo()。编译器和汇编器最终会在二进制文件中写入jmp someaddr
。我知道虚拟内存的概念。程序会认为它有整个记忆,起始位置是0x000
。通过这种方式,汇编程序可以计算foo()的位置。
但实际上直到运行时才决定这个?我必须运行程序才能知道我将程序加载到哪里,因此jmp
的地址。但是当程序实际运行时,操作系统如何进入并更改jmp
的地址?这些是直接CPU指令吗?
答案 0 :(得分:6)
这个问题一般无法回答,因为它完全取决于硬件和操作系统。然而,典型的答案是初始加载的程序可以按照您的说法进行编译:因为VM硬件为每个程序提供了自己的地址空间,所以在程序链接时可以修复所有地址。不需要在加载时重新计算地址。
动态加载库的事情变得更加有趣,因为同一个最初加载的程序使用的两个可能使用相同的基址编译,因此它们的地址空间重叠。
解决此问题的一种方法是在DLL中要求位置无关代码。在这样的代码中,所有地址都与代码本身有关。跳转通常是相对于PC的(尽管也可以使用代码段寄存器)。数据也与某些数据段或基址寄存器有关。要选择运行时位置,PIC代码本身不需要更改。只要在每个DLL例程的前奏中都只需要设置段或基址寄存器。
PIC往往比位置相关代码慢一点,因为有额外的地址算法,PC和/或基址寄存器可以阻塞处理器的指令流水线。
所以另一种方法是让加载器在必要时重新绑定DLL代码以消除地址空间重叠。为此,DLL必须包含代码中所有绝对地址的表。加载程序计算假定代码和数据库地址与实际之间的偏移量,然后遍历表,在程序复制到VM时将偏移量添加到每个绝对地址。
DLL还有一个入口点表,以便调用程序知道库过程的起始位置。这些也必须调整。
重新定位对性能也不是很好。它减慢了装载速度。而且,它会破坏DLL代码的共享。每个rebase偏移量至少需要一个副本。
由于这些原因,作为Windows一部分的DLL是故意使用非重叠的VM地址空间编译的。这加快了加载速度并允许共享。如果您注意到第三方DLL会破坏磁盘并缓慢加载,而像C运行时库这样的MS DLL会快速加载,那么您将看到Windows中的重新定位效果。
您可以通过阅读目标文件格式来推断有关此主题的更多信息。 Here is one example.
答案 1 :(得分:2)
与位置无关的代码是可以从任何地址运行的代码。如果在位置无关代码中有jmp
指令,则它通常是相对跳转,跳转到当前位置的偏移量。复制代码时,它不会更改代码部分之间的偏移,因此它仍然有效。
可重定位代码是可以从任何地址运行的代码,但您可能必须先修改代码(也许您不能只复制它)。代码将包含一个重定位表,告诉它如何修改它。
不可重定位代码是必须在某个地址加载的代码,否则无效。
每个程序都不同,这取决于程序的编写方式,编译器设置或其他各种因素。
共享库通常被编译为与位置无关的代码,它允许在不同进程中的不同位置加载相同的库,而无需将多个副本加载到内存中。可以在进程之间共享相同的副本,即使它在每个进程中位于不同的地址。
可执行文件通常是不可重定位的,但它们可以与位置无关。虚拟内存允许每个程序拥有自身的整个地址空间(减去一些开销),因此每个可执行文件都可以选择加载它的地址,而不必担心与其他可执行文件的冲突。一些可执行文件与位置无关,可用于提高安全性(ASLR)。
对象文件和静态库通常是可重定位的代码。链接器在组合它们时将重新定位它们以创建共享库,可执行文件或其他映像。
引导加载程序和操作系统内核几乎总是不可重定位。
答案 2 :(得分:0)
是的,它是在运行时。操作系统,管理启动和切换任务的部分理想地处于不同的保护级别,它具有更多的功率。它知道正在使用的内存并为新任务分配一些内存。它配置mmu,以便新任务具有从零开始的虚拟地址空间或该操作系统和处理器的任何规则。如何在该起始地址进入用户模式,是非常特定于处理器的。
例如,一种方法是硬件可能会保存某些状态,而不仅仅是地址,而是模式或虚拟ID,或者在发生中断时保存的东西,比如堆栈。并且由该处理器定义的来自中断指令的返回将地址和状态/模式从堆栈中取出并在那里切换(导致让我们假设mmu基于新模式而不是旧模式对其下一次获取作出反应)。对于那样工作的处理器,你可以通过在堆栈上放置正确的项来伪造中断返回,这样当你踢中断返回指令时,它基本上会跳转并具有模式切换等附加功能。
例如,ARM系列(不是cortex-m)有一个处理器状态寄存器,用于您现在正在运行的内容(在中断或服务调用的情况下)和第二个状态寄存器,用于您来自哪里,状态被中断,当你做正确的返回时,你给它地址,然后使用另一个寄存器切换回该模式。您可以从非用户模式直接访问该寄存器,以便您可以操纵返回的状态。手臂上没有返回指令,只是跳转的味道(程序计数器的修改),所以它是一种特殊的跳跃。简短的回答是,在虚拟地址空间中,在应用程序模式下,任务切换到正在运行的任务后,处理器对于跳转到第一次或返回时的选择非常具体。直接或间接处理器文档将描述这些模式以及如何更改它们。如果没有明确描述,那么你必须自己从指令和mmu保护以及如何切换任务中找出答案。