实现8086虚拟机(三)——指令解码

news2024/11/15 12:30:28

文章目录

    • 中间指令格式
    • 解码模块的实现

编译器将汇编语句翻译成机器指令,而虚拟机做的工作正好相反,就是将机器指令解码成可以识别的中间形式,然后执行。

为什么要这么做?拿 mov 指令举例,看下它的机器指令的格式:

在这里插入图片描述
一共有 7 大类,每一类的格式不同,一共有 28 种形式。如果不把机器指令转换成另一种中间格式,那么在写 EU 执行的代码时,必然要将指令解码和执行的逻辑混合在一起,使得代码变得难以维护和扩展。因此我在实现时将解码的部分单独剥离出来,所有的解码都交给解码模块去做。

解码模块提供的解码函数 Decode 原型如下:

func Decode(instructions []byte) []byte 

它的输入是机器指令,输出是中间形式的指令格式。

中间指令格式

我定义的中间指令格式是这样的:

指令类型,指令详细类型,[源操作数],[目的操作数]

以下是定义的指令类型,每一个都表示解码模块支持的指令:

const (
	InstructionMov uint8 = iota
	InstructionAdd
	InstructionOr
	InstructionAdc
	InstructionSbb
	InstructionAnd
	InstructionSub
	InstructionXor
	InstructionCmp
	InstructionInc
	InstructionDec
	InstructionNot
	InstructionNeg
	InstructionMul
	InstructionImul
	InstructionDiv
	InstructionIdiv
	InstructionSegPrefix
	InstructionPush
	InstructionPop
	InstructionJmp
	InstructionCall
	InstructionRet
	InstructionRetf
	InstructionIret
	InstructionLoop
	InstructionInt
	InstructionNop
	InstructionTest
)

比如 mov 指令解码后的中间格式第一字节就是 InstructionMov 。

第二字节表示详细的指令类型,比如 mov 指令,可能是寄存器之间的移动,寄存器和内存直接的移动等等,mov 指令详细类型定义如下:

const (
	MovReg8ToReg8 uint8 = iota
	MovReg16ToReg16
	MovReg8ToMemory
	MovReg16ToMemory
	MovMemoryToReg8
	MovMemoryToReg16
	MovRegToSeg
	MovMemoryToSeg
	MovSegToReg
	MovSegToMemory
	MovImmediateToReg8
	MovImmediateToReg16
	MovMemoryToAL
	MovMemoryToAX
	MovALToMemory
	MovAXToMemory
	MovImmediate8ToMemory
	MovImmediate16ToMemory
)

接下来的几个字节就是操作数了。

  1. 如果是寄存器操作数,那么操作数就是寄存器的ID:
const (
	AL uint8 = iota
	CL
	DL
	BL
	AH
	CH
	DH
	BH
)

const (
	AX uint8 = iota
	CX
	DX
	BX
	SP
	BP
	SI
	DI
)

这些 ID 是和机器指令格式中的 ID 一样的,而不是随意确定的。

  1. 如果是立即数,那么操作数就是立即数的值。

  2. 如果是内存操作数,那么格式如下:

mod | rm,[偏移量]

rm 字段是 3 位,mod 字段是 2 位。实现的解码函数如下:

func decodeMemoryOperand(mod uint8, rm uint8, disp []byte) []byte {
	var decoded []byte
	decoded = append(decoded, mod<<3|rm)
	if mod == 0b00 && rm != 0b110 {
		return decoded
	}

	decoded = append(decoded, disp...)
	return decoded
}

它的参数 mod,rm ,偏移量都是从机器指令中提取的。

解码模块的实现

要实现解码模块,首先要将机器指令分类,然后将每一种类的机器指令与解码函数关联。机器指令的第一字节包含操作码和D,W字段,我们就可以用它来做分类,区分是哪一类指令。

在这里插入图片描述
比如上文提到的 mov 指令第一字节就有 28 种不同的值,我们需要将每一种指令都与一个解码函数关联。

