第二章 BIOS --> MBR

第二章 BIOS --> MBR

本文是对《操作系统真象还原》第二章学习的笔记,欢迎大家一起交流。

第一棒 BIOS

首先我们要先明白计算机的启动过程,在 x86 模式下,开机的一瞬间,cpu 的 cs:ip 寄存器被强制初始化为 0xF000:0xFFF0 ,此时所指向的地址即 0xf000*16+0xfff0=0xffff0​,而在开机时,cpu 处于实模式下,即内存地址是唯一的,直接操作物理地址,此时内存布局是这样的(这个图很重要,后面还会用到):

image

可以看到,最大的地址就是 FFF0:FFFF ,即 0xFFFFF,所以我们开机时 cs:ip 的指向就差 16 个字节就到顶了,所以我们 BIOS 的代码肯定不在这里,那么这里也肯定是个跳转指令,跳转到真正的 BIOS 代码处,那么我们去验证一下,去 bochs 里面调一下,在开机一瞬间(x86):

image

可以清楚的看到 cs:ip 的指向以及所对应的汇编代码,确定是 jmp 指令,并且跳转到 0xfe05b

接着 BIOS 便紧接着检测内存、显卡等外设信息,当检测通过,并初始化好硬件后,开始在内存中 0x000~0x3FF 处建立数据结构,中断向量表 IVT 并填写中断例程,紧接着到了交接力棒的时候,将接力棒交给 mbr

bios--->(f000:fff0)jmp---> 检测内存、显卡等外设信息,初始化硬件,中断向量--->jmp 0:0x7c00 (mbr 程序)

第二棒 MBR

再说 mbr 之前,继续详细说一下 BIOS 的作用,下面这段话是我抄的 kanshanxd 博主的,他的文章和视频也是很好的参考资料

BIOS有四个主要功能:
1、硬件自检;
2、建立一些需要用到的数据结构与中断向量表;
3、校验启动盘中位于0盘0道1扇区(其实就是0扇区,只不过CHS方法用1开头)的内容,校验这里是不是放着主引导记录MBR,校验方法是检测这个扇区最后两个字节是不是0x55与0xaa(所以我们编写的主引导记录MBR最后两个字节应该是这两个);
4、在3的基础上是,那么就将该扇区内容加载至0x7c00内存处,这个位置是由于历史遗留导致的兼容,由最初的操作系统本身所占内存大小与布局所决定(书p58)。加载完毕后,然后跳转过去执行。

我们可以看到 mbr 所在扇区应该是 0 盘 0 道 1 扇区,并且这个扇区最后两个字节必须是 0x55 和 0xaa,并且扇区最终会被加载到 x7c00 内存处,关于这几个硬编码我们来一个一个解释。

0 盘 0 道 1 扇区以及 0x55 和 0xaa 的由来

首先我们要明确扇区表示法有两种,CHS 和 LBA,我们这里采用的是 CHS,而 CHS 的扇区从 0 开始,所以 0 盘 0 道 1 扇区也是 0 盘 0 道 0 扇区,知道这个意思就行,而两个魔数 0x55 和 0xaa,如果此扇区的最后两个字节是这两个魔数,BIOS 就认为 MBR 就在这个扇区里面,所以 BIOS 要挨个磁盘进行寻找,直到找到魔数,就认为找到了主引导程序(MBR),所以我们的想法是 MBR 和 BIOS 离得越近越好,而 0 盘 0 道 1 扇区就是第一个扇区,所以我们干脆将 MBR 放入这个扇区了。

0x7c00 的由来

首先我们要知道 MBR 不是放到哪里都行的,首先不能覆盖别的数据,也不能被后来的数据过早覆盖,但我们 mbr 交过接力棒之后是可以被覆盖的,我们根据上面的内存布局再来看一下内存情况:

8086CPU 要求物理地址 0x0~0x3FF 存放中断向量表,所以此处不能动了,再选新的地方看看。
按 DOS 1.0 要求的最小内存 32KB 来说,MBR 希望给人家尽可能多的预留空间,这样也是保全自己的作法,免得过早被覆盖。
所以 MBR 只能放在 32KB 的末尾。
MBR 本身也是程序,是程序就要用到栈,栈也是在内存中的,MBR 虽然本身只有 512 字节,但还要为其所用的栈分配点空间,所以其实际所用的内存空间要大于 512 字节,估计 1KB 内存够用了。
结合以上三点,选择32KB中的最后1KB最为合适,那此地址是多少呢?32KB换算为十六进制为0x8000,减去 1KB(0x400)的话,等于 0x7c00。这就是倍受质疑的 0x7c00 的由来,这下清楚了。

可见,加载 MBR 的位置取决于操作系统本身所占内存大小和内存布局。

小总结

1、BIOS 基本输入输出,检查各种设备的情况,并且建立中断向量表,填写中断例程,某些非常实用的中断是由 BIOS 提供的。

BIOS 将 0 柱面 0 磁头 1 扇区的数据转移到了 0x7c00 位置, 并将 CS:IP 指向了该块位置 ,默认认为 0 盘 0 道 1 扇区的就是启动盘, 且校验方式为看最后两个字节的魔数 是否为 0x55 和 0xaa ,若是的话则校验成功。
2、MBR 接到了接力棒, 作为主引导程序, 发挥自己的作用。

代码部分

代码分析

接下来简单实现一个 MBR 的代码,MBR 的作用是加载某个程序,而这个程序就是内核加载器,我们现在先简单的让 MBR 在屏幕打印字符“1 MBR”

