实现一个汇编器
上文《汇编器源码剖析》中,我们对一汇编器进行了源码剖析,这里我们仿照其实现一个自己版本的汇编器,90%的东西都是借鉴于上文中的源码。
实现一个汇编器,首先需要定义一个汇编指令集,这里我们还是沿用上文中的汇编指令集。汇编指令与指令之间是一一对应的关系,也就是说是直译的过程。我们的指令集是枚举类型,也是沿用上文源码的指令集。
我们的函数功能是对输入的汇编指令,将其读入,翻译成对应的二进制代码,然后将其输出。
实现汇编器的重点在于理解汇编器的原理,而汇编器的原理就在于定义好汇编指令集、二进制指令集,并且确定好二者之间的映射转换关系。
以上即是汇编器的原理。具体如何定义汇编指令集、二进制指令集,如何实现汇编到二进制的映射转换,是具体的实现细节,不同的应用场景实现方法可能不同。
这里,我们给出最为简单的一种实现模型,我们先给出代码,然后根据代码讲解具体的实现细节。
// 实现一个简单的汇编器 #include <iostream> #include <sstream> #include <string> #include <vector> #include <map> using namespace std; enum BinIns; // 二进制指令结构体 // 指令码+操作数 struct Instruction { BinIns op; // 指令码只占一个字节 int arg; // 操作数,占四个字节 }; // 枚举类型的二进制指令集 enum BinIns { binHalt, binIn, binOut, binAdd, binSub, binMul, binDiv, binDup, binLd, binSt, binLdc, binJlt, binJle, binJgt, binJge, binJeq, binJne, binJmp, binInvalid }; // 初始化汇编指令集 void InitAssembleInstructions(vector<string>& assIns) { assIns.clear(); assIns.push_back("HALT"); assIns.push_back("IN"); assIns.push_back("OUT"); assIns.push_back("ADD"); assIns.push_back("SUB"); assIns.push_back("MUL"); assIns.push_back("DIV"); assIns.push_back("DUP"); assIns.push_back("LD"); assIns.push_back("ST"); assIns.push_back("LDC"); assIns.push_back("JLT"); assIns.push_back("JLE"); assIns.push_back("JGT"); assIns.push_back("JGE"); assIns.push_back("JEQ"); assIns.push_back("JNE"); assIns.push_back("JMP"); } // 初始化 // 指令-参数个数 void InitInstrctionArgNumber(map<BinIns, int>& insArgNum) { insArgNum.clear(); insArgNum[binHalt] = 0; insArgNum[binIn] = 0; insArgNum[binOut] = 0; insArgNum[binAdd] = 0; insArgNum[binSub] = 0; insArgNum[binMul] = 0; insArgNum[binDiv] = 0; insArgNum[binDup] = 0; insArgNum[binLd] = 0; insArgNum[binSt] = 0; insArgNum[binLdc] = 1; insArgNum[binJlt] = 1; insArgNum[binJle] = 1; insArgNum[binJgt] = 1; insArgNum[binJge] = 1; insArgNum[binJeq] = 1; insArgNum[binJne] = 1; insArgNum[binJmp] = 1; insArgNum[binInvalid] = 1; } // 建立汇编指令到二进制指令的映射 // 初始化 void InitAssembleToBinary(const vector<string>& assIns, map<string, BinIns>& assToBin) { assToBin.clear(); for (auto i = 0; i != assIns.size(); ++i) { // assIns和BinIns的指令次序一致 assToBin[assIns[i]] = static_cast<BinIns>(i); } } // 读入汇编指令 void ReadAssemble(vector<string>& ass) { ass.clear(); string line; while (getline(cin, line)) { ass.push_back(line); } } // 显示 void Display(const vector<string>& bar) { for (auto i = 0; i != bar.size(); ++i) { cout << bar[i] << endl; } } string StringToUpper(const string& str) { string ret; for (auto i = 0; i != str.size(); ++i) { ret += toupper(str[i]); } return ret; } void AssembleToBinary(const vector<string>& ass, vector<Instruction>& bin, const map<string, BinIns>& assToBin, map<BinIns, int>& insArgNum) { string assline; // 将ass汇总 for (auto i = 0; i != ass.size(); ++i) { assline += StringToUpper(ass[i]) + ' '; } cout << assline << endl; istringstream sin(assline); string strOp, strArg; Instruction ins; BinIns op; int arg; while (sin >> strOp) { auto cit = assToBin.find(strOp); if (cit == assToBin.end()) { // 没有找到对应的指令码 // 忽略处理 ; break; } op = cit->second; // insArgNum为非const型 // assToBin const型保障了insArgNum[]不会存在更新的情况 int argNum = insArgNum[op]; if (argNum > 0) { sin >> strArg; arg = atoi(strArg.c_str()); } else { arg = 0; } ins.op = op; ins.arg = arg; bin.push_back(ins); } } string IntToString(int n, int sizeofbytes) { string ret; ret.resize(sizeofbytes * 8, '0'); for (int i = ret.size() - 1; i >= 0 && n != 0; --i, n /= 2) { ret[i] = n % 2 + '0'; } return ret; } void OutputBinary(const vector<Instruction>& bin, const map<BinIns, int>& insArgNum) { for (auto i = 0; i != bin.size(); ++i) { cout << bin[i].op; cout << ' ' << IntToString(bin[i].op, 1); auto cit = insArgNum.find(bin[i].op); if (cit == insArgNum.end()) { // 如果没有找到 // 不做处理 ; break; } if (cit->second > 0) { cout << ' ' << bin[i].arg; cout << ' ' << IntToString(bin[i].arg, 4); } cout << endl; } } int main() { // 汇编指令集 vector<string> assIns; InitAssembleInstructions(assIns); // 二进制指令-操作数个数 map<BinIns, int> insArgNum; InitInstrctionArgNumber(insArgNum); // 汇编指令到二进制的映射 map<string, BinIns> assToBin; InitAssembleToBinary(assIns, assToBin); vector<string> ass; // 保持读入的汇编指令 ReadAssemble(ass); cout << endl; Display(ass); cout << endl; vector<Instruction> bin; // 保存二进制指令 AssembleToBinary(ass, bin, assToBin, insArgNum); OutputBinary(bin, insArgNum); cout << endl; return 0; }
测试样例
下面我们对程序进行如下解释。
数据结构
Instruction:指令结构体,有两个元素op和arg,分别对应于指令码和操作数。op最多需要一个操作数。
BinIns:枚举类型,二进制指令集。
AssIns:vector<string>类型,汇编指令集
insArgNum:map<BinIns, int>类型,用来记录二进制指令对应的操作数个数
assToBin:map<string, BinIns>类型,汇编指令到二进制指令的映射,用于汇编指令到二进制指令的转换操作
ass:vector<string>类型,用于存储输入的汇编指令
bin:vector<Instruction>类型,用于存储由汇编指令转换的二进制指令
操作函数
InitAssembleInstruction:用于初始化汇编指令集
InitInstructionArgNumber:用于初始化二进制指令对应的操作数个数
InitAssembleToBinary:用于初始化汇编指令到二进制指令的映射
ReadAssemble:用于读入汇编指令
Display:用于显示读入的汇编指令
AssembleToBinary:用于将汇编指令转换为二进制指令
OutputBinary:用于将转换后的二进制指令输出
StringToUpper:用于将汇编代码统一转换为大写,这样可以忽略汇编代码的大小写
IntToString:用于将int型数转换为二进制形式
main:测试函数,先将assIns、insArgNum、assToBin等初始化,读入汇编指令,并将其转换二进制指令,最后将转换后的二进制指令输出。
汇编器实现的关键三点:汇编指令的表示、二进制指令的表示、汇编指令到二进制指令之间的转换三个方面。
在程序中我们对输入的汇编指令是按照空白符间隔的方式进行的汇编代码切分,如果进一步改进,可以对汇编代码进行词法分析,切分出汇编token——指令码和操作数,然后将指令码和操作数翻译成对应的二进制代码。
接下来,我们会分析一个反汇编器的源代码,然后根据反汇编器的实现原理,实现一个自己的反汇编器。
2013.10.4 22:40 国庆假期 于家中