但是仅仅使用第一字节做分类还是不行的,因为有些指令的第一字节都是相同的,
比如:
在这里插入图片描述
ADD,OR,ADC,SBB,AND,SUB,XOR,CMP 这 8 个指令第一节都是 0x80,只有第二字节的 REG 字段的值不一样,所以,有时还需要根据机器指令第二字节的 REG 字段做区分。

然后需要将机器指令与解码函数关联,当识别到某种指令时,调用它的解码函数。

于是我在解码模块定义了如下的结构体和变量来表示每种机器指令对应的解码函数。

// 解码函数
type decodeFunc func([]byte) []byte

// 指令的解码结构体
type decodeInstruction struct {
	InnerDecode [8]*decodeInstruction
	Decode      decodeFunc
}

// 所有指令的解码结构体数组
var decodeInstructions [256]*decodeInstruction

当需要添加指令【该指令可以通过第一字节区分】和它的解码函数时,就调用 AddDecodeInstruction 函数:

func AddDecodeInstruction(firstByte byte, Func decodeFunc) {
	if decodeInstructions[firstByte] == nil {
		decodeInstructions[firstByte] = &decodeInstruction{Decode: Func}
	} else {
		log.Fatalf("duplicated!!")
	}
}

当需要添加只能通过第二字节区分的指令和它的解码函数时,就调用 AddDecodeInstruction2 函数:

func AddDecodeInstruction2(firstByte byte, reg byte, Func decodeFunc) {
	if decodeInstructions[firstByte] == nil {
		decodeInstructions[firstByte] = &decodeInstruction{}
	}

	d := decodeInstructions[firstByte]
	if d.InnerDecode[reg] == nil {
		d.InnerDecode[reg] = &decodeInstruction{Decode: Func}
	} else {
		log.Fatalf("duplicated 2!!")
	}
}

比如,要将所有的 28 种 mov 指令和解码函数关联,我创建一个 decode_mov.go 文件,它的初始化函数如下:

func init() {

	var firstByte byte
	for firstByte = 0x88; firstByte <= 0x8B; firstByte++ {
		AddDecodeInstruction(firstByte, decodeMovRegOrMemoryToFromReg)
	}

	AddDecodeInstruction(0x8C, decodeMovSegToFromRegOrMemory)
	AddDecodeInstruction(0x8E, decodeMovSegToFromRegOrMemory)

	for firstByte = 0xA0; firstByte <= 0xA3; firstByte++ {
		AddDecodeInstruction(firstByte, decodeMovMemoryToFromAccumulator)
	}

	for firstByte = 0xB0; firstByte <= 0xBF; firstByte++ {
		AddDecodeInstruction(firstByte, decodeMovImmediateToReg)
	}

	AddDecodeInstruction(0xC6, decodeMovImmediateToRegOrMemory)
	AddDecodeInstruction(0xC7, decodeMovImmediateToRegOrMemory)

}

当需要新增一种指令,那就新建一个文件,调用 AddDecodeInstruction 或 AddDecodeInstruction2 函数将指令与解码函数注册到解码模块。

解码函数实现如下:

func Decode(instructions []byte) []byte {
	d := decodeInstructions[instructions[0]]
	if d == nil {
		log.Fatalf("unsupported instruction 0x%X", instructions[0])
	}

	if d.Decode != nil {
		return d.Decode(instructions)
	}

	//d.Decode 为nil,说明指令第一字节是相同的,要靠第二字节REG字段分为
	if len(instructions) < 2 {
		return nil
	}

	reg := (instructions[1] & 0b111000) >> 3
	d = d.InnerDecode[reg]
	if d == nil {
		log.Fatalf("unsupported instruction[0] 0x%X, instruction[1] 0x%x",
			instructions[0], instructions[1])
	}

	return d.Decode(instructions)
}

它根据机器指令的第一字节【有时加上第二字节的REG字段】调用相应的解码函数生成中间指令格式。EU 会调用它生成中间指令,然后执行。

