从裸机启动开始运行一个C++程序(七)

news2024/11/15 16:54:36

前序文章请看:
从裸机启动开始运行一个C++程序(六)
从裸机启动开始运行一个C++程序(五)
从裸机启动开始运行一个C++程序(四)
从裸机启动开始运行一个C++程序(三)
从裸机启动开始运行一个C++程序(二)
从裸机启动开始运行一个C++程序(一)

重新写一份MBR代码

前面我们花了不少的篇幅来介绍保护模式,以及通过汇编指令进入保护模式的方法。那么这一节,我们就来上一个完整的实例,首先在实模式下加载Kernel,然后配置GDT,之后进入保护模式,跳转至Kernel,再在Kernel里再打印一些文字用以区分。

前面的实例中我们已经把工程代码分为了mbr和kernel,在mbr中读盘,加载到内存中,然后再通过跳转指令运行kernel文件。虽然照理来说,在kernel中再配置GDT然后进入保护模式也没什么问题,但这样就会使得kernel中同时夹杂两种模式的指令代码(后续我们介绍完386模式以后,就还可能会同时混有16位和32位指令),不方便管理和维护。因此,我们在mbr中就做好这一些,最后以保护模式跳转至kernel部分,这样我们的kernel就会纯粹许多。

下面是我们重新写的一份MBR代码:

; 调用0x10号BIOS中断,清屏
mov al, 0x03
mov ah, 0x00
int 0x10 

; LBA28模式,逻辑扇区号28位,从0x00000000xFFFFFFF
; 设置读取扇区的数量
mov dx, 0x01f2
mov al, 2 ; 读取连续的几个扇区,每读取一个al就会减1
out dx, al
; 设置起始扇区号,28位需要拆开
mov dx, 0x01f3
mov al, 0x02 ; 从第2个扇区开始读(1起始,0留空),扇区号0~7out dx, al
mov dx, 0x01f4 ; 扇区号8~15mov al, 0
out dx, al
mov dx, 0x01f5 ; 扇区号16~23mov al, 0
out dx, al
mov dx, 0x01f6
mov al, 111_0_0000b ;4位是扇区号24~27位,第4位是主从盘(01从),高3位表示磁盘模式(111表示LBA)
; 配置命令
mov dx, 0x01f7
mov al, 0x20 ; 0x20命令表示读盘
out dx, al

wait_finish:
; 检测状态,是否读取完毕
mov dx, 0x01f7
in al, dx ; 通过该端口读取状态数据
and al, 1000_1000b ; 保留第7位和第3cmp al, 0000_1000b ; 要检测第7位为0(表示不在忙碌状态)和第3位是否是1(表示已经读取完毕)
jne wait_finish ; 如果不满足则循环等待

