实现8086虚拟机(一)——基本框架

news2024/11/15 11:18:54

文章目录

    • 基本框架
      • 几点说明:

在 实现8086汇编编译器(四)——生成可执行程序 一文中,我已经实现了一个编译器,可以将汇编语言汇编成二进制程序。

这几篇文章来讲述如何实现虚拟机,也就是执行这个程序的“机器”【它也是一个程序】。这个“机器”的输入是一个二进制程序,以如下汇编程序为例:

assume cs:code,ds:data,ss:stack     ;将cs,ds,ss分别和code,data,stack段相连
data segment
  dw 0123h, 0456h, 0789h, 0abch, 0defh, 0fedh, 0cbah, 0987h
data ends

stack segment
  dw 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
stack ends
code segment
  start: mov ax,stack
         mov ss,ax
         mov sp,20h         ; 将设置栈顶ss:sp指向stack:20

         mov ax, data        ; 将名称为"data"的段的段地址送入ax
         mov ds,ax          ; ds指向data段

         mov bx,0           ; ds:bx指向data段中的第一个单元
         mov cx,8

    s0: push cs:[bx]
        add bx,2
        loop s0             ; 以上将代码段0~15单元总的8个字型数据依次入栈

        mov bx,0
        mov cx, 8
    s1:pop cs:[bx]
        add bx,2
        loop s1             ; 以上依次出栈8个字型数据到代码段0~15单元中

  mov ax,4c00h
  int 21h
code ends
end start

汇编之后对应的二进制程序如下:

root@ubuntu:~/gogo/demo# hexdump a.exec 
0000000 0030 0000 0000 0000 0000 0000 0010 0000
0000010 0002 0000 0031 0000 0210 0039 0000 0110
0000020 0000 0000 0000 0000 0000 0000 0000 0000
*
0000100 0123 0456 0789 0abc 0def 0fed 0cba 0987
0000110 0000 0000 0000 0000 0000 0000 0000 0000
*
0000130 00b8 8e00 bcd0 0020 00b8 8e00 bbd8 0000
0000140 08b9 2e00 37ff c381 0002 f7e2 00bb b900
0000150 0008 8f2e 8107 02c3 e200 b8f7 4c00 21cd
0000160

那么这个机器要执行这个程序的话,主要分为几大步:

  1. 读取二进制程序,识别出程序头和程序
  2. 根据程序头中的信息,设定一个程序要加载到内存中的地址,并根据程序头中的重定位信息修改程序
  3. 将程序写入内存【需要用程序模拟一个内存芯片】中
  4. 设置 CPU 【需要用程序模拟一个CPU】寄存器 CS 和 IP 的值,让它开始执行程序

基本框架

基本框架代码如下:

	// 1. 打开并读取二进制文件
	f, err := os.Open("./a.exec")
	if err != nil {
		log.Fatal("Open error:", err)
	}

	stat, err := f.Stat()
	if err != nil {
		log.Fatal("Stat error:", err)
	}

	b := make([]byte, stat.Size())
	n, err := f.Read(b)
	if err != nil {
		log.Fatal("Read error:", err)
	}

	// 2. 读取程序头部
	buf := bytes.NewBuffer(b[:programHeaderLen])
	programHeader := ProgramHeader{}
	binary.Read(buf, binary.LittleEndian, &programHeader.codeSegProgOffset)
	binary.Read(buf, binary.LittleEndian, &programHeader.codeEntryProgOffset)
	binary.Read(buf, binary.LittleEndian, &programHeader.dataSegProgOffset)
	binary.Read(buf, binary.LittleEndian, &programHeader.stackSegProgOffset)
	binary.Read(buf, binary.LittleEndian, &programHeader.relocationLen)
	programHeader.relocationInfos = make([]RelocationInfo, programHeader.relocationLen)
	err = binary.Read(buf, binary.LittleEndian, programHeader.relocationInfos)
	if err != nil {
		log.Fatal("binary.Read error: ", err)
	}

	// 3. 假设代码段的段地址为0x1000,根据重定位信息修改程序
	program := b[programHeaderLen:]
	var cs int16 = 0x1000
	for _, v := range programHeader.relocationInfos {
		switch v.Type {
		case CodeSegementRelocation:
			program[v.Offset] = byte(cs)
			program[v.Offset+1] = byte(cs >> 8)
		case DataSegementRelocation:
			ds := cs + int16((programHeader.dataSegProgOffset-programHeader.codeSegProgOffset)>>4)
			program[v.Offset] = byte(ds)
			program[v.Offset+1] = byte(ds >> 8)
		case StackSegementRelocation:
			ss := cs + int16((programHeader.stackSegProgOffset-programHeader.codeSegProgOffset)>>4)
			program[v.Offset] = byte(ss)
			program[v.Offset+1] = byte(ss >> 8)
		}
	}

	// 4. 初始化一个CPU和内存芯片
	// 初始化一个内存芯片,大小为1M
	m := Memory{}
	m.Init(1 << 20)
	// 初始化一个CPU
	c := CPU{}
	c.Init()
	// 将CPU与内存相连
	c.ConnectMemory(&m)

	// 5. 将程序写入内存
	// 计算出程序在内存中的起始地址
	var phyAddr uint32 = uint32(cs)<<4 - programHeader.codeSegProgOffset

	// 将程序加载到内存
	c.writeMemory(phyAddr, program)

	// 6. CPU 开始执行程序
	// 第一个参数是CPU开始执行时CS寄存器的值,第二个参数是IP寄存器的值
	c.Run(uint16(cs), uint16(programHeader.codeEntryProgOffset))