后文讲述 mov 指令和 jmp 指令解码函数的实现。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/357508.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

手动创建 vue2 ssr 开发环境

本文和个人博客同步发表 更多优质文章查看个人博客 前言 手动搭建 vue ssr 一直是一些前端开发者的噩梦&#xff0c;因为其中牵扯到很多依赖包之间的配置以及webpack在node中的使用。就拿webpack配置来说&#xff0c;很多前端开发者还是喜欢用webpack-cli脚手架搭建项目。导致…

NCHW - NHWC - CHWN 排列

TensorFlow有两种数据格式NHWC和NCHW,默认的数据格式是NHWC,可以通过参数data_format指定数据格式。这个参数规定了 input Tensor 和 output Tensor 的排列方式。 1、data_format 设置为 “NHWC” 时,排列顺序为 [batch, height, width, channels] 设置为 “NCHW” 时,排…

Linux中常用命令汇总三

Linux中常用命令汇总二地址&#xff1a;https://blog.csdn.net/u011837804/article/details/1289972501、用户组管理类每个用户都有一个用户组&#xff0c;系统可以对一个用户组中的所有用户进行集中管理。不同 Linux 系统对用户组的规定有所不同&#xff0c;如Linux下的用户属…

MIPI CSI 进一步理解

CSI&#xff08;Camera Serial Interface&#xff09;定义了摄像头外设与主机控制器之间的接口&#xff0c;旨在确定摄像头与主机控制器在移动应用中的标准。 CSI关键词描述 缩写 解释 CCI Camera Control Interface&#xff08;物理层组件&#xff0c;通常使用I2C或I3C进行通…

显示技术之器件---LED、Mini LED、Micro LED、OLED、LCD、SMD、SMT

显示技术之基础—半导体 1、LED 1.1 LED(Light Emitting Diode) 即发光二极管&#xff0c;是一种半导体固体发光器件&#xff0c;它是利用固体半导体芯片作为发光材料&#xff0c;当两端加上正向电压&#xff0c;半导体中的载流子发生复合引起光子发射而产生光。LED由含镓&a…

基于SpringBoot的卓越导师双选系统

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7/8.0 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.3.9 浏…

九龙证券|机构密集调研银行业!这些上市银行尤其受捧,关注两大变量

2023年开年以来&#xff0c;组织连续了上一年对上市银行的调研热情。 Wind数据显现&#xff0c;到现在&#xff0c;年内已有9家银行承受637家组织调研&#xff0c;参加调研的组织数较上一年同期添加近一成。 其间&#xff0c;7家被调研银行均处江浙区域。上一年以来&#xff0…

Docker 学习笔记

概述 1. 什么是 Docker&#xff1f; Docker 是一个应用容器平台&#xff0c;管理项目中用到的所有环境&#xff08;MySQL、Redis…&#xff09; 2. Docker 和虚拟机的区别 虚拟机是携带操作系统的&#xff0c;本身很小的应用程序因为携带了操作系统而变得十分笨重&#xff0…

WebRTC paced sender

文章目录4.1 pacer创建4.2 音视频数据包发送4.3 webrtc::PacketRouter4.4 Pacer 媒体数据发送控制4.5 pacer 中的码率探测paced sender通常简称为pacer&#xff0c;其是WebRTC RTP栈的一部分&#xff0c;用于平滑发送到网络上的数据流包&#xff0c;考虑一个帧率为60fps带宽为1…

linux高级命令之线程的注意点