先把代码贴上,我们再进行解读

;主引导程序 
;------------------------------------------------------------
SECTION MBR vstart=0x7c00        ;起始地址  0x7c00
    mov ax,cs
    mov ds,ax
    mov es,ax
    mov ss,ax
    mov fs,ax
    mov sp,0x7c00       ;初始化栈
    ;这个时候 ds = es = ss = 0 栈指针指向MBR开始位置

    ;ah = 0x06 al = 0x00 想要调用int 0x06的BIOS提供的中断对应的函数 即向上移动即完成清屏功能
    ;cx dx 分别存储左上角与右下角的左边 详情看int 0x06函数调用
    mov ax,0x0600
    mov bx,0x700

    ;(CL,CH) = 窗口左上角的(X,Y)位置
    ;(DL,DH) = 窗口右下角的(X,Y)位置
    mov cx,0
    mov dx,0x184f
  
    ;调用BIOS中断
    int 0x10 

    ;;;;;;;;;    下面这三行代码是获取光标位置    ;;;;;;;;;
    ;.get_cursor获取当前光标位置,在光标位置处打印字符.
   mov ah, 3		; 输入: 3号子功能是获取光标位置,需要存入ah寄存器
   mov bh, 0		; bh寄存器存储的是待获取光标的页号

   int 0x10		; 输出: ch=光标开始行,cl=光标结束行
			; dh=光标所在行号,dl=光标所在列号

    ;  打印字符串  
   ;还是用10h中断,不过这次是调用13号子功能打印字符串
   mov ax, message 
   mov bp, ax		; es:bp 为串首地址, es此时同cs一致,
			; 开头时已经为sreg初始化

   ; 光标位置要用到dx寄存器中内容,cx中的光标位置可忽略
   mov cx, 5		; cx 为串长度,不包括结束符0的字符个数
   mov ax, 0x1301	; 子功能号13是显示字符及属性,要存入ah寄存器,
			; al设置写字符方式 ah=01: 显示字符串,光标跟随移动
   mov bx, 0x2		; bh存储要显示的页号,此处是第0页,
			; bl中是字符属性, 属性黑底绿字(bl = 02h)
   int 0x10		; 执行BIOS 0x10 号中断
    ;;;;;;;;;      打字字符串结束	 ;;;;;;;;;;;;;;;

    jmp $		; 死循环

    ;字符串声明 db == define byte dw == define word ascii一个字符占一个字节
    message db "1 MBR" 

    ;预留两个字节 其余空余的全部用0填满 为使检测当前扇区最后两字节为0x55 0xaa 检测是否为有效扇区
    ;510 = 512字节-2预留字节  再减去(当前位置偏移量-段开始位置偏移量)求出来的是剩余空间
    times 510 - ($ - $$) db 0 
    db 0x55,0xaa

注释很详细了,这里挑重点说:

首先是这一部分

image

我们将 vstart 设置为 0x7c00,这代表我们告诉编译器我们这段程序将被放到 0x7c00 处执行,编译器再编译时就会将第一行假装在偏移 0x7c00 处执行,而我们也确定 mbr 会在 0x7c00 处执行。

此时 cs 的值是 0,因为 BIOS 是通过 jmp 0:0x7c00 执行的,cs 的值肯定是 0,所以用它来初始化别的段寄存器

sp 的初始值被初始化 0x7c00,sp 是栈指针,向低地址发展,所以这样我们就在内存中形成了这样的内存布局:

image

接下来进行清屏,因为 BIOS 会有一些输出,所以要进行清屏。

这里利用了 BIOS 中断,具体可以参考 BIOS 中断大全(表格)_bios 功能调用表格-CSDN 博客,第一行将 ah 的值赋位 0x06,al 的值赋位 0x00,然后结合注释看就可以了

image

然后获取光标打印字符串,这里用到的都是 BIOS 中断,都是看说明就好了,这里不再解释

image

image

看一下最后的处理首先是 jmp $​,而 $ 就是指当前行地址,就是一直重复在这里 jmp,保证我们能看到上面的输出,万一我们的输出又被别的什么东西覆盖了呢

接下来 message db "1 MBR ​则是声明了一个字符串,也是打印的字符串,位置就是 message,在上面打印中断时用到了

times 510-($-$$) db 0 ​则是从当前处到离最后还有两个位置一直填充 0,$-$$ ​就是当前段的起始地址-当前行的地址,其实就是在当前磁盘已经用了多少字节,然后用 510 减去这个字节数是将当前行到 510 一直填充 0

最后 db 0x55,0xaa ​则是填上魔数,让 0x55 和 0xaa 这两个魔数恰好出现在该扇区的最后两个字节处,,让 BIOS 认识我们 MBR 程序.

image

编译脚本

利用 nasm 进行编译

nasm -o mbr.bin mbr.S 

然后利用 dd 命令将我们编译出来的程序放到对应位置,if 代表读取的文件,of 代表写入的文件,bs 代表块大小 512 字节,seek 代表输出到文件时我们想要跳过几个字节,count 代表写入的块数,conv 代表如何转换文件,notrunc 即不打断文件

dd if=./mbr.bin of=../../hd60M.img bs=512 seek=0 count=1 conv=notrunc

去 bochs 中模拟执行,可以得到如下结果

image

posted @ 2025-01-03 18:31  fdx_xdf  阅读(13)  评论(0编辑  收藏  举报