跳转至

第 7 章:来到世界更高峰,汇编语言

你问我第 6 章去哪儿了?去上 bhh 的汇编语言就知道了。

指令

LC-3 的汇编指令都可以表示成下面这个样子:

Label Opcode Operands ; Comment

其中你可以不写标记(Label),也可以不写注释(Comment)。

指令和操作数 Opcodes and Operands

这两个东西是必须有的(没有操作数的指令就算了)。

指令 opcode,或者叫操作码,是一种对于人类来说更为友好的表示方法。比如 ADD、AND 和 LDR 很明显要比 0001、0101 和 0110 来得直观一些。

不同指令可能会有不同的操作数 operand,同一个指令也可以有不同的操作数,比如 ADD 就有两种操作数:加俩寄存器,和加一个寄存器和一个立即数。

和指令一样,寄存器们也有了自己的名字,叫 R0 到 R7。十进制还是比二进制友好一些。

标记 Label

标记可以指向一个特定的内存位置。它可以在程序中被直接引用。

LC-3 中,标记名字可以长 1 到 20 个字符,并且可以使用大小写字母和数字。不过第一个字符必须是字母。

标记的应用大大节省了脑细胞:

BRp AGAIN

汇编器会自动算好 nextPC 和 AGAIN 之间的距离;结果就是跳转的地方就是 AGAIN 所在的位置。

注释 Comment

不写注释我 kao 烂你的头。

写废话的我也 kao 烂你的头。

伪指令 Pseudo-Ops

在汇编程序里面,有那么一些指令不是给机器看的,而是给汇编器看的。

LC-3 的伪指令都以点号 . 开始,一共 5 种。

于是就有人问了,啊,汇编器它要看什么呢?

.ORIG

.ORIG xxxxx

它告诉汇编器,应该要把接下来的程序放在内存的什么位置。它对接下来的指令立即生效。比如:

1
2
3
4
5
6
.orig x3000
add r0, r0, 0
add r0, r0, 0
add r0, r0, 0
.orig x6000
add r0, r0, 0

四条指令分别被存到了 x3000、x3001、x3002 和 x6000。

.FILL

.FILL xxxxx

它让汇编器把后面跟的值直接原封不动地放在这个位置。比如:

len .FILL x114

len 所在的这个位置的内存就是 0000 0001 0001 0100。

.BLKW

.BLKW xxxxx

它告诉汇编器,从这里开始的接下来几个内存空间会被占用。或者说,它为了某种东西开辟了这么多空间。

.STRINGZ

.STRINGZ "Hello, World!"

它把字符串的内容一个一个地放到之后的内存空间中,然后最后补一个结束符(x00)。

.END

它告诉汇编器,源代码到这儿就没有了,可以收工了。

(不是把程序结束的意思啊!那个是 halt!)

机器码的生成

汇编器需要把汇编代码扫两次,以正确地生成机器码。

终于不用人肉汇编了!好耶!

创建符号表

第一遍扫描会记录每个标记到底是在内存的哪个位置。为什么不一次就把汇编代码翻译完呢?因为程序中可能会在某个地方出现在它之后的标记,然后此时汇编器还不知道这个标记在哪里,于是它就爆炸了。

生成机器语言程序

第二遍扫描会开始进行汇编的翻译工作。

其中像各种涉及到 PC + offset 类型的寻址操作时,它会计算出当前的 nextPC 和目标位置的距离。注意因为 LC-3 的命令长度时常不够用,如果距离超出了能表示的距离(比如 LD 里面给它留了 9 位,可以表示 [-256, +255]),那么汇编器就爆炸了。