基于堆栈的虚拟机实现
前面我们对一基于堆栈虚拟机进行了源码剖析《基于栈的虚拟机源码剖析》。之前我们也实现了一个简单的基于堆栈的虚拟机《实现一个堆栈虚拟机》。在《实现一个堆栈虚拟机》中,我们将虚拟机定义为一个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解析器的实现),从中学习一些东西。
基于堆栈的虚拟机实现相关推荐
- 20165219王彦博《基于Cortex-M4的虚拟机制作与测试》课程设计个人报告
20165219王彦博<基于Cortex-M4的虚拟机制作与测试>课程设计个人报告 一.个人贡献 参与课设题目讨论及完成全过程: 资料收集: 负责环境搭建,代码运行下载: 撰写小组结题报告 ...
- 堆栈 cookie 检测代码检测到基于堆栈的缓冲区溢出_WhatsApp缓冲区漏洞曝光 攻击者可通过MP4文件执行远程代码...
Facebook 刚刚披露了 WhatsApp 缓冲区漏洞的部分细节.在上周的一份安全公告中,其表示 CVE-2019-11931 是由基于堆栈的缓冲区溢出 bug 引发,导致攻击者可向受害者发送精心 ...
- 检测到基于堆栈的缓冲区溢出_检测到堆栈粉碎
检测到基于堆栈的缓冲区溢出 我敢打赌,每个Java开发人员在他们的职业生涯开始时第一次遇到Java代码的本机方法时都会感到惊讶. 我还可以肯定,多年来随着了解JVM如何通过JNI处理对本机实现的调用而 ...
- 有未经处理的异常(在 xx.exe 中): 堆栈 Cookie 检测代码检测到基于堆栈的缓冲区溢出。
有未经处理的异常(在 xx.exe 中): 堆栈 Cookie 检测代码检测到基于堆栈的缓冲区溢出. 参考文章: (1)有未经处理的异常(在 xx.exe 中): 堆栈 Cookie 检测代码检测到基 ...
- linux虚拟机模板部署模板,创建和部署基于 Linux 的虚拟机模板
适用于: System Center 2012 SP1 - Virtual Machine Manager 通过使用 Virtual Machine Manager (VMM) 中 System Ce ...
- PHP高级计算器的过程,PHP基于堆栈实现的高级计算器功能示例
PHP基于堆栈实现的高级计算器功能示例 发布于 2017-10-14 13:38:26 | 108 次阅读 | 评论: 0 | 来源: 网友投递 PHP开源脚本语言PHP(外文名: Hypertext ...
- 堆栈 Cookie 检测代码检测到基于堆栈的缓冲区溢出
报错:0x000CC3C9 处有未经处理的异常(在 image_opencv2.exe 中): 堆栈 Cookie 检测代码检测到基于堆栈的缓冲区溢出. 主要检查代码中有没有对数组的越界操作, ...
- 虚拟化技术 — QEMU-KVM 基于内核的虚拟机
目录 文章目录 目录 KVM QEMU QEMU-KVM 集成软件架构 CPU 虚拟化实现 Memory 虚拟化实现 I/O 虚拟化实现 QEMU-KVM 虚拟机的本质 VM 的 vCPU 两级调度 ...
- 基于VMware的虚拟机资源池实现(上)-创建资源池
大家将要读到的是如何使用VMware技术完成一个自动化/流程化(企业内部流程)的虚拟机资源池,这一方案是已经在大型金融企业管理了数千台虚拟机的实际方案.在大多数做云的甲方企业或乙方集成商看来,这就是云 ...
最新文章
- Apache ZooKeeper - Leader 选举 如何保证分布式数据的一致性
- 对称振子天线matlab程序,对称振子天线详解.ppt
- tmux命令启动MySQL_tmux启动脚本
- 如何启用和关闭数据库的Oracle归档模式
- highcharts图表高级入门之polar:极地图的基本配置以及一些关键配置说明
- [js] json和对象有什么区别?
- 大数据分析与应用技术国家工程实验室项目通过验收
- vb 访问远程计算机,vb 连接远程服务器
- python创建txt文件_Mac怎么创建txt文件?教你设置新建txt的快捷键
- thinkphp 捕捉错误
- Funcode实现黄金矿工
- InfoGAN详解与实现(采用tensorflow2.x实现)
- js 香港地区 手机号效验正则
- win10桌面显示计算机及网上邻居,Win10网上邻居在哪里Win10桌面显示网络图标的方法...
- Drillbeach---第三章 Drillbench Hydraulics User Guide
- apache linux启动失败,apache2 启动失败,出现下列错误,请问怎么解决,谢谢
- express实现上传图片到七牛云
- 防止 Access 数据库被下载的手段。
- 如何删除ActiveX控件
- 每日新闻 | 人造肉销售火爆全食超市CEO却吐槽:不健康
热门文章
- 【计算机网络】网络层 : IP 数据报分片 ( 数据分片机制 | 分片示例 | 三种数据长度单位 )
- 设计模式-Adapter模式
- MySQL 5.7 create VIEW or FUNCTION or PROCEDURE
- 用户 'XXX\SERVERNAME$' 登录失败。 原因: 找不到与提供的名称匹配的登录名。 [客户端: ]...
- 小看--发布-订阅(观察者)模式
- 自定义 checkbox 新玩法 ?
- 客户端与服务器cookie
- (转)CentOS 5.5 64bit 编译安装Adobe FlashMediaServer 3.5
- Webbrowers控件的小技巧
- [译] 新手和老手都将受益的JavaScript小技巧