1

例如在我的程序中,我调用了一个函数 foo()。编译器和汇编器最终将写入jmp someaddr二进制文件。我知道虚拟内存的概念。程序会认为它拥有整个内存可供使用,并且起始位置是0x000. 通过这种方式,汇编器可以计算出 foo() 的位置。

但实际上这直到运行时才决定,对吧?我必须运行程序才能知道我将程序加载到哪里,因此jmp. 但是当程序实际运行时,操作系统是如何进来改变的jmp?这些是直接的CPU指令对吗?

4

3 回答 3

6

这个问题一般无法回答,因为它完全取决于硬件和操作系统。然而,一个典型的答案是,最初加载的程序可以按照您的说法进行编译:因为 VM 硬件为每个程序提供了自己的地址空间,所以当程序链接时,所有地址都可以固定。加载时无需重新计算地址。

动态加载的库变得更有趣,因为同一个初始加载的程序使用的两个库可能使用相同的基地址编译,因此它们的地址空间重叠。

解决此问题的一种方法是在 DLL 中要求位置独立代码。在这样的代码中,所有地址都与代码本身相关。跳转通常与 PC 相关(尽管也可以使用代码段寄存器)。数据也与某些数据段或基址寄存器相关。要选择运行时位置,PIC 代码本身不需要更改。在每个 DLL 例程的前奏中,只需要设置段或基址寄存器。

PIC 往往比位置相关代码慢一点,因为有额外的地址算术,并且 PC 和/或基址寄存器可能会成为处理器指令流水线的瓶颈。

因此,另一种方法是让加载程序在必要时对 DLL 代码进行 rebase,以消除地址空间重叠。为此,DLL 必须包含代码中所有绝对地址的表。加载程序计算假定的代码和数据库基地址与实际之间的偏移量,然后遍历表,在程序复制到 VM 时将偏移量添加到每个绝对地址。

DLL 也有一个入口点表,以便调用程序知道库过程从哪里开始。这些也必须调整。

变基对性能也不是很好。它会减慢加载速度。此外,它破坏了 DLL 代码的共享。每个变基偏移量至少需要一个副本。

由于这些原因,作为 Windows 一部分的 DLL 故意使用不重叠的 VM 地址空间进行编译。这可以加快加载速度并允许共享。如果您注意到第 3 方 DLL 会压缩磁盘并缓慢加载,而像 C 运行时库这样的 MS DLL 加载速度很快,您就会看到 Windows 中变基的效果。

您可以通过阅读有关目标文件格式的信息来推断有关此主题的更多信息。 这是一个例子。

于 2013-10-05T04:40:07.393 回答
2

与位置无关的代码是可以从任何地址运行的代码。如果你有一个jmp位置无关代码中的指令,它通常会是一个相对跳转,它跳转到与当前位置的偏移量。当您复制代码时,它不会更改代码部分之间的偏移量,因此它仍然可以工作。

可重定位代码是您可以从任何地址运行的代码,但您可能必须先修改代码(也许您不能只是复制它)。该代码将包含一个重定位表,该表告诉它需要如何修改。

不可重定位代码是必须在某个地址加载的代码,否则它将无法工作。

每个程序都是不同的,这取决于程序的编写方式、编译器设置或其他各种因素。

  • 共享库通常被编译为与位置无关的代码,它允许在不同进程中将同一个库加载到不同位置,而不必将多个副本加载到内存中。相同的副本可以在进程之间共享,即使它在每个进程中位于不同的地址。

  • 可执行文件通常是不可重定位的,但它们可以与位置无关。虚拟内存允许每个程序拥有自己的整个地址空间(减去一些开销),因此每个可执行文件都可以选择加载它的地址,而不必担心与其他可执行文件发生冲突。一些可执行文件与位置无关,可用于提高安全性 (ASLR)。

  • 目标文件和静态库通常是可重定位代码。当组合它们以创建共享库、可执行文件或其他映像时,链接器将重新定位它们。

  • 引导加载程序和操作系统内核几乎总是不可重定位的。

于 2013-10-05T04:42:51.123 回答
0

是的,它在运行时。操作系统,管理启动和切换任务的部分理想地处于不同的保护级别,它具有更大的功能。它知道正在使用的内存并为新任务分配一些内存。它配置 mmu,以便新任务具有从零开始的虚拟地址空间,或者该操作系统和处理器的任何规则。您如何在该起始地址进入用户模式,是非常特定于处理器的。

例如,一种方法是硬件可能会保存一些状态,不仅是地址,还有模式或虚拟 ID 或在发生中断时保存的东西,比如说在堆栈上。并且从该处理器定义的中断指令的返回将地址和状态/模式从堆栈中取出并在那里切换(让我们假设 mmu 根据新模式而不是旧模式对其下一次取指作出反应)。对于像这样工作的处理器,您可以通过将正确的项目放在堆栈上来伪造中断返回,这样当您启动中断返回指令时,它基本上会执行带有模式切换等附加功能的跳转。

例如 ARM 系列(不是 cortex-m)有一个处理器状态寄存器用于您现在正在运行的内容(在中断或服务调用的情况下)和第二个状态寄存器用于您来自哪里,被中断的状态,当你做正确的返回时,你给它一个地址,它使用另一个寄存器切换回那个模式。您可以从非用户模式直接访问该寄存器,以便您可以操纵返回的状态。arm 中没有返回指令,只有跳转的味道(对程序计数器的修改),所以它是一种特殊的跳转。

简短的回答是,对于您的选择是第一次跳转或在任务切换到虚拟地址空间中的应用程序模式中正在运行的任务后返回到什么,这对于处理器来说是非常具体的。处理器文档将直接或间接地描述这些模式以及如何更改它们。如果没有明确描述,那么您必须根据说明和 mmu 保护以及如何切换任务等方式自行弄清楚。

于 2013-10-05T04:43:12.950 回答