前面我们对一基于堆栈虚拟机进行了源码剖析《基于栈的虚拟机源码剖析》。之前我们也实现了一个简单的基于堆栈的虚拟机《实现一个堆栈虚拟机》。在《实现一个堆栈虚拟机》中,我们将虚拟机定义为一个VirtualBox类,VirtualBox类中有成员变量:堆栈、指令内存、数据内存,另外还有成员函数:读取指令、执行指令。《基于栈的虚拟机源码剖析》中,是C语言实现的,没有设计成类的形式,但依然有堆栈、指令、数据、读取指令、执行指令等模块。

这里,我们再次实现一个基于堆栈的虚拟机。先给出实现代码,然后再对代码进行解释。

// 基于堆栈的虚拟机实现
#include <iostream>
#include <string>
#include <vector>
#include <stack>
using namespace std;// 虚拟机的二进制指令集
enum Command
{HALT, IN, OUT, ADD, SUB, MUL, DIV,INMEMORY, /* 存放内存 */OUTMEMORY, /* 读取内存 */DUP,LD, ST, LDC, JLT, JLE, JGT, JGE, JEQ, JNE, JMP,INVALID
};// 指令结构体
struct Instruction
{Command com;   // 指令码int     opd;   // 操作数
};// 堆栈
stack<int> stk;// 指令内存
vector<Instruction> insMemory;// 数据内存
vector<int> datMemory(101);// 状态码
enum StateCode
{scHALT, scOK,errDIVBYZERO, errDATMEMORY, errINSMEMORY,errSTACKOVERFLOW, errSTACKEMPTY, errPOP,errUNKNOWNOP
};// 输出错误信息
void Error(const string& err)
{cerr << err << endl;
}// 根据指令码返回指令码需要的操作数个数
int GetOperandCount(Command com)
{int ret = 0;switch (com){case INMEMORY:case OUTMEMORY:case LDC:case JLT:case JLE:case JGT:case JGE:case JEQ:case JNE:case JMP:ret = 1;break;default:ret = 0;break;}return ret;
}// 读取指令
// 从字符串中读取指令码和操作数
// 这里读取的是文本,而非二进制
// 所以指令码占2位,操作数占4位
void ReadInstruction(const string& strCodes)
{int idx = 0;Instruction ins;Command com;int     opd;while (idx < strCodes.size()){string strCod = strCodes.substr(idx, 2);idx += 2;cout << strCod;com = static_cast<Command>(atoi(strCod.c_str())); // 字符转换为指令码,不检测com是否合法int cnt = GetOperandCount(com);if (cnt > 0){string strOpd = strCodes.substr(idx, 4);idx += 4;opd = atoi(strOpd.c_str());cout << '\t' << strOpd;}else{opd = 0;}cout << endl;ins.com = com;ins.opd = opd;insMemory.push_back(ins);}
}// 执行指令
void ExecuteInstructions()
{for (auto idx = 0; idx < insMemory.size() && insMemory[idx].com != HALT; /*++idx*/){bool idxChg  = false;int  idxJump = 0;switch (insMemory[idx].com){case HALT: // 终止break;case IN:   // 输入
            {int tmp;cout << "输入:" << endl;cin >> tmp;stk.push(tmp);}break;case OUT:   // 输出
            {int tmp = stk.top();stk.pop();cout << tmp << endl;}break;case ADD:   // 加法
            {int a = stk.top();stk.pop();int b = stk.top();stk.pop();int c = b + a;stk.push(c);}break;case SUB:   // 减法
            {int a = stk.top();stk.pop();int b = stk.top();stk.pop();int c = b - a;stk.push(c);}break;case MUL:    // 乘法
            {int a = stk.top();stk.pop();int b = stk.top();stk.pop();int c = b * a;stk.push(c);}break;case DIV:    // 除法
            {int a = stk.top();stk.pop();int b = stk.top();stk.pop();if (a == 0){Error("除数为0");// 忽略
                }else{int c = b / a;stk.push(c);}}break;case INMEMORY:{int addr = insMemory[idx].opd;if (addr < 0 || addr >= datMemory.size()){Error("数据地址错误");// 忽略处理
                }else{datMemory[addr] = stk.top();stk.pop();}}break;case OUTMEMORY:{int addr = insMemory[idx].opd;if (addr < 0 || addr >= datMemory.size()){Error("数据地址错误");// 忽略处理
                }else{stk.push(datMemory[addr]);}}break;case DUP:    // 将栈顶元素的值重复压栈
            {stk.push(stk.top());}break;case LD:     // 弹出栈顶元素值,以值为地址,将该地址上的值压栈
            {int addr = stk.top();stk.pop();if (addr < 0 || addr >= datMemory.size()){Error("地址错误");// 忽略错误
                }else{stk.push(datMemory[addr]);}}break;case ST:     // 弹出值,再弹出地址,将值赋予该地址
            {int val = stk.top();stk.pop();int addr = stk.top();stk.pop();if (addr < 0 || addr >= datMemory.size()){Error("地址错误");// 忽略错误
                }else{datMemory[addr] = val;}}break;case LDC:    // 该指令有参数,将该参数压入栈中
            {stk.push(insMemory[idx].opd);}break;case JLT:    // 该指令有参数,如果从栈中弹出的值小于0,则指令指针寄存器跳转到操作数
            {int tmp = stk.top();stk.pop();if (tmp < 0){idxChg  = true;idxJump = insMemory[idx].opd;}}break;case JLE:    // <=
            {int tmp = stk.top();stk.pop();if (tmp <= 0){idxChg  = true;idxJump = insMemory[idx].opd;}}break;case JGT:    // >
            {int tmp = stk.top();stk.pop();if (tmp > 0){idxChg  = true;idxJump = insMemory[idx].opd;}}break;case JGE:    // >=
            {int tmp = stk.top();stk.pop();if (tmp >= 0){idxChg  = true;idxJump = insMemory[idx].opd;}break;}case JEQ:    // ==
            {int tmp = stk.top();stk.pop();if (tmp == 0){idxChg  = true;idxJump = insMemory[idx].opd;}}break;case JNE:    // !=
            {int tmp = stk.top();stk.pop();if (tmp != 0){idxChg  = true;idxJump = insMemory[idx].opd;}}break;case JMP:    // 无条件
            {idxChg  = true;idxJump = insMemory[idx].opd;}break;default:{Error("未知指令码");}break;}if (idxChg){idx = idxJump;}else{++idx;}}
}// 复位虚拟机
void Reset()
{insMemory.clear();datMemory.clear();datMemory.resize(101);while (!stk.empty()){stk.pop();}
}int main()
{// 测试虚拟机// 测试:// 输入两个数,并将其比较,将较大者输出//00 01      #输入第一个数                                          IN//01 01      #输入第二个数                                          IN//02 07 0001 #将数2存入到内存1中                                    INMEMORY//03 07 0000 #将数1存入到内存0中                                    INMEMORY//04 08 0000 #重新将内存0数入栈                                     OUTMEMORY//05 08 0001 #重新将内存1数入栈                                     OUTMEMORY//06 04      #将数1减去数2,数1和数2都弹栈,并将结果入栈            SUB//07 15 0011 #检测栈顶元素是否大于0,如果大于0进行跳转              JGT//08 08 0001 #如果不大于0,则将内存1输出,首先还是将内存1数入栈     OUTMEMORY//09 02      #将数1输出                                             OUT//10 19 0013 #无条件跳转到终止                                      JMP//11 08 0000 #如果大于0,则将内存0输出,首先还是将内存0数入栈       OUTMEMORY//12 02      #将数0输出                                             OUT//13 00      #终止                                                  HALT// 输入指令为:// 010107000107000008000008000104150011080001021900130800000200while (true){string strCodes;cin >> strCodes;ReadInstruction(strCodes);ExecuteInstructions();Reset();}return 0;
}

上面基于堆栈的虚拟机主要包含以下几部分:

         1.虚拟机的二进制指令集定义

         2.指令结构体定义

         3.堆栈

         4.指令内存

         5.数据内存

         6.状态码的定义

         7.错误信息的处理

         8.根据指令码获取其操作数个数

9.读取指令,这里我们是从文本中读取的指令码和操作数,而不是从二进制数据中读取。由于指令码的个数大于9,所以我们的指令码占2位,另外操作数占据4位,操作数可以是数据内存的地址,也可以是指令内存的地址,或者是具体的数值。

         10.执行指令

         11.复位

         12.测试指令:

这里我们的测试样例是:输入两个数,将其比较输出其中较大的哪个数,指令如下:

00 01      #输入第一个数                                         IN
01 01      #输入第二个数                                         IN
02 07 0001 #将数2存入到内存1中                                    INMEMORY
03 07 0000 #将数1存入到内存0中                                    INMEMORY
04 08 0000 #重新将内存0数入栈                                     OUTMEMORY
05 08 0001 #重新将内存1数入栈                                     OUTMEMORY
06 04      #将数1减去数2,数1和数2都弹栈,并将结果入栈                SUB
07 15 0011 #检测栈顶元素是否大于0,如果大于0进行跳转                  JGT
08 08 0001 #如果不大于0,则将内存1输出,首先还是将内存1数入栈          OUTMEMORY
09 02      #将数1输出                                           OUT
10 19 0013 #无条件跳转到终止                                      JMP
11 08 0000 #如果大于0,则将内存0输出,首先还是将内存0数入栈            OUTMEMORY
12 02      #将数0输出                                            OUT
13 00      #终止                                                HALT输入指令为:
010107000107000008000008000104150011080001021900130800000200

另外,对于JLT、JLE、JGT、JGE、JEQ、JNE、JMP这几个跳转指令,其操作数是为下一个执行指令的地址,而非当前指令地址的增量。

以上是我们根据《基于栈的虚拟机的实现》,相对于之前的《实现一个堆栈虚拟机》临摹的另一个版本的堆栈虚拟机。堆栈虚拟机支持更多的指令,比如算术指令、函数操作等有待我们进一步学习。另外,基于寄存器的虚拟机实现原理将在以后的学习中予以介绍。除此之外,还会研读一些别人的源码(比如XML解析器的实现),从中学习一些东西。

基于堆栈的虚拟机实现相关推荐

  1. 20165219王彦博《基于Cortex-M4的虚拟机制作与测试》课程设计个人报告

    20165219王彦博<基于Cortex-M4的虚拟机制作与测试>课程设计个人报告 一.个人贡献 参与课设题目讨论及完成全过程: 资料收集: 负责环境搭建,代码运行下载: 撰写小组结题报告 ...

  2. 堆栈 cookie 检测代码检测到基于堆栈的缓冲区溢出_WhatsApp缓冲区漏洞曝光 攻击者可通过MP4文件执行远程代码...

    Facebook 刚刚披露了 WhatsApp 缓冲区漏洞的部分细节.在上周的一份安全公告中,其表示 CVE-2019-11931 是由基于堆栈的缓冲区溢出 bug 引发,导致攻击者可向受害者发送精心 ...

  3. 检测到基于堆栈的缓冲区溢出_检测到堆栈粉碎

    检测到基于堆栈的缓冲区溢出 我敢打赌,每个Java开发人员在他们的职业生涯开始时第一次遇到Java代码的本机方法时都会感到惊讶. 我还可以肯定,多年来随着了解JVM如何通过JNI处理对本机实现的调用而 ...

  4. 有未经处理的异常(在 xx.exe 中): 堆栈 Cookie 检测代码检测到基于堆栈的缓冲区溢出。

    有未经处理的异常(在 xx.exe 中): 堆栈 Cookie 检测代码检测到基于堆栈的缓冲区溢出. 参考文章: (1)有未经处理的异常(在 xx.exe 中): 堆栈 Cookie 检测代码检测到基 ...

  5. linux虚拟机模板部署模板,创建和部署基于 Linux 的虚拟机模板

    适用于: System Center 2012 SP1 - Virtual Machine Manager 通过使用 Virtual Machine Manager (VMM) 中 System Ce ...

  6. PHP高级计算器的过程,PHP基于堆栈实现的高级计算器功能示例

    PHP基于堆栈实现的高级计算器功能示例 发布于 2017-10-14 13:38:26 | 108 次阅读 | 评论: 0 | 来源: 网友投递 PHP开源脚本语言PHP(外文名: Hypertext ...

  7. 堆栈 Cookie 检测代码检测到基于堆栈的缓冲区溢出

     报错:0x000CC3C9 处有未经处理的异常(在 image_opencv2.exe 中):  堆栈 Cookie 检测代码检测到基于堆栈的缓冲区溢出. 主要检查代码中有没有对数组的越界操作, ...

  8. 虚拟化技术 — QEMU-KVM 基于内核的虚拟机

    目录 文章目录 目录 KVM QEMU QEMU-KVM 集成软件架构 CPU 虚拟化实现 Memory 虚拟化实现 I/O 虚拟化实现 QEMU-KVM 虚拟机的本质 VM 的 vCPU 两级调度 ...

  9. 基于VMware的虚拟机资源池实现(上)-创建资源池

    大家将要读到的是如何使用VMware技术完成一个自动化/流程化(企业内部流程)的虚拟机资源池,这一方案是已经在大型金融企业管理了数千台虚拟机的实际方案.在大多数做云的甲方企业或乙方集成商看来,这就是云 ...

最新文章

  1. Apache ZooKeeper - Leader 选举 如何保证分布式数据的一致性
  2. 对称振子天线matlab程序,对称振子天线详解.ppt
  3. tmux命令启动MySQL_tmux启动脚本
  4. 如何启用和关闭数据库的Oracle归档模式
  5. highcharts图表高级入门之polar:极地图的基本配置以及一些关键配置说明
  6. [js] json和对象有什么区别?
  7. 大数据分析与应用技术国家工程实验室项目通过验收
  8. vb 访问远程计算机,vb 连接远程服务器
  9. python创建txt文件_Mac怎么创建txt文件?教你设置新建txt的快捷键
  10. thinkphp 捕捉错误
  11. Funcode实现黄金矿工
  12. InfoGAN详解与实现(采用tensorflow2.x实现)
  13. js 香港地区 手机号效验正则
  14. win10桌面显示计算机及网上邻居,Win10网上邻居在哪里Win10桌面显示网络图标的方法...
  15. Drillbeach---第三章 Drillbench Hydraulics User Guide
  16. apache linux启动失败,apache2 启动失败,出现下列错误,请问怎么解决,谢谢
  17. express实现上传图片到七牛云
  18. 防止 Access 数据库被下载的手段。
  19. 如何删除ActiveX控件
  20. 每日新闻 | 人造肉销售火爆全食超市CEO却吐槽:不健康

热门文章

  1. 【计算机网络】网络层 : IP 数据报分片 ( 数据分片机制 | 分片示例 | 三种数据长度单位 )
  2. 设计模式-Adapter模式
  3. MySQL 5.7 create VIEW or FUNCTION or PROCEDURE
  4. 用户 'XXX\SERVERNAME$' 登录失败。 原因: 找不到与提供的名称匹配的登录名。 [客户端: ]...
  5. 小看--发布-订阅(观察者)模式
  6. 自定义 checkbox 新玩法 ?
  7. 客户端与服务器cookie
  8. (转)CentOS 5.5 64bit 编译安装Adobe FlashMediaServer 3.5
  9. Webbrowers控件的小技巧
  10. [译] 新手和老手都将受益的JavaScript小技巧