简单的汇编模拟器教程(JavaScript)部分1[译]
来源:互联网 发布:入门单板吉他推荐 知乎 编辑:程序博客网 时间:2024/06/01 15:18
这篇博客的目标是制作一个简单的模拟器,能够把代码变成CPU指令,并且在一个虚拟的机器上运行。这样来学习汇编语言再好不过了,因为无需顾虑操作系统相关的细节。
模拟器
模拟器使用JavaScript Angular,可以在任何终端的网页浏览器里运行。尽管做了很多简化以及有很多限制,基本结构和所有的模拟器是一样的。这个虚拟的机器包含下面的组成部分。
- 内存(256字节)。内存里储存程序代码以及可供程序运行时存储使用。
- 8位的CPU。CPU从内存读取指令并执行。
- 控制台输出。控制台输出使用内存映射即把内存特定部分映射到控制台输出。如果要输出内容,只要往指定区域写入数据即可。
CPU
模拟器的核心是CPU。这个CPU包含4个通用寄存器(GP),他们的职责是存储执行命令时用到的值。CPU如何知道执行哪条命令呢?我们使用一个指令指针(IP)指向要执行的命令。技术上来说,IP只是一个包含了更多附加功能的寄存器。IP存储下一条指令在内存中的位置信息,在每个CPU指令周期里,CPU会读取一条指令并执行。
为了运行更多的程序,比如提供if-else功能,CPU需要基于上一次运算结果作出决定。这些结果存储为1位的标志位。我们的CPU包含3个不同的标志位。
- Zero(Z)。零标志,最重要的一个。如果指令结果是0,那么这位置为1,否则为0。
- Carry(C)。进位标志,如果一条指令进位了,就置为1。
- Fault。错误,如果一条指令导致CPU错误状态(比如除以0)就置为1。在错误的状态下,CPU停止并且不再继续执行指令。
最后我们升级我们的CPU,给它添加一个栈指针寄存器(sp)。SP指向内存中当前栈的位置。随着程序运行进行存储、函数实现时增大或减小。
首先我们定义所有的寄存器,指针和标志位。然后一个reset函数来初始化CPU或者重置所有功能。
var gpr, ip, sp, zero, carry, fault;function reset() { gpr = [0, 0, 0, 0]; self.maxSP; ip = 0; zero = false; carry = false; fault = false;}
每个CPU指令周期,执行一条命令。指令可能是加(Addition),减(Subtraction),跳(Jump Branching分支),乘(Multiplication),除(Division)等等。
对每种操作有一个特定的指令。两个寄存器相加的情形和寄存器与常数相加的情形是不同的,使用的是不同的指令。意味着即使是我们的简单模拟器,也会有很多指令。就加运算(Addition)来说,我们要实现4种指令。
一条指令包含操作码(opcode)和操作数(operand)。第一个操作数通常是目标(target),第二个是源(source)。如果一条指令只有一个操作数,那么它既是目标也是源。
举例,加法指令定义如下
[opcode] [oprand1] [oprand2]0x0a reg, reg0x0b reg, [address]0x0c reg, address0x0d reg, constant
为了便于使用,我们把操作码用更有意义的名字来表示。例如0x0a替换为ADD_REG_TO_REG。完整列表在opcode.js
所有支持指令的文档在simulator documentation
代码首先用IP从内存读取下一条指令。然后,从内存提取指令里的操作数。计算出结果后设置全部的CPU标志位的值。指令执行完成,IP增加后,CPU周期就算结束了。
function step() { if (fault?) { throw "FAULT. Reset to CPU continue." } var instr = memory.load(ip) switch(instr) { case opcodes.ADD_REG_TO_REG: // 读取第一个操作数:目标寄存器 var regTo = memory.load(ip+1); // 读取第二个操作数:源寄存器 var regFrom = momory.load(ip+2); // 执行指令。寄存器相加 var value = processResult(readRegister(regTo) + readRegister(regFrom)); // 把值写到目标寄存器 writeRegister(regTo, value); // 增加IP ip += 3 break; case opcodes.ADD_REGADDRESS_TO_REG: ... case opcodes.ADD_ADDRESS_TO_REG: ... case opcodes.ADD_NUMBER_TO_REG: ... case ... ... default: throw "Invalid opcode: " + instr; }}function processResult(value) { zero = false; carry = false; if (value >= 256) { carry = true; value %= 256; } else if (value ===0) { zero = true; } else if (value < 0) { carry = true; value = 255 - (-value)%256; } return value;};
你会注意到指令指针(IP)增加3而不是1。这是因为一条指令占用1个字节存储操作码,而操作数也要各自额外占用1个字节。所以加指令一共占用3字节的内存。
完整代码在这:cpu.js。这里包含额外的检查,确保寄存器和内存地址是合法的。
下一篇会实现内存,控制台输出,界面以及如何把汇编代码变成指令。
原文链接
- 简单的汇编模拟器教程(JavaScript)部分1[译]
- 简单的汇编模拟器教程(JavaScript)部分2[译]
- 简单汇编模拟器的语法
- 1313汇编模拟器一(单一语句简单寄存器)
- 汇编模拟器一(单一语句简单寄存器)
- (译)如何使用cocos2d来做一个简单的iphone游戏教程(第一部分)
- (译)如何使用cocos2d来做一个简单的iphone游戏教程(第一部分)
- (译)如何使用cocos2d来做一个简单的iphone游戏教程(第一部分)
- (译)如何使用GameCenter制作一个简单的多人游戏教程:第一部分
- (译)如何使用GameCenter制作一个简单的多人游戏教程:第二部分
- (译)如何使用GameCenter制作一个简单的多人游戏教程:第一部分
- (译)如何使用GameCenter制作一个简单的多人游戏教程:第一部分
- Win32汇编教程四 编写一个简单的窗口
- Win32汇编教程四:编写一个简单的窗口
- Win32汇编教程3 - 创建简单的窗口
- Iczelion的Win32汇编教程(1-3)
- javascript的缓动效果(第1部分)javascript
- Iczelion的Win32汇编教程读书笔记1
- 电话拦截
- 编程规范1 命名规则---做一名优秀的程序员必备
- 重新发现矩阵
- Lecture 3
- 476. Number Complement - 最高比特位后取反
- 简单的汇编模拟器教程(JavaScript)部分1[译]
- 确认当前系统的存储模式(大端模式,小端模式)
- 1047. Student List for Course (25)
- centos配置jdk
- STM32单片机常用库函数
- 【Visual Studio】控制台程序运行时一闪而过
- 简述serializable和transient关键字作用
- AFNetWorking3.x完全解读
- 42.android广播-自定义广播