分享

【Computer Systems】Assembler如何写一个汇编器

 山峰云绕 2023-10-17 发布于贵州

https://blog.csdn.net/weixin_41873294/article/details/121536298?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-121536298-blog-132841942.235%5Ev38%5Epc_relevant_anti_vip&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-121536298-blog-132841942.235%5Ev38%5Epc_relevant_anti_vip&utm_relevant_index=2

(汇编器将每条汇编指令解析为字段例如load R3和7将每个字段翻译成其等效的二进制代码最后生成可由硬件执行的二进制指令)

期末复习,写个blog帮助自己梳理。课本用的是《The Elements of Computing Systems, second edition》

汇编器(Assembler)是将汇编语言翻译为机器语言的程序。课本上教的是 the construction of a Hack assembler—a program that translates programs written in the Hack symbolic language into binary code that can execute on the barebone Hack hardware.

难点一:

汇编语言构成的程序中通常会有一些变量,对应着特定的内存地址,这会使得情况变得复杂一些。 汇编程序需要识别这些符号并将它们解析为物理内存地址。 这通常使用符号表 symbol table 来完成——是一种常用的数据结构。

难点二:

需要能够处理命令行参数、处理输入和输出文本文件、解析指令、处理空格、处理符号、生成代码的能力。

-

一、背景 background

机器语言分为两种:binary and symbolic

A binary instruction, 例如 11000010000000110000000000000111,是一个商定好规范的微代码包,旨在由某些目标硬件平台解码和执行。指令最左边的 8 位,11000010,可以代表一个类似于“load”的操作。 接下来的 8 位,00000011,可以代表一个寄存器 R3。 剩下的 16 位,0000000000000111,可以代表数值7。所以这条特定的 32 位指令将让硬件执行“将常量 7 加载到寄存器 R3”的操作。 现代计算机平台支持数百种可能的操作。 因此,机器语言可能很复杂,涉及许多操作代码、内存寻址模式和指令格式。

但因为二进制指令 binary instruction 看懂很麻烦。所以使用商定的等效符号语法 symbolic syntax,例如“load R3 7”。 由于从symbolic syntax到二进制代码的转换更简单,直接用符号表示法编写 low-level 程序并让计算机程序将它们转换成二进制代码是有意义的。 符号语言symbolic language 称为汇编 assembly,翻译程序称为汇编器 Assembler。 汇编器将每条汇编指令解析为字段,例如load、R3和7,将每个字段翻译成其等效的二进制代码,最后生成可由硬件执行的二进制指令。

Symbols:

以 symbolic instruction,goto 312 为例。翻译之后,该指令能够使计算机获取并执行存储在地址 312 中的指令,这可能是某个循环的开始。 那么,如果它是一个循环的开始,为什么不在汇编程序中用一个描述性标签来标记这一点,比如 LOOP,并使用命令 goto LOOP 而不是 goto 312? 我们所要做的就是在某处记录 LOOP 代表 312。 当我们将程序翻译成二进制代码时,我们使用 312替换程序中的每个LOOP记号。

这是为获得程序可读性和可移植性而付出的小代价。

一般来说,汇编语言使用符号有以下三个目的

标签 Labels:汇编程序可以声明和使用标记代码中不同位置的符号,例如 LOOP 和 END。

变量 Variables:汇编程序可以声明和使用符号变量,例如 i 和 sum。

预定义符号 Predefined symbols:汇编程序可以使用商定的符号来引用计算机内存中的特殊地址,例如 SCREEN 和 KBD。

因此我们需要让汇编器记住 SCREEN 代表 16384,LOOP 代表 312,sum 代表某个其他地址,等等。 这个符号处理任务是汇编程序的重要的任务之一 。

虽然行号不是代码的一部分,但它们在翻译过程中扮演着重要的角色。 如果二进制代码将从地址 0 开始加载到指令存储器中,那么每条指令的行号将与其存储器地址一致。

注意注释和标签声明不生成代码,这就是为什么后者有时被称为伪指令。

·

二、Hack 机器语言的一些特点 The Hack Machine Language Specification

二进制 Hack 程序:二进制 Hack 程序是一系列文本行,每行由 16 个 0 和 1 字符组成。 如果该行以 0 开头,则表示二进制 A 指令 A-instruction。 否则,它代表一个二进制 C 指令 C-instruction。

汇编 Hack 程序:汇编 Hack 程序是一系列文本行,每行都是一条汇编指令、一个标签声明或一条注释:

汇编指令 Assembly instruction:A 指令 A-instruction 或 C 指令 C-instruction。

标签声明 Label declaration:(xxx) 形式的一行,其中 xxx 是一个符号。

