[函数调用:标签到内存地址

问题描述 投票:0回答:1

我很难理解事件的正确顺序。编译以抽象语言编写的程序时,会将其翻译为机器代码。随后,仅在程序运行之后,才将其加载到代码段中的ram中。此时,程序中的每条指令将位于特定的存储器地址上。在汇编中调用函数时,通常在Call语句后跟一个标签。我假设此标签将由编译器替换为函数的内存地址。这是我绝对无法理解的地方。如果仅在程序运行时将指令加载到内存中,从而为每个指令获取自己的内存地址,那么编译器如何知道标签所对应的内存地址?如果该函数还没有存储在内存中,那么该程序如何以不再可用标签的二进制代码编译,知道与该标签相对应的内存地址,函数将在执行时加载到该地址?我有点困惑。帮帮我。

function assembly call
1个回答
0
投票

程序包含几个“部分”(某些是可选的:):

  • 保存代码的部分,通常称为文本部分
  • 保存可变全局数据的初始值的部分
  • 保存不变常量的部分,通常称为rodata
  • 具有一组重定位记录的部分

部分在磁盘上的程序文件中存储为连续的块或内存块。

加载器创建内存块并将代码,数据,rodata加载到其中;取决于os,将由加载程序创建堆栈,也可能由创建子进程的父进程派生创建堆栈。]

知道最终地址后,加载程序还会处理重定位记录。这些重定位描述了在文本和数据部分中,最终加载的地址需要更新的地方。

重定位机制是通用的,因为代码可以引用代码,代码可以引用数据,数据可以引用代码,数据可以引用数据。

单个重定位记录描述了需要更新的参考。每条记录描述:

  1. 引荐来源-在文本或数据部分以什么偏移量进行地址更新
  2. 引用目标-引用哪个部分:代码或数据
  3. 要进行哪种更新(某些体系结构具有复杂的指令编码)
  4. 通常,引荐来源网址已具有偏移量,因此更新是要添加所引用节的基数的问题。

程序中的其他元数据描述了从何处开始,例如初始的程序计数器,将作为文本部分的偏移量。

当今大多数处理器都支持(如fuz所描述的)位置无关代码。通常,这是通过pc相对寻址完成的。处理器使用pc相对寻址模式在单个文本部分内执行分支和调用,因此这些指令不需要重定位记录。

动态加载的库增加了复杂性,因为每个DLL和要运行的主程序各自具有程序的格式,即它们各自将具有自己的部分,即各自具有其自己的文本部分。重定位还将能够描述对符号导入的引用,并得到包含符号名称,导入和导出的其他部分的支持。

目标文件(编译器输出,预链接)通常也遵循这种格式。单个目标文件包含这些部分,以及重定位记录,符号名称,导入和导出。链接器的工作是将目标文件合并到单个程序或更大的目标文件中。&nbsp在合并过程中,链接器可解析某些重定位,但无法解析所有重定位,因此仍有一些重定位。

© www.soinside.com 2019 - 2024. All rights reserved.