基于龙芯32位基础整数指令集的汇编器(二)

目录

  • 前篇传送门
  • 前情提要
  • 第三步(续):分支指令的细节
  • 第四步:代码段和数据段
      • 数据变量处理
  • 第五步:基础的错误检查机制
  • 结尾

前篇传送门

这是本项目的第二篇文章,以下为前篇的传送门:
第一篇

前情提要

  • 已经划分了指令集
  • 已确定了使用Qt来做可视化界面开发
  • 明确了汇编代码翻译过程

第三步(续):分支指令的细节

  • 分支指令的跳转地址不仅可以是10进制立即数,还可以是16进制立即数,但最关键的问题是:它可能是一个标号
  • 标号一词在汇编代码中是常见的,标号常用于标志一个相对地址,使得分支指令在编写时可以不顾及具体值,跳转到对应的指令位置
  • 但这对于汇编器而言并非自动就能转换的,需要我们能动地去改造一下

首先,我们需要明确为了能实现相对地址的转换,我们需要一个匹配机制,这意味着我们需要抛弃之前的设计思想(按行读取一遍即可),改为两次遍历行输入,即第一次遍历仅进行预处理+标号记忆,第二遍才开始正式翻译机器码。

为什么一遍不行呢?试想一下,如果我们的跳转指令是跳向后面的某个地方,然而我们总是顺序读取指令的,那就会出现我们找不到这个标号的存在的情况,那就很尴尬了,你说它有问题吧,人家又不是真的没定义过这个标号,你说它没问题吧,你的汇编器又不能正确翻译……

为了记忆标号,我们使用自定义结构体,辅以向量当容器进行存储,该标号结构体应该要有如下成员:

  1. 标号名
  2. 标号出现的位置(或直接PC值)

当第二遍进行指令翻译时,我们就能对存在使用标号跳转的分支指令进行正确的翻译了

第四步:代码段和数据段

一份汇编代码,如果只能支持代码段那未免有些单薄,所以我们需要将代码段和数据段分开处理,那问题就是:如何区分我现在是在处理代码段还是数据段呢?

  • 龙芯的汇编指令格式中指出,.text.data关键字可以用于标志代码段和数据段的起始位置,由此,我们可以通过汇编代码翻译的方式,逐行读取的过程中,使用QString数据类型提供的.mid方法,同时辅以.simplify方法(用于去除行首尾的空格和格式符);
  • 在程序中设立text_flagdata_flag两个标志,如果读取到的内容是上述两个关键字,就将对应的标志激活,通过固定的逻辑判断,就不难推断读取的内容是数据还是代码了
  • 同时,将代码和数据分开后,处理函数也能够更好地实现分治策略

但新的问题也随之而来,数据部分怎么处理呢?

数据变量处理

  • 数据变量定义的一个例子:string .asciz "Hello world.\r\n"这里定义了一个名为string的变量,类型是asciz,内容是Hello world.\r\n;所以我们需要一个“数据变量”结构体,成员有三:
    1. 变量名
    2. 变量类型
    3. 变量内容
    • 然后再用一个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仓库获取,仓库地址,内容属作者原创,只供学习免费使用,其余用途请联系作者

你可能感兴趣的:(汇编器,qt,软件工程)