注释 Comment:以两个斜杠 (//) 开头的行被视为注释,在翻译中需要被忽略。

Symbols:

Hack 汇编程序中的符号分为三类:预定义符号 predefined symbols、标签符号 label symbols, 和变量符号 variable symbols。

预定义符号:任何 Hack 汇编程序都可以使用预定义符号, R0、R1、……、R15 分别代表 0、1、……15。SP、LCL、ARG、THIS、THAT分别代表0、1、2、3、4。SCREEN 和 KBD 分别代表 16384 和 24576。 这些符号的值被解释为 Hack RAM 中的地址。

标签符号:伪指令 (xxx) 定义符号 xxx 来指代 Hack ROM 中保存着汇编程序中下一条指令的位置。 标签符号可以定义一次,并且可以在汇编程序的任何地方使用,甚至可以在定义它的行之前使用。

变量符号:出现在汇编程序中的任何符号 xxx ,若是未预定义且未在其他地方由标签声明 (xxx) 定义的话,都被视为变量。变量在第一次遇到时被映射到连续的 RAM 位置,从 RAM 地址 16 开始。因此,程序中遇到的第一个变量被映射到 RAM[16],第二个被映射到 RAM[17],依此类推。

Syntax Convention:

符号 Symbol:符号可以是不以数字开头的任何字母、数字、下划线 (_)、点 (.)、美元符号 ($) 和冒号 (:) 的序列。

常量 Constant:只能出现在@xxx 形式的 A 指令中。常量 xxx 是 0-32767 范围内的一个值,以十进制表示。

空白 white space:忽略每行汇编指令之前的空格字符和空行。

大小写:所有汇编助记符(如 JEQ 等)都必须大写。其余的符号——标签和变量名——区分大小写。 推荐的约定是对标签使用大写字母,对变量使用小写字母。

这是Hack 机器语言规范。

三、汇编语句到二进制指令的翻译 Assembly-to-Binary Translation

汇编器将汇编指令流作为输入,生成已翻译的二进制指令流作为输出。生成的代码可以按原样加载到计算机内存中并执行。为了执行翻译过程,汇编程序必须处理指令和符号。

处理指令 Handling Instructions:

对于每条汇编指令,汇编器 1)将指令解析为其底层字段 2)对于每个字段,生成相应的二进制码 3)如果指令包含符号引用,则将该符号解析为其数值 4)将生成的二进制代码组装成一个由 16 个 0 和 1 组成的字符串 5)将组装好的字符串写入输出文件

处理符号 Handling Symbols:

因为 hack 允许汇编程序在定义符号之前使用符号标签(goto 指令的目的地),这种约定使汇编代码编写者的生活更轻松,而汇编程序开发人员的生活更加艰难。一个常见的解决方案是开发一个 two-pass assembler,从头到尾读取代码两次。 在第一遍中,汇编器构建一个符号表,将所有标签符号 label symbols 添加到表中,并且不生成任何代码。 在第二遍中,汇编器使用符号表处理变量符号并生成二进制代码。

初始化:汇编器创建一个符号表,并用所有预定义的符号及其预分配值对其进行初始化。初始化阶段的结果是包含所有符号的符号表,包括 KBD。

第一遍:汇编器逐行遍历整个汇编程序,跟踪行号。行号的数字从 0 开始,每当遇到 A 指令或 C 指令时加 1,但在遇到注释或标签声明时不会改变。每次遇到标签声明(xxx)时,汇编器都会在符号表中添加一个新条目,将符号 xxx 与当前行号加 1(这将是程序中下一条指令的 ROM 地址)相关联。 此过程导致将所有程序的标签符号及其相应的值添加到符号表中。

第二遍:汇编器再次遍历整个程序并按如下方式解析每一行。 每次遇到带有符号引用的 A 指令,即@xxx,其中 xxx 是一个符号而不是数字,汇编器会在符号表中查找 xxx。 如果找到该符号,汇编器将用其数值替换它并完成指令的翻译。 如果未找到该符号,则它必须代表一个新变量。 为了处理它,汇编器 (i) 将条目 <xxx, value> 添加到符号表中,其中 value 是 RAM 空间中为变量指定的下一个可用地址,并且 (ii) 使用该地址完成指令的转换。 在 Hack 平台中,RAM 空间 用于存储变量的指定从 16 开始,每次在代码中找到新变量后递增 1。 

 汇编器代码我放我github上了


例如 11000010000000110000000000000111,是一个商定好规范的微代码包,旨在由某些目标硬件平台解码和执行。指令最左边的 8 位,11000010,可以代表一个类似于“load”的操作。 接下来的 8 位,00000011,可以代表一个寄存器 R3。 剩下的 16 位,0000000000000111,可以代表数值7。所以这条特定的 32 位指令将让硬件执行“将常量 7 加载到寄存器 R3”的操作

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多