分享

实现一个汇编器(汇编指令与指令之间是一一对应的关系也就是说是直译的过程《汇编器源码剖析》)

 山峰云绕 2023-10-18 发布于贵州

  https://blog.csdn.net/judyge/article/details/41171673

(汇编指令与指令之间是一一对应的关系也就是说是直译的过程《汇编器源码剖析》)汇编指令到二进制指令之间的转换

   上文《汇编器源码剖析》中,我们对一汇编器进行了源码剖析,这里我们仿照其实现一个自己版本的汇编器,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]) + '\t';
    }

    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 << '\t' << IntToString(bin[i].op, 1);
        auto cit = insArgNum.find(bin[i].op);if (cit == insArgNum.end())
        {// 如果没有找到// 不做处理            ;break;
        }if (cit->second > 0)
        {
            cout << '\t' << bin[i].arg;
            cout << '\t' << 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——指令码和操作数,然后将指令码和操作数翻译成对应的二进制代码。

         接下来,我们会分析一个反汇编器的源代码,然后根据反汇编器的实现原理,实现一个自己的反汇编器。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多