几点说明:

  1. 第3条注释。设置代码段的段地址为 0x1000【偏移地址为0】,也就是程序的代码段将被加载到内存偏移量为 64K 的空间【内存地址是0x10000】。这个值是任意定的,但要求不能是CPU保留地址空间。代码段的段地址确定后,就可以根据重定位信息计算出数据段和堆栈段的段地址。
  2. 第5条注释。由于我们规定代码段的内存地址是0x10000,而代码段在程序中也有偏移量【48】,所以0x10000减去这个偏移量得到程序在内存中的起始地址。
  3. 6条注释。当我们将程序加载到内存后,此时代码段内存地址是0x10000,对应CS=0x1000,IP=0,但是程序入口地址也有偏移量【在这个示例程序中偏移量为0】,所以要将IP设置为程序入口偏移量。

这个程序在内存中的布局如下图所示:
在这里插入图片描述

后文讲述如何实现CPU和内存芯片。

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

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

相关文章

LabVIEW错误-2147220623:最大内存块属性不存在

LabVIEW错误-2147220623&#xff1a;最大内存块属性不存在在使用NI Linux实时操作系统目标中&#xff0c;使用系统属性节点和分布式系统管理器&#xff08;DSM&#xff09;&#xff0c;但遇到一些问题&#xff1a;它未正确报告系统上的可用物理内存量。在NI Linux实时系统上出现…

深入浅出带你学习无列名注入

前言 大家对于SQL注入一定不陌生&#xff0c;我们常用的SQL注入方法是通过information_schema这个默认数据库来实现&#xff0c;可是你有没有想过&#xff0c;如果过滤了该数据库那么我们如何进行SQL语句的查询呢&#xff0c;本文就带给大家如何通过不使用information_schema来…

MyBatis详解2——增删改查操作

一、SpringBoot单元测试 1.1什么是单元测试 单元测试是指对软件中的最小测试单元进行检查和验证的过程。 执行单元测试就是为了证明某段代码的执行结果是否符合我们的预期。如果测试通过则是符合预期&#xff0c;否则测试失败。 1.2单元测试的好处 1.单元测试不用启动Tomca…

全球十大资质正规外汇期货平台排行榜(最新版汇总)

外汇期货简称为FxFut&#xff0c;是“Forex Futures”的缩写&#xff0c;是在集中形式的期货交易所内&#xff0c;交易双方通过公开叫价&#xff0c;以某种非本国货币买进或卖出另一种非本国货币&#xff0c;并签订一个在未来的某一日期根据协议价格交割标准数量外汇的合约。 …

Pycharm开发工具的安装和基础使用

数据来源 01 Python开发环境 Pycharm集成开发工具(DE)&#xff0c;是当下全球Pthn开发者&#xff0c;使用最频繁的工具软件。 绝大多数的 Python程序&#xff0c;都是在 Pycharm工具内完成的开发。 Pycharm工具下载 首先&#xff0c;我们先下载并安装它&#xff1a;打开网站…

罗列几个提升WPF应用程序冷启动性能的方法!(Part 2)

在上文中&#xff08;点击这里回顾>>&#xff09;&#xff0c;我们主要介绍了针对三个技术的WPF应用程序性能提升&#xff0c;本文将着重介绍针对DevExpress WPF界面控件研发的应用程序如何提升性能&#xff01;有用控件推荐~DevExpress WPF拥有120个控件和库&#xff0c…

PostgreSQL的学习心得和知识总结(一百二十三)|深入理解PostgreSQL数据库开源扩展pg_dirtyread的使用场景和实现原理

目录结构 注&#xff1a;提前言明 本文借鉴了以下博主、书籍或网站的内容&#xff0c;其列表如下&#xff1a; 1、参考书籍&#xff1a;《PostgreSQL数据库内核分析》 2、参考书籍&#xff1a;《数据库事务处理的艺术&#xff1a;事务管理与并发控制》 3、PostgreSQL数据库仓库…

大彩 串口屏

资料下载 视频 屏幕程序创建 创建 主界面设置 实现按钮和文本的添加&#xff0c;实现画面的切换 下面注释4有点问题&#xff0c;切换画面还是会下传指令集&#xff0c;只是无法在软件中进行指令集的设置了 按钮界面 首先第一步同上添加背景图片&#xff0c;然后添加…

