之前我们有对一汇编器进行源码剖析,详见《汇编器源码剖析》,并且《实现一个汇编器》。本文,我们继续之前的工作,对反汇编器进行源码剖析,之后我们会根据反汇编器的实现原理,实现一个自己版本的反汇编器。本文剖析的反汇编器代码详见:source code。
源码中反汇编器对应的文件为sdasm.c。
sdasm.c文件中包含汇编指令集的定义op_desc,其类型为字符串数组。
const char *op_desc[] = { "HALT", "IN", "OUT", "ADD", "SUB", "MUL", "DIV", "DUP", "LD", "ST", "LDC", "JLT", "JLE", "JGT", "JGE", "JEQ", "JNE", "JMP", 0 };
最为主要的是dasm_output函数,其功能即是针对输入参数const Instruction* insts二进制指令,将其转换为对应的汇编指令。并将结果输出到const char* file文件中。其具体转换过程是一次扫描insts,将insts[].op二进制指令对应的汇编指令输出到文件中,进而检测insts[].op对应的操作数个数,如果操作数个数大于0,则将指令对应的操作数也输出到文件中。一条指令占一行。
void dasm_output( const char *file, const Instruction *insts, int size ) { FILE *fp; char f[256]; int i; strcpy( f, file ); strcat( f, ".dasm" ); fp = fopen( f, "w" ); for( i = 0; i < size && insts[i].op != opHalt; ++ i ) { fprintf( fp, "%3d\t%s", i, op_desc[insts[i].op] ); if( get_operand_count( insts[i].op ) > 0 ) { fprintf( fp, "\t%d", insts[i].arg ); } fputc( '\n', fp ); } fclose( fp ); }
Instruction的定义还是沿用sm.h中的定义:
typedef struct Instruction { int op; int arg; } Instruction;
另外,程序extern了一个外部函数:
extern int get_operand_count( int op );
其用于根据二进制指令获取该指令对应的操作数个数。
以上是整个反汇编器的框架,通过对反汇编器的剖析,我们可以知道反汇编器的精髓在于二进制指令集到汇编指令集的映射。
接下来我们将根据反汇编器的实现原理,做一个自己版本的反汇编器。并总结汇编器和反汇编器之间的关系和联系。
上文《反汇编器源码剖析》,我们对一反汇编器源码进行了学习,了解了反汇编器的实现原理。反汇编是汇编的逆过程,其也是包含三个主要部分:
- 汇编指令集
- 二进制指令集
- 二进制指令到汇编指令的映射
有了这三部分之后,我们就可以对二进制指令,将其翻译成汇编指令,也就完成了反汇编过程。
我们的二进制指令集和汇编指令集还是沿用之前的指令集。
下面我们先给出实现的反汇编器,然后对相关代码进行解释。
// 实现一个反汇编器 #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 }; // 枚举类型说明: // enum后面定义的是枚举类型名 // 花括号内部是该枚举类型可以取的值 // 初始化汇编指令集 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] = 0; } // 建立二进制指令到汇编指令的映射 // 初始化 void InitBinaryToAssemble(const vector<string>& assIns, map<BinIns, string>& binToIns) { binToIns.clear(); for (auto i = 0; i != assIns.size(); ++i) { // assIns和BinIns的指令次序一致 binToIns[static_cast<BinIns>(i)] = assIns[i]; } } // 读入二进制指令 void ReadBinary(vector<string>& bin) { bin.clear(); string line; while (getline(cin, line)) { bin.push_back(line); } } // 显示二进制指令 void Display(const vector<string>& bar) { for (auto i = 0; i != bar.size(); ++i) { cout << bar[i] << endl; } } // 将读入的二进制指令转换为Instruction形式 void BinaryToAssemble(const vector<string>& bin, vector<string>& ass, const map<BinIns, string>& binToIns, map<BinIns, int>& insArgNum) { ass.clear(); string binLine; for (auto i = 0; i != bin.size(); ++i) { binLine += bin[i] + '\t'; } cout << binLine << endl; istringstream sin(binLine); string strOp, strArg; string op; string arg; string assIns; BinIns opBin; while (sin >> strOp) { opBin = static_cast<BinIns>(atoi(strOp.c_str())); auto cit = binToIns.find(opBin); if (cit == binToIns.end()) { // 非法二进制指令 // 忽略处理 ; break; } op = cit->second; int argNum = insArgNum[cit->first]; if (argNum > 0) { sin >> strArg; arg = strArg; } else { arg = ""; } assIns = op + '\t' + arg; ass.push_back(assIns); } } // 二进制字符串为十进制字符串 string StringToNum(const string& str) { string ret; int num = 0; for (auto i = 0; i != str.size(); ++i) { num = num * 2 + str[i] - '0'; } char tmp[101]; itoa(num, tmp, 10); ret = tmp; return ret; } // 二进制指令转换为十进制指令 // 针对输入的二进制指令为二进制编码形式的情况 void BinaryToDec(vector<string>& bin) { for(auto i = 0; i != bin.size(); ++i) { istringstream sin(bin[i]); string tmp, ins; while (sin >> tmp) { ins += StringToNum(tmp) + '\t'; } bin[i] = ins; } } int main() { // 汇编指令集 vector<string> assIns; InitAssembleInstructions(assIns); // 二进制指令-操作数个数 map<BinIns, int> insArgNum; InitInstrctionArgNumber(insArgNum); // 汇编指令到二进制的映射 map<BinIns, string> binToAss; InitBinaryToAssemble(assIns, binToAss); vector<string> bin; // 保存读入的二进制指令 ReadBinary(bin); cout << endl; Display(bin); cout << endl; vector<string> ass; // 保存转换后的汇编指令 BinaryToAssemble(bin, ass, binToAss, insArgNum); Display(ass); cout << endl; return 0; }
测试用例:
反汇编器的实现与汇编器实现整体框架基本一致。二进制指令集和汇编指令集完全一样,不同点在于汇编器是从汇编指令到二进制指令的转换,反汇编器是从二进制指令到汇编指令的转换。
我们对输入的二进制指令先将其保存,然后将其逐个扫描,将其解析出对应的二进制值,找到该指令对应的汇编指令,如果该指令具有操作数,则继续将操作数读取出来,并入到汇编指令中。顺序扫描整个二进制指令,即得到其对应的汇编指令。
另外,默认情况下,我们认为输入的二进制指令其表示形式为十进制的,如果输入的是二进制的形式,那么可以调用BinaryToDec函数,将二进制形式的指令转换为十进制形式的指令,进而进行反汇编操作。
到此为止,我们剖析了汇编器和反汇编器的源码,了解了汇编器和反汇编器的原理,并实现了简单的汇编器和反汇编器。
汇编器和反汇编器其主要包含三部分:二进制指令集的定义、汇编指令集的定义、二进制指令集和汇编指令集之间的相互转换关系。
下面我们给出汇编器和反汇编器的简单模型:
接下来,我们将继续学习stack_machine源代码。此外,学习一些C++方面的东西。
2013.10.6 0:33 国庆假期 于家中