前篇传送门
这是本项目的第二篇文章,以下为前篇的传送门:
第一篇
前情提要
- 已经划分了指令集
- 已确定了使用Qt来做可视化界面开发
- 明确了汇编代码翻译过程
第三步(续):分支指令的细节
- 分支指令的跳转地址不仅可以是10进制立即数,还可以是16进制立即数,但最关键的问题是:它可能是一个标号
- 标号一词在汇编代码中是常见的,标号常用于标志一个相对地址,使得分支指令在编写时可以不顾及具体值,跳转到对应的指令位置
- 但这对于汇编器而言并非自动就能转换的,需要我们能动地去改造一下
首先,我们需要明确为了能实现相对地址的转换,我们需要一个匹配机制,这意味着我们需要抛弃之前的设计思想(按行读取一遍即可),改为两次遍历行输入,即第一次遍历仅进行预处理+标号记忆,第二遍才开始正式翻译机器码。
为什么一遍不行呢?试想一下,如果我们的跳转指令是跳向后面的某个地方,然而我们总是顺序读取指令的,那就会出现我们找不到这个标号的存在的情况,那就很尴尬了,你说它有问题吧,人家又不是真的没定义过这个标号,你说它没问题吧,你的汇编器又不能正确翻译……
为了记忆标号,我们使用自定义结构体,辅以向量当容器进行存储,该标号结构体应该要有如下成员:
- 标号名
- 标号出现的位置(或直接PC值)
当第二遍进行指令翻译时,我们就能对存在使用标号跳转的分支指令进行正确的翻译了
第四步:代码段和数据段
一份汇编代码,如果只能支持代码段那未免有些单薄,所以我们需要将代码段和数据段分开处理,那问题就是:如何区分我现在是在处理代码段还是数据段呢?
- 龙芯的汇编指令格式中指出,
.text
和.data
关键字可以用于标志代码段和数据段的起始位置,由此,我们可以通过汇编代码翻译的方式,逐行读取的过程中,使用QString数据类型提供的.mid
方法,同时辅以.simplify
方法(用于去除行首尾的空格和格式符); - 在程序中设立
text_flag
和data_flag
两个标志,如果读取到的内容是上述两个关键字,就将对应的标志激活,通过固定的逻辑判断,就不难推断读取的内容是数据还是代码了 - 同时,将代码和数据分开后,处理函数也能够更好地实现分治策略
但新的问题也随之而来,数据部分怎么处理呢?
数据变量处理
- 数据变量定义的一个例子:
string .asciz "Hello world.\r\n"
这里定义了一个名为string的变量,类型是asciz,内容是Hello world.\r\n;所以我们需要一个“数据变量”结构体,成员有三:- 变量名
- 变量类型
- 变量内容
- 然后再用一个vector将若干个数据变量结构体存储起来即可
- 接下来我们会发现一个问题(学过CSAPP或操作系统的小伙伴应该对“内部对齐”这个词有些印象),是的,变量存放在内存中,内存按字节编址,但是操作系统为了管理方便,并非直接让变量之间紧挨着存放,而是会在不同类型的变量之间存在人为空白,使得变量的起始地址具有某些特定的规律(如模4为0,或模8为0等等)
- 因此我们可能在处理过程中再对数据变量结构体增加一个成员:起始地址
- 且在处理数据段的过程中,我们人为地计算每个变量的起始地址,使其满足内部对齐要求
第五步:基础的错误检查机制
正如某人所说:“你现在已经是个成熟的汇编器了,要学会自己检错”,咳咳,扯远了,说回正题,完整地走完前三步后,我们虽然已经能够对一篇基本的汇编代码进行翻译,但前提是这篇代码一个错都不能有(格式错误,非逻辑错误),这对于用户显然是非常不友好的,试想各位使用过的IDE那个不是会在编译后提示你几个error几个warning呢?
虽然我们做不到商用IDE那般精确地定位错误,但至少不应该对一些简单的错误视而不见,因此我们需要考虑给汇编器装上检错的一只眼。
首先我们要确定一些常见的错误类型,前4步中可能存在的错误类型整理如下
错误类型 | 错误描述 |
---|---|
指令名错误 | 即指令名书写有误,如将add.w 写成addw 之类的,或是给了个根本不存在的指令名如adc.w |
寄存器名错误 | 龙芯汇编码的寄存器书写规范要求寄存器名前要加符号"$",而且用户可能并不是使用r0~r31,而是诸如rsp,rbp,a1,t1等助记名,所以我们不仅要存储两种名称,还要对用户写错名称的情况进行检查 |
操作数个数问题 | 有些指令是三操作数,有些是双操作数或单操作数的,当我们明确指令类名后,其操作数个数是固定的,因此如果用户给出的操作数个数不对的话,我们也需要进行反应 |
分支指令中使用尚未定义的标号 | 显然,如果我们发现用户输入了一个整篇代码都没出现过的标号,是一定需要提醒用户的(大部分情况可能是因为用户粗心大意打错标号了,而非真的想用一个从未定义过的标号) |
多次定义代码段和数据段起始位置 | 和做人一样要从一而终,不能来回拉扯,无论是代码段还是数据段,都只能定义一次(当然,如果读者觉得可以这样做的话也无妨,这只是本人的一些个人设计罢了) |
全文未见过.text 关键字 | 一篇汇编代码中可以没有数据变量(数据段),但不能没有代码段啊,代码段都没有就很迷…… |
在什么位置检测错误呢?这是个大问题
首先我们目前采用的翻译方式是两次遍历,这意味着错误可能在两次遍历中的某一次出现,一旦出现,我们就应该提前终止翻译吗?可以这样做,且推荐这样做,不仅是避免了后续可能存在的逻辑漏洞,且能节省处理时间。
第一遍预处理时,我们就应该进行指令名错误、多次定义代码段和数据段起始位置和全文未见过.text关键字这三种错误进行检测
第二遍正式翻译时,我们则需要对其他类型错误进行检测,上面的三种反而不会再次出现。
同时我们可以定义一个全局错误描述符,用于表示整个流程中是否出现了错误,出现了什么错误,然后构建一个错误信息输出函数,将对应错误输出到某个Qt窗口,反馈给用户
结尾
第二章就到此结束了,详细代码项目可通过gitee仓库获取,仓库地址,内容属作者原创,只供学习免费使用,其余用途请联系作者