二进制程序中的内存地址如何在运行时指向内存中的正确位置?
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
- 重定位地址所在的位置(它在代码或数据段中的偏移量)
- 在哪个段
- 它指的是哪个段或符号
- 该地址应该申请什么样的搬迁
考虑以下 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,以便其他数据引用可以与之相关。
据我了解,当您编译程序(例如 C 程序)时,编译器会获取您的代码并以二进制(即目标架构的机器代码)格式输出可执行程序。
在此二进制文件中,您将拥有指向内存中地址的指令,以便从程序的其他部分加载 data/instructions。
鉴于此程序将在某个任意位置加载到内存中,程序如何知道这些内存地址是什么?他们怎么样 set/calculated 是谁的工作?
例如,当二进制文件首次加载到内存中时,是否只有内存位置的占位符被 OS 替换?
如果它需要动态加载一个共享库,它如何确定内存位置?
'virtual memory' 如何发挥作用? (如果有的话)
how does the program know what these memory addresses are?
程序(及其作者)不知道什么当它加载到计算机内存时内存地址是什么,它只知道哪里 占位符是相对于其段的开头。这就是编译器为每个这样的占位符附带 重定位记录 的原因。重定位是一条信息,它告诉 OS 或 linker
- 重定位地址所在的位置(它在代码或数据段中的偏移量)
- 在哪个段
- 它指的是哪个段或符号
- 该地址应该申请什么样的搬迁
考虑以下 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,以便其他数据引用可以与之相关。