【问题标题】:tutorials on first pass and second pass of assembler汇编程序的第一遍和第二遍教程
【发布时间】:2012-02-09 11:43:24
【问题描述】:

是否有很好的教程可以解释汇编程序的第一遍和第二遍以及它们的算法?我搜索了很多关于它们的信息,但没有得到令人满意的结果。 如有教程请链接。

【问题讨论】:

    标签: algorithm assembly


    【解决方案1】:

    不知道有什么教程,真的没什么。

    one:
      inc r0
      cmp r0,0
      jnz one
      call fun
      add r0,7
      jmp more_fun
    fun:
      mov r1,r0
      ret
    more_fun:
    

    汇编器/软件,就像人类一样,从上到下读取源文件,文件中的字节 0 到最后。对于您在每次传递中完成的内容没有硬性规定,它不一定是“文件”传递,而是“数据”传递。

    第一关: 当您阅读每一行时,您会对其进行解析。您正在构建某种具有文件顺序指令的数据结构。当您遇到像这样的标签时,您会跟踪前面的指令,或者您可能在指令之间有一个标记,但是您选择实施它。当你遇到一个使用标签的指令时,你有两个选择,你现在可以去寻找那个标签,如果它是一个向后看的标签,那么你应该已经像 jnz one 指令一样看到它了。 如果到目前为止,您一直在跟踪指令的数量和大小(如果是可变字长),如果它是相对指令,您现在可以选择对该指令进行编码,如果指令集使用绝对指令,您可能无论如何都必须留下一个占位符。

    现在调用 fun 和 jump more_fun 指令带来了问题,当你看到这些指令时你现在无法解决它们,你不知道这些标签是在这个文件本地还是在另一个文件中,所以你不能编码这条指令在第一次通过,你必须保存它以备后用,这就是第二次通过的原因。

    第二次传递很可能是跨数据结构传递,而不是实际传递到文件上,这在很大程度上是特定于实现的。例如,您可能有一个一维结构数组,并且所有内容都在其中。您可以选择对该数据进行多次传递,例如,在数组中启动一个索引以查找未解析的标签。当您找到未解析的标签时,通过数组发送第二个索引以查找标签定义。如果您没有找到它,那么特定于应用程序,您的汇编程序是创建稍后链接的对象还是创建一个二进制文件,它是否必须在这个程序集中将所有内容解析为二进制步骤?如果对象,那么您假设这是外部的,除非特定于应用程序,否则您的汇编程序需要将外部标签定义为外部。因此,缺少的标签是否是错误是特定于应用程序的。如果它不是错误,那么特定于应用程序,您应该对最长/最远类型的分支进行编码,留下地址或距离详细信息供链接器填写。

    对于您找到的标签,您现在大致了解了多远。现在,根据汇编器的指令集和/或特性,您需要对数据进行多次传递。您需要开始对指令进行编码,假设您至少有一种相对距离呼叫或分支指令的风格,您必须决定第一次编码传递是否希望,我假设,是一个更短/更小的指令相对距离分支或假设较大的一个。在您通过指令获得一个或几个编码传递之前,您无法真正确定较小的是否会到达。

    top:
      ...
      jmp down
      ...
      jnz top
      ...
    down:
    

    当您对 jmp 进行向下编码时,您可能会乐观地选择将其编码为较小的(如果字长可变,则为字节数/字数)相对分支,留下有待确定的距离。当您到达 jnz 顶部时,可以说它恰好是刚好足够接近顶部以使用相对分支进行编码的字节。在第二次通过时,尽管您必须返回并完成 jmp,但您会发现它无法到达,您需要更多字节/单词将其编码为长分支。现在 jnz top 也必须成为一个 far 分支(导致 down 再次移动)。你必须不断地传递指令,计算它们的远/短距离,直到你通过没有改变。小心不要陷入无限循环,其中一次您可以缩短一条指令,但这会导致另一条指令变长,而在下一次通过时,加长一个会导致另一个变长,但第二个会缩短,这会永远重复.

    我们可以回到顶部,在您的第一遍中,您可能会构建一个或多个数据结构,也许您会构建一个找到的标签列表和一个缺失标签的列表。第二遍你查看丢失的列表,看看它们是否在找到,然后以这种方式解决它们。或者可能在第一遍时,有些人可能会争辩说这是一个单遍汇编程序,当您找到一个标签时,在继续浏览文件之前,您回顾一下是否有人在寻找该标签(或者该标签是否已经定义声明错误)我将其称为多通道汇编器,因为它仍然多次传递数据。

    现在让情况变得更糟。以 arm 指令集和任何其他固定长度指令集为例。您的相关分支通常在一条指令中编码,因此是固定长度的指令集。远分支通常涉及从在该地址找到的数据中加载 pc,这意味着您确实需要两条指令,然后在该指令的相对范围内的某个地方,包含一个包含分支绝对地址的数据字。您可以选择强制用户创建这些,但以 ARM 汇编器为例,他们可以并且将为您执行此操作,最简单的示例是:

    ldr r0,=0x12345678
    ...
    b somewhere
    

    该语法意味着使用值 0x12345678 加载 r0,这不适合 arm 指令。汇编器使用该语法所做的是尝试在该指令范围内的代码中找到可以放置数据值的死点,然后将该指令编码为来自 pc 相对地址的加载。例如,在无条件分支之后是隐藏数据的好地方。有时您必须使用 .pool 之类的指令来鼓励或提醒汇编程序将这些数据粘贴到好地方。 r0 不是 r15 的程序计数器,您可以在那里使用 r15 将其连接到上面的分支讨论。

    看看我为这个项目创建的汇编器http://github.com/dwelch67/lsasim,一个固定长度的指令集,但我强制用户分配字并从中加载,我不允许使用 arm 汇编器倾向于允许的快捷方式。

    我希望这有助于解释事情。底线是您无法在一次线性传递数据中解析标签,您必须返回并将点连接到前向引用的标签。而且我认为你必须做很多次才能解决所有的长/短编码(除非指令集/语法强制用户明确指定绝对与相对分支,有些做 rjmp vs jmp 或 rjmp vs ljmp, rcall与通话等)。确保通过“文件”,这不是问题。如果您允许包含类型指令,一些工具将创建一个临时文件,它会在其中提取所有包含以创建一个没有包含的单个文件,然后该工具会对此进行一次传递(这就是 gcc 管理包含的方式,例如,有时保存中间文件并查看生成了哪些文件)(如果您报告带有警告/错误的行号,那么您必须管理临时文件行与原始文件名和行。)。

    【讨论】:

    • 为了完全准确,如果您没有足够的内存来存储所有表和部分二进制文件,则需要对源代码进行第二次传递。第一次通过收集标签及其所在位置的信息,只需要标签表的 ram。第二遍使用源代码和标签表生成输出以解析前向引用。这是这两个术语的真正来源。今天很少需要对源进行两次传递。
    【解决方案2】:

    David Solomon 的书Assemblers and Loaders 是一个很好的起点。这是一本较旧的书,但信息仍然相关。

    您可以下载PDF of the book

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-09-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-08
      • 2011-12-30
      相关资源
      最近更新 更多