线程的注意点学习目标能够说出线程的注意点1. 线程的注意点介绍线程之间执行是无序的主线程会等待所有的子线程执行结束再结束线程之间共享全局变量线程之间共享全局变量数据出现错误问题2. 线程之间执行是无序的import threading import timedeftask():time.sleep(1)print(&qu…

一句话解读《持续交付》核心能力

​DevOps 是基于持续交付的软件工程。DevOps的核心知识体系在DevOps 四书。持续交付主要是指应用软件集成交付环节&#xff0c;通过配置管理、构建与持续集成、测试管理、部署与发布管理、环境管理、数据管理和度量管理领域的能力建设和工程实践保证软件持续顺畅高质量的对用户…

JavaWeb9-volatile解决内存可见性和指令重排序问题

目录 1.解决内存可见性问题 2.解决指令重排序问题 3.volatile缺点 4.特使使用场景 volatile&#xff08;易变的&#xff0c;易挥发的&#xff0c;不稳定的&#xff09;可以解决内存可见性和指令重排序的问题。 1.解决内存可见性问题 代码在写入 volatile 修饰的变量时&am…

【Linux】自定义生成Kickstart(system-config-kickstart)

文章目录前言一、安装二、运行三、配置3.1 基本配置3.2 安装方法3.3 引导装载程序选项3.4 分区信息3.5 网络配置3.6 验证3.7 防火墙配置3.8 显示配置3.9 软件包选择3.10 预安装脚本3.11 安装后脚本3.12 保存与查看四、总结前言 本文简单介绍下system-config-kickstart的使用&a…

AMQP协议介绍

这篇文章主要介绍AMQP 0-9-1 协议&#xff0c;是RabbitMQ支持的协议之一&#xff0c;理解AQMP对于使用和理解RabbitMQ也很有帮助。 AMQP 0-9-1&#xff08;高级消息队列协议&#xff09;是一种消息传递协议&#xff0c;它使客户端应用程序能与消息中间件进行通信。消息中间件接…

Alibaba Arthas

Alibaba Arthas 基于arthas 3.4.6 Arthas是Alibaba开源的Java诊断工具 可以用来解决 查看class 的加载路径&#xff0c;排除ClassLoader 双向委派存在的问题 程序在线反编译&#xff0c;与热更新 监控到JVM的实时运行状态&#xff08;线程状态&#xff0c;程序热点&#x…

敏感词之 DFA 算法

敏感词之 DFA 算法 常用算法 遍历匹配 将输入的词语&#xff0c;与词库中的敏感词逐个字符遍历&#xff0c;对比是否包含 优点&#xff1a;思路简单&#xff0c;易于实现&#xff08;KMP 算法&#xff0c;Brute-Force 算法&#xff09; 缺点&#xff1a;当词库数目非常大时…

uniapp自定义验证码输入框,隐藏光标

一. 前言 先看下使用场景效果图&#xff1a; 点击输入框唤起键盘&#xff0c;蓝框就相当于input的光标&#xff0c;验证码输入错误或者不符合格式要求会将字体以及边框改成红色提示&#xff0c;持续1s&#xff0c;然后清空数据&#xff0c;恢复原边框样式&#xff1b;5位验证…

【Kubernetes】【十二】Pod详解 Pod调度

Pod调度 ​ 在默认情况下&#xff0c;一个Pod在哪个Node节点上运行&#xff0c;是由Scheduler组件采用相应的算法计算出来的&#xff0c;这个过程是不受人工控制的。但是在实际使用中&#xff0c;这并不满足的需求&#xff0c;因为很多情况下&#xff0c;我们想控制某些Pod到达…

Prometheus监控案例-tomcat、mysql、redis、haproxy、nginx

监控tomcat tomcat自身并不能提供监控指标数据&#xff0c;需要借助第三方exporter实现&#xff1a;https://github.com/nlighten/tomcat_exporter 构建镜像 基于tomcat官方镜像&#xff0c;重新制作一个镜像&#xff0c;将tomcat-exporter和tomcat整合到一起。Ddockerfile如…

【安全知识】——如何绕过cdn获取真实ip

作者名&#xff1a;白昼安全主页面链接&#xff1a; 主页传送门创作初心&#xff1a; 以后赚大钱座右铭&#xff1a; 不要让时代的悲哀成为你的悲哀专研方向&#xff1a; web安全&#xff0c;后渗透技术每日鸡汤&#xff1a; 现在的样子是你想要的吗&#xff1f;cdn简单来说就是…