; 从端口加载数据到内存
mov cx, 512 ; 一共要读的字节除以2(表示次数,因为每次会读2字节所以要除以2mov dx, 0x01f0
mov ax, 0x0800
mov ds, ax
xor bx, bx ; [ds:bx] = 0x08000
read:
in ax, dx ; 16位端口,所以要用16位寄存器
mov [bx], ax
add bx, 2 ; 因为ax是16位,所以一次会写2字节
loop read

; 下面配置GDT
mov ax, 0x07e0
mov es, ax

; 空白段
mov [es:0x00], dword 0
mov [es:0x04], dword 0

; 1号段
; 基址0x8000,大小8KB
mov [es:0x08], word 0x1fff ; Limit=0x1fff
mov [es:0x0a], word 0x8000 ; Base=0x008000,这是低16位
mov [es:0x0c], byte 0      ; 这是Base的高8位
mov [es:0x0d], byte 1_00_1_100_0b ; P=1, DPL=0, S=1, Type=100b, A=0
mov [es:0x0e], word 0      ; 保留位都置0

; 下面是gdt信息的配置(暂且放在0x07f00的位置)
mov ax, 0x07f0
mov es, ax
mov [es:0x00], word 15      ; 因为目前配了2个段,长度为16,所以limit为15
mov [es:0x02], dword 0x7e00 ; GDT配置表的首地址
; 把gdt配置进gdtr
lgdt [es:0x00]

mov eax, cr0
or eax, 0x01 ; PE位置1,启动保护模式
mov cr0, eax

jmp 00001_00_0b:0 ; 远跳指令可以刷新cs,使用1号段,正好跳转至kernel的加载位置(0x8000)

times 510-($-$$) db 0 ; MBR剩余部分用0填充
dw 0xaa55

可以看到最后有一个远跳指令jmp 00001_00_0b:0,由于这个时候我们已经通过控制cr0寄存器来进入保护模式了,所以前面段的部分就已经不是段基址而是段选择子了。通过远跳指令我们就可以刷新cs寄存器,让他表示选择子1号段,而根据GDT的配置,1号段的段基址就是0x8000,也就正好是我们Kernel加载的位置。

所以接下来,我们只需要在Kernel中,输出一下信息,观察程序能否正常运行即可。但有个严重的问题是,要想输出信息我们需要写显存,可是显存在0xb8000~0xb8f9f,而1号段的界限在0x1fff,所以,我们当前的情况是没法操作显存的。那怎么办?我相信有读者可能会想到,那要不我们把1号段配长一点,包括到显存,是不是就可以操作了?

答案是:不可以!因为除了界限问题,保护模式下段是具有属性的,也就是GDT中的TypeXEW这3位,我们配置的时候配置的是100W0的时候是不可写的,所以我们不可以向这个段里写数据。

那,把Type配成101不就可以解决了么?理论上来说是的,但并不推荐大家这样去做,因为我们不希望指令段在执行时轻易被更改,这样程序的风险极大。当然了,如果大家仅仅是自己做实验方便的话,倒也无妨。

不过更稳妥的做法,是再配一个段,专门去管理显存。这样我们就要回到MBR里加一个GDT,把显存区域划分成一个段,然后再在显存中写数据即可。

; 2号段
; 基址0xb8000,上限0xb8f9f,覆盖所有显存
mov [es:0x10], word 0x0f9f ; Limit=0x0f9f
mov [es:0x12], word 0x8000 ; Base=0x0b8000,这是低16位
mov [es:0x14], byte 0x0b   ; 这是Base的高8位
mov [es:0x15], byte 1_00_1_001_0b ; P=1, DPL=0, S=1, Type=001b, A=0
mov [es:0x16], word 0

别忘了对应的GDT的配置也要变,要让2号段生效才行:

; 下面是gdt信息的配置(暂且放在0x07f00的位置)
mov ax, 0x07f0
mov es, ax
mov [es:0x00], word 23      ; 因为目前配了3个段,长度为24,所以limit为23
mov [es:0x02], dword 0x7e00 ; GDT配置表的首地址

下面给出修正后的完整MBR代码:

; 调用0x10号BIOS中断,清屏
mov al, 0x03
mov ah, 0x00
int 0x10 

; LBA28模式,逻辑扇区号28位,从0x00000000xFFFFFFF
; 设置读取扇区的数量
mov dx, 0x01f2
mov al, 2 ; 读取连续的几个扇区,每读取一个al就会减1
out dx, al
; 设置起始扇区号,28位需要拆开
mov dx, 0x01f3
mov al, 0x02 ; 从第2个扇区开始读(1起始,0留空),扇区号0~7位
out dx, al
mov dx, 0x01f4 ; 扇区号8~15位
mov al, 0
out dx, al
mov dx, 0x01f5 ; 扇区号16~23位
mov al, 0
out dx, al
mov dx, 0x01f6
mov al, 111_0_0000b ;4位是扇区号24~27位,第4位是主从盘(01从),高3位表示磁盘模式(111表示LBA)
; 配置命令
mov dx, 0x01f7
mov al, 0x20 ; 0x20命令表示读盘
out dx, al

wait_finish:
; 检测状态,是否读取完毕
mov dx, 0x01f7
in al, dx ; 通过该端口读取状态数据
and al, 1000_1000b ; 保留第7位和第3位
cmp al, 0000_1000b ; 要检测第7位为0(表示不在忙碌状态)和第3位是否是1(表示已经读取完毕)
jne wait_finish ; 如果不满足则循环等待

; 从端口加载数据到内存
mov cx, 512 ; 一共要读的字节除以2(表示次数,因为每次会读2字节所以要除以2)
mov dx, 0x01f0
mov ax, 0x0800
mov ds, ax
xor bx, bx ; [ds:bx] = 0x08000
read:
in ax, dx ; 16位端口,所以要用16位寄存器
mov [bx], ax
add bx, 2 ; 因为ax是16位,所以一次会写2字节
loop read

; 下面配置GDT
mov ax, 0x07e0
mov es, ax

; 空白段
mov [es:0x00], dword 0
mov [es:0x04], dword 0

; 1号段
; 基址0x8000,大小8KB
mov [es:0x08], word 0x1fff ; Limit=0x1fff
mov [es:0x0a], word 0x8000 ; Base=0x008000,这是低16位
mov [es:0x0c], byte 0      ; 这是Base的高8位
mov [es:0x0d], byte 1_00_1_100_0b ; P=1, DPL=0, S=1, Type=100b, A=0
mov [es:0x0e], word 0

; 2号段
; 基址0xb8000,上限0xb8f9f,覆盖所有显存
mov [es:0x10], word 0x0f9f ; Limit=0x0f9f
mov [es:0x12], word 0x8000 ; Base=0x0b8000,这是低16位
mov [es:0x14], byte 0x0b   ; 这是Base的高8位
mov [es:0x15], byte 1_00_1_001_0b ; P=1, DPL=0, S=1, Type=001b, A=0
mov [es:0x16], word 0

; 下面是gdt信息的配置(暂且放在0x07f00的位置)
mov ax, 0x07f0
mov es, ax
mov [es:0x00], word 23      ; 因为目前配了3个段,长度为24,所以limit为23
mov [es:0x02], dword 0x7e00 ; GDT配置表的首地址
; 把gdt配置进gdtr
lgdt [es:0x00]

mov eax, cr0
or eax, 0x01 ; PE位置1,启动保护模式
mov cr0, eax

jmp 00001_00_0b:0 ; 远跳指令可以刷新cs,使用1号段,正好跳转至kernel的加载位置(0x8000)

times 510-($-$$) db 0 ; MBR剩余部分用0填充
dw 0xaa55

然后我们在Kernel中打印随便打印点东西,查看效果即可。注意,要写显存的话,需要将段寄存器调整到2号选择子。

以下是Kernel例程:

begin:

mov ax, 00010_00_0b ; 选择2号段,以操作显存
mov ds, ax
; 打印Hello
mov [0x0000], byte 'H'
mov [0x0001], byte 0x0f
mov [0x0002], byte 'e'
mov [0x0003], byte 0x0f
mov [0x0004], byte 'l'
mov [0x0005], byte 0x0f
mov [0x0006], byte 'l'
mov [0x0007], byte 0x0f
mov [0x0008], byte 'o'
mov [0x0009], byte 0x0f

hlt

times 1024-($-begin) db 0 ; 补满2个扇区

以下是运行效果图:
运行效果

小结

由此,我们成功在保护模式下写入数据,并完成的打印。

我会将这一节的整个工程文件打包上传到附件中,读者可以自行使用。

下一篇开始我们会继续进军386模式,也就是后来非常成熟的IA-32架构,并介绍对应的32位指令。要知道IA-32架构的代码就已经可以跟C语言代码做链接了哟!离我们的目标就近了许多,大家敬请期待~

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

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

相关文章

Mall脚手架总结(四) —— SpringBoot整合RabbitMQ实现超时订单处理

前言 在电商项目中,订单因为某种特殊情况被取消或者超时未支付都是比较常规的用户行为,而实现该功能我们就要借助消息中间件来为我们维护这么一个消息队列。在mall脚手架中选择了RabbitMQ消息中间件,接下来荔枝就会根据功能需求来梳理一下超时…

SRE实战:如何低成本推进风险治理?稳定性与架构优化的3个策略

一分钟精华速览 SRE 团队每天面临着不可控的各类风险和重复发生的琐事,故障时疲于奔命忙于救火。作为技术管理者,你一直担心这些琐事会像滚雪球一样,越来越多地、无止尽地消耗你的团队,进而思考如何系统性地枚举、掌控这些风险&a…

ctf中ping命令执行绕过

相关wp参考:CTF中的命令执行绕过方式 - 知乎 CTFping命令绕过及符号用法_ctf ping-CSDN博客 在用linux命令时候,我们可以 一行执行多条命令 或者 有条件的执行下一条命令 linux命令中一些符号的用法 1. “;”分号用法 方式:command1 ; command…

【ccf-csp题解】第7次csp认证-第三题-路径解析超详细题解-字符串模拟

本题思路来源于acwing ccfcsp认证课 题目描述 思路分析 首先&#xff0c;为了处理路径中的反斜杠符号&#xff0c;我们可以实现一个get函数&#xff0c;把一个路径中每一对反斜杠之间的内容存到vector<string>中&#xff0c;如果有连续的多个反斜杠则只看成一个 举个例…

“.NET视频总结:认识框架的结构和组件,掌握开发工具的奥妙“一

目录 第一单元&#xff1a;二十一世纪程序执行 背景: 总结&#xff1a; 第二单元:对象导向与类别设计 背景: 总结&#xff1a; 第三单元&#xff1a;使用类别与基底类别库 总结: 第四单元:Windows开发程序 背景: 总结: 第五单元:防护式程序设计 背景: 总结: 第六…

数据库中的DECODE函数,SIGN函数

oracle中的if(),oracle中if/else的三种实现方式详解_电竞GO的博客-CSDN博客 DECODE(CONCAT(b.AZZ231,%),%,,CONCAT(b.AZZ231,%)) czwcl,

Acrel-6000电气火灾监控系统应用

安科瑞 崔丽洁 摘要 建筑电气火灾在建筑物火灾中占较大的比例&#xff0c;起火原因也很多&#xff0c;包括短路、过热、漏电、雷击和电气等故障&#xff0c;火灾危害也较大。因此&#xff0c;各种原因引起的火灾都应得到有效控制。目前&#xff0c;短路、过热、雷击等保护措施…

【Java网络编程】

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 前言 Java是一种广泛应用于网络编程的编程语言。通过Java的网络编程能力&#xff0c;我们可以构建强大的网络应用程序。本文将介绍Java网络编程的基础知识、常用API和一些实…

nginx目录穿越

测试nginx版本为nginx/1.23.3 location /file {alias /home/;} 在/usr跟目录下新建a.txt测试文件 通过访问 http://{ip}:{端口}/file../test.txt 实现目录穿越 防护:location与alias的值都加上/或不加/

MongoDB实践

MongoDB学习 MongoDB简介 MongoDB 是一种流行的文档型 NoSQL 数据库&#xff0c;它具有以下特点和应用场景&#xff1a; 文档型数据库&#xff1a;MongoDB 使用 BSON&#xff08;Binary JSON&#xff09;格式的文档来存储数据。每个文档可以具有不同的字段&#xff0c;这使得…

山西电力市场日前价格预测【2023-10-12】

日前价格预测 预测说明&#xff1a; 如上图所示&#xff0c;预测明日&#xff08;2023-10-12&#xff09;山西电力市场全天平均日前电价为506.23元/MWh。其中&#xff0c;最高日前电价为841.91元/MWh&#xff0c;预计出现在18: 30。最低日前电价为351.76元/MWh&#xff0c;预计…

关键词搜索速卖通商品列表接口,速卖通商品列表数据接口

在网页抓取方面&#xff0c;可以使用 Python、Java 等编程语言编写程序&#xff0c;通过模拟 HTTP 请求&#xff0c;获取速卖通网站上的商品页面。在数据提取方面&#xff0c;可以使用正则表达式、XPath 等方式从 HTML 代码中提取出有用的信息。值得注意的是&#xff0c;速卖通…

PowerDesinger导入保险业excel数据模型(代码亲测可用)

一、Excel表 按如下结构填充建表所需的数据(附 Excel 表模板): 二、PowerDesigner 新建模型 点击:File——>New Model——>Model types——>Physical Data Mode 三、PowerDesigner 导入 Excel 脚本 按住Ctrl+Shift+X进入脚本运行界面,导入以下脚本并运行: 注…

牛客 明明的随机数

HJ3 明明的随机数 原题思路代码运行截图收获 原题 HJ3 明明的随机数 思路 如果是C的话直接用set结构体就可以自动排序GO&#xff1a;用一个501的数组存储是否出现&#xff0c;最后从头开始输出出现过的数字 代码 #include <iostream> #include <set> using na…

css怎么实现文字描边

有时&#xff0c;我们会遇到UI稿有文字描边的效果&#xff0c;比如下图的效果。 一、给需要描边的文字加一个id选择器 例如&#xff1a; 二、css写法&#xff1a; number,{//这个是实现文字描边的关键&#xff0c;也就是‘空心文字’&#xff0c;这个是定义文字字符的描边的宽…

向量的运算

向量加法 实质是坐标加法。如下图&#xff1a; 向量减法 实质是坐标减法。如下图&#xff1a; 向量乘法 分为点乘和叉乘&#xff0c;点乘是计算平行四边形的面积&#xff0c;叉乘是计算投影&#xff0c;点乘为标量&#xff0c;叉乘为向量。

Git 使用技巧:5个提高效率的命令,不再只会pull和push

前言 使用 Git 作为代码版本管理&#xff0c;早已是现在开发工程师必备的技能。可大多数工程师还是只会最基本的保存、拉取、推送&#xff0c;遇到一些commit管理的问题就束手无策&#xff0c;或者用一些不优雅的方式解决。 本文分享我在开发工作中实践过的实用命令。这些都能…

【1】MongoDB的安装以及连接

今天是2023年10月11日&#xff0c;MongoDB最新版本是7.0.2 最近闲着没事学习一下MongoDB这个NoSQL数据库&#xff0c;有时间就顺手记录一下我学习的笔记吧~ 学习笔记来自黑马程序员《MongoDB基础入门到高级进阶&#xff0c;一套搞定mongodb》 配套资料&#xff1a;点此资料链接…

Android 面经总结分享(相当走心)

背景描述 这是来自一位粉丝朋友的面经分享&#xff0c;他在 「Android 开发行业」 摸爬滚打5年多的开发&#xff0c;呆过的互联网公司有三家&#xff0c;均从事的Android 开发的工作。最后离职的一家公司也是做的最久的一家&#xff0c;工作了将近3年多时光。 废话不多说了&a…

机器学习 - 似然函数:概念、应用与代码实例

目录 一、概要二、什么是似然函数数学定义似然与概率的区别重要性举例 三、似然函数与概率密度函数似然函数&#xff08;Likelihood Function&#xff09;定义例子 概率密度函数&#xff08;Probability Density Function, PDF&#xff09;定义 区别与联系 四、最大似然估计&am…