汇编程序在执行地址解析时从哪里开始(就内存地址而言)



我试图理解现代语言的两步汇编程序的逐步分析,并逐行分解这些步骤。

当涉及到两个通道的汇编器时,地址解析在第二个通道上完成。然而,我不确定汇编器在内存中的具体位置。

它只是从生成的符号表中的第一个列表开始吗?或者两次通过实际上意味着两次完整的代码读取(从一开始)?

这个问题有道理吗?如果没有,我很抱歉。

最简单的"两次通过";汇编程序可以直接读取输入文本两次。理想情况下,使用相同的精确代码,它在两次传递中都要做相同的工作来解析和理解每一行汇编文本,但在第一次传递中,它只收集符号定义和计数位置,而不是生成任何机器代码。第一步只构建一个符号表,它是键值对的字典,其中名称是键,位置是值。第二遍生成机器代码,但不生成符号表。


一个简单的单程汇编程序可以只读取输入文本一次,尽可能生成机器代码,但要记住生成的机器代码中的前向引用发生在哪里(以及什么样的引用),因为当最终发现引用符号的值时,这些引用必须被修复,也就是修补。


如果存在可变长度指令,这两种设计都会受到影响,其最佳长度取决于标签和代码的位置。这种情况尤其发生在同时具有短分支指令和长分支指令的指令集中,其中当分支指令的位置和分支目标的位置之间的增量在某个大小内(例如,适合8位)时,可以使用短分支指令。如果一个分支必须从短变长,反之亦然,这可能会影响代码&标签位置,这反过来会影响其他分支的大小。

在这种情况下,当分支距离未知时,我通过假设短分支(在具有相当多分支的代码中,默认情况下假设较大的分支会迫使许多人在原本不需要大分支的情况下实际需要大分支),找到了最佳结果(生成的代码大小最小)。试图优化(最小)代码大小的汇编程序必须准备好在生成的机器代码中进行广泛的调整,因此一次解析到某个中间形式并处理该中间形式可能是最好的方法,因为当更改需要级联分析和调整时,这将允许更快的迭代。对于中间形式,有很多替代方案,从每行汇编代码的一个节点,到在其末尾最多有一个可能变化大小的指令的某个更大的机器代码块的节点。


当汇编程序生成对象文件作为单独(文件)编译的一部分,并打算通过链接器进行组合时,情况会稍微复杂一些。在这种情况下,将有外部导入和导出的符号以及内部符号。外部定义符号(即此处使用但此处定义)的最终修复/修补由链接器完成。汇编程序的输出(一个对象文件)然后包含机器代码(和数据)、修正记录(指示对未解析符号的引用)、导入(未解析符号名称)和导出(导出符号的名称和位置)。

不支持多文件链接的简单的两遍汇编程序可以简化假设,即所有符号都将通过第一遍解析,因此不需要生成或存储修复/修补信息。添加对多文件链接的支持消除了这种简化,这也是我更喜欢"链接"的另一个原因;一次通过";汇编程序设计,因为它已经有了在同一文件中进行前向引用的固定记录的固有概念,并且更容易适应多文件链接。


RISC V系列处理器(和一些其他处理器)的一些工具链支持链接器";放松";。这允许根据符号和代码在多个编译单元中的最终位置来更改引用的大小(例如分支的代码序列,通常是函数调用)。这意味着前面提到的机器代码块的概念在最终编译器/汇编程序输出中保持独立,以供链接器调整,甚至单个对象文件内的内部分支(在其他通常完全解析的系统中)也可能需要调整(也可能调整大小)。

在链接器松弛的系统中,对于交叉编译单元引用,通常我们会首先使用最大的代码序列(例如通过编译器或汇编程序),并在可能的情况下让松弛的链接器缩短代码序列或操作数大小。这是因为,在不执行松弛的情况下(如调试所需),较大的代码仍然可以运行,即即使某些调用站点使用的代码空间超出了需要。

(链路时间操作和优化还有许多其他功能,例如在较短的分支序列实际上无法到达的架构上插入分支岛或thunk,然后还有静态加载和动态加载(DLL)这两个持续的主题,其中一些符号分辨率会进一步延迟,相关的修复也是如此)。

通常汇编程序会将源解析为其含义的某种内部表示,因此它只需要再次循环,而不需要重新解析并将其与原始结果匹配。

即第二遍只是调整现有数据结构中的偏移字段!

这是一个争论不休的话题。。。

one:
j two
nop
two:
jmp one

第一个";通过";在使用之前,您会遇到标签one,因此您可以辩称您已经解决了该相对地址。

对于j2,汇编程序在第一次传递时还没有看到这个标签,所以它必须在这次传递中确定它是本地存在还是外部存在。

它看到二,然后看到j一,但已经知道一在哪里,所以在第一次通过时,它可以计算到一的相对偏移量,并完成指令机代码(假设这种汇编语言和指令集,j意味着相对跳跃)。

为了完成j两个指令;通过";需要。

现在一些人坚持认为;通过";在我们可以将源代码存储在内存中以及符号表、部分或完全生成的机器代码等之前,您可以像过去一样重新读取源代码本身。今天,可能很遗憾,它都驻留在内存中(防止出现像过去那样长时间的机器生成程序,因为在过去,程序可能会超过您的内存)。

其他人则认为允许pass定义通过数据结构和符号表的pass来解析相对地址是非常好的。根据汇编语言和本地标签与全局标签的不同,这通常需要多次通过。

不管您是否称之为pass,请解释一下如何不从生成的表的开头重新开始?您希望从您生成的任何代码表的开始到结束都是线性的,并线性地遍历您为每个未解析标签生成的任何符号表。

因此,有些人认为,您必须再次阅读源代码,才能将其称为两遍汇编程序。有些人认为,简单地读取由原始代码生成的表被认为是第二次通过。由于在过去几十年中没有理由再次读取源代码,但人们一直使用术语"两次通过汇编程序",并一直问这个问题,这意味着两次通过包括读取生成的表,而不仅仅是读取源代码密码

但由于这是一个基于观点的话题,你可以自由选择自己的定义。

请注意,很大一部分计算机和编程术语是模糊的,缺乏具体的定义。或者更糟糕的是,许多人对这个定义有强烈的看法,但有很大比例的人认为自己属于许多流行的定义之一,留下了多个完全有效(和正确)但不同的定义。

最新更新