性能VS功能,同为测试又有哪些不一样?

我们在求职的时候&#xff0c;发现有的是招聘的功能测试&#xff0c;有的招聘的是性能测试&#xff0c;那么功能测试和性能测试的区别是什么呢&#xff1f; 侧重点不同 功能测试的侧重点是功能是否满足客户需求。 比如说我们拿到一个节假日搞活动的需求&#xff0c;这个需求…

【订阅】订阅MySql集简云连接器同步报销审批数据至MySql数据库

方案场景 企业在实现数字化转型的道路上&#xff0c;因企业多个系统孤立数据割断&#xff0c;数据互通成为企业率先解决的最大问题&#xff0c;依靠钉钉OA审批&#xff0c;企业通过审批后手动录入到企业的自建系统&#xff0c;然后再同步到MySQL数据库&#xff0c;这种方式不仅…

WPF MVVM系统入门-下

WPF MVVM系统入门-下 CommandManager 接上文WPF MVVM系统入门-上&#xff0c;我们想把Command放在ViewModel中&#xff0c;而不是Model中&#xff0c;可以将CommandBase类改为 public class CommandBase : ICommand {public event EventHandler? CanExecuteChanged{add { C…

[Verilog硬件描述语言]程序设计语句

目录一、数据流建模二、行为级建模2.1 应用场景2.2 initial过程语句2.3 always过程语句2.3.1 电平敏感信号&#xff1a;2.3.2 边沿敏感信号&#xff1a;2.3.3 initial和always语句使用注意2.4 例题&#xff1a;用always过程语句描述4选1数据选择器2.5 例题&#xff1a; 用alway…

2023-02-16:干活小计

数学公式表示学习&#xff1a; 大约耗时&#xff1a;2 hours 在做了一些工作后重读论文&#xff1a;MathBERT: A Pre-Trained Model for Mathematical Formula Understanding 这是本篇论文最重要的idea&#xff1a;Current pre-trained models neglect the structural featu…

魔百和M401A刷入Armbian系统EMMC开启wifi

文章目录一、Armbian系统写入U盘二、U盘内uEnv.txt文件修改三、盒子从U盘进行启动四、设置用户名和密码五、Armbian系统写入EMMC六、 重启系统reboot(不可以拔U盘)七、盒子关机拔出U盘八、插入USB无线网卡&#xff0c;连接wifi上次盒子刷了5.15版本的armbian系统&#xff0c;可…

C++ map和set

目录 1. 关联式容器 2. 键值对 3. 树形结构的关联式容器 3.1 set 3.1.1 set的介绍 3.1.2 set的使用 3.2 map 3.2.1 map的介绍 3.2.2 map的使用 3.3 multiset 3.3.1 multiset的介绍 3.3.2 multiset的使用 3.4 multimap 3.4.1 multimap的介绍 3.5 在OJ中的使用 4.…

Android框架源码分析-浅析OkHttp3

浅析OkHttp3 这篇文章主要用来回顾Okhttp3源码中&#xff0c;同步异步请求的区别、拦截器的责任链模式、连接池管理以及探讨socket通信到底在哪里实现。 列出的代码可能删掉了非核心部分的展示&#xff0c;如果有异议请查看源码 连接池涉及知识&#xff1a;可能根据 IP 地址…

iis7.5应用程序池的启动模式设置

最近发现公司的网站第一次登录时比较慢&#xff0c;甚至有超时的时候&#xff0c;当我检查应用程序池(IIS 7.5)时&#xff0c;应用程序池正常启动&#xff0c;但有时候处于停止状态&#xff0c;停止原因未知。所以必须第一时间重新启动它&#xff0c;以保证网站能被正常访问。于…

kubeadm Dashboard harbor

主机名IP地址安装组件master01192.168.186.10docker、kubeadm、kubelet、kubectl、flannelnode01192.168.186.20docker、kubeadm、kubelet、kubectl、flannelnode02192.168.186.30docker、kubeadm、kubelet、kubectl、flannelharbor192.168.186.40docker、docker-compose、harb…

python语言基础(最详细版)

文章目录一、程序的格式框架缩进1、定义2、这里就简单的举几个例子注释二、语法元素的名称三、数据类型四、数值运算符五、关系运算六、逻辑运算七、运算符的结合性八、字符串一、程序的格式框架 缩进 1、定义 &#xff08;1&#xff09;python中通常用缩进来表示代码包含和…

Python迭代器、生成器和装饰器

一、迭代器 1、迭代器简介 迭代操作是访问集合元素的一种方式&#xff0c;是 Python最强大的功能之一。 迭代器是用来迭代取值的工具&#xff0c;是一个可以记住遍历的位置的对象。 迭代器对象从集合的第一个元素开始访问&#xff0c;直到所有的元素被访问完结束。迭代器只能…