我很难理解事件的正确顺序。编译以抽象语言编写的程序时,会将其翻译为机器代码。随后,仅在程序运行之后,才将其加载到代码段中的ram中。此时,程序中的每条指令将位于特定的存储器地址上。在汇编中调用函数时,通常在Call语句后跟一个标签。我假设此标签将由编译器替换为函数的内存地址。这是我绝对无法理解的地方。如果仅在程序运行时将指令加载到内存中,从而为每个指令获取自己的内存地址,那么编译器如何知道标签所对应的内存地址?如果该函数还没有存储在内存中,那么该程序如何以不再可用标签的二进制代码编译,知道与该标签相对应的内存地址,函数将在执行时加载到该地址?我有点困惑。帮帮我。
程序包含几个“部分”(某些是可选的:):
部分在磁盘上的程序文件中存储为连续的块或内存块。
加载器创建内存块并将代码,数据,rodata加载到其中;取决于os,将由加载程序创建堆栈,也可能由创建子进程的父进程派生创建堆栈。]
知道最终地址后,加载程序还会处理重定位记录。这些重定位描述了在文本和数据部分中,最终加载的地址需要更新的地方。
重定位机制是通用的,因为代码可以引用代码,代码可以引用数据,数据可以引用代码,数据可以引用数据。
单个重定位记录描述了需要更新的参考。每条记录描述:
通常,引荐来源网址已具有偏移量,因此更新是要添加所引用节的基数的问题。
程序中的其他元数据描述了从何处开始,例如初始的程序计数器,将作为文本部分的偏移量。
当今大多数处理器都支持(如fuz所描述的)位置无关代码。通常,这是通过pc相对寻址完成的。处理器使用pc相对寻址模式在单个文本部分内执行分支和调用,因此这些指令不需要重定位记录。
动态加载的库增加了复杂性,因为每个DLL和要运行的主程序各自具有程序的格式,即它们各自将具有自己的部分,即各自具有其自己的文本部分。重定位还将能够描述对符号导入的引用,并得到包含符号名称,导入和导出的其他部分的支持。
目标文件(编译器输出,预链接)通常也遵循这种格式。单个目标文件包含这些部分,以及重定位记录,符号名称,导入和导出。链接器的工作是将目标文件合并到单个程序或更大的目标文件中。&nbsp在合并过程中,链接器可解析某些重定位,但无法解析所有重定位,因此仍有一些重定位。