二进制程序中的内存地址如何在运行时指向内存中的正确位置?

How do memory addresses in binary programs point to the right place in memory at runtime?

据我了解,当您编译程序(例如 C 程序)时,编译器会获取您的代码并以二进制(即目标架构的机器代码)格式输出可执行程序。

在此二进制文件中,您将拥有指向内存中地址的指令,以便从程序的其他部分加载 data/instructions。

鉴于此程序将在某个任意位置加载到内存中,程序如何知道这些内存地址是什么?他们怎么样 set/calculated 是谁的工作?

例如,当二进制文件首次加载到内存中时,是否只有内存位置的占位符被 OS 替换?

如果它需要动态加载一个共享库,它如何确定内存位置?

'virtual memory' 如何发挥作用? (如果有的话)

how does the program know what these memory addresses are?

程序(及其作者)不知道什么当它加载到计算机内存时内存地址是什么,它只知道哪里 占位符是相对于其段的开头。这就是编译器为每个这样的占位符附带 重定位记录 的原因。重定位是一条信息,它告诉 OS 或 linker

  1. 重定位地址所在的位置(它在代码或数据段中的偏移量)
  2. 在哪个段
  3. 它指的是哪个段或符号
  4. 该地址应该申请什么样的搬迁

考虑以下 Windows 可移植可执行程序的简单片段或源代码:

[.text]
Main:NOP
     LEA ESI,[Mem]
     ; more instructions 
[.data]
     DB "Some data"
Mem: DB "Other data"

将转换为机器指令和内存数据:

|[.text]                   |[.text]
|00000000:90               |Main:NOP
|00000001:8D35[09000000]   |     LEA ESI,[Mem]
|00000007:                 |     ; more instructions
|[.data]                   |[.data]
|00000000:536F6D6520646174~|     DB "Some data"
|00000009:4F74686572206461~|Mem: DB "Other data"

编译器不知道Mem的虚拟地址,它只知道它位于.data段开始的0x00000009字节处,所以它会把这个临时号进入 LEA ESI,[Mem] 的操作代码并创建相对于段 .data 的占位符重定位(位于段 .text 中的偏移量 0x00000003)。

在 link 时,linker 决定 .text 段将加载到虚拟地址 0x00401000.data 段,位于 VA 0x00402000。然后链接器读取重定位记录并通过添加 0x00402000 来修改占位符。 linked 可执行文件中的指令 LEA ESI,[Mem] 将是 8D3509204000,这是 Mem 的最终固定虚拟地址。我们将能够在 运行 时在调试器中看到该地址。

重定位也存在于 linked 可执行文件中(16 位 DOS MZ 或 Windows PE),以防它们无法在虚拟映像库中加载地址假定为 link 时间。使用 Linux 中的 linking SO 库会更复杂,请参阅 http://www.skyfree.org/linux/references/ELF_Format.pdf[=29= 中的 2 动态 linking 章]

MMU 允许 OS 为每个应用程序创建相同的地址 space(认为地址为 0 到 N),这样每个应用程序都可以针对已知地址 space 进行编译。这种情况下搬家的必要性不大。即使在 DOS 天你 could/would 相对于某些数据段有固定的偏移量,因此应用程序可以有一个假设的地址 space.

Linux 的内核 bootstrap 是一个你会看到重定位的地方,但内核本身并没有那么多,或者可能在过去这么多年里发生了变化。

可加载模块和共享库是您可能需要重新定位的地方。至少对于流行的处理器 运行 流行的操作系统(Linux、Windows、macOS、arm、x86、mips)代码本身可以构建为 reloca table 无需修改,只要它是相对于自身的,这是假定的。

相对于代码的数据,但如果你想移动数据,那么某种形式的 table 是典型的,其中 table 相对于代码(或其他一些链接机制)是固定的,但它包含告诉数据从哪里开始的信息,或者数据开始中的特定 items/markers,以便其他数据引用可以与之相关。