一 部署工作环境

开发环境:Ubuntu 20.04 LTS

首先是需要安装的东西:

1
2
3
sudo apt install build-essential
sudo apt-get install libghc-x11-dev
sudo apt-get install xorg-dev

下载Bochs https://udomain.dl.sourceforge.net/project/bochs/bochs/2.6.8/bochs-2.6.8.tar.gz

下载完毕之后将其移动至虚拟机中想要的位置,然后解压,命令:tar -zxvf bochs-2.6.8.tar.gz

为即将要安装的bochs创建一个空目录

1
mkdir bochs

进入解压后的bochs-2.6.8文件夹 cd bochs-2.6.8

配置bochs的config文件(–prefix这后面填的是你想要安装bochs的目录),编译,安装

./configure –prefix=/home/ubuntu/Desktop/bochs –enable-debugger –enable-disasm –enable-iodebug –enable-x86-debugger –with-x –with-x11 LDFLAGS=’-pthread’

1
2
3
make

make install

进入安装好bochs的目录 cd .. cd bochs创建bochsrc.disk 命令: touch bochsrc.disk,在其中写下配置信息(修改romimage:、romimage:、 keyboard:后面路径信息的前部分为自己的bochs安装路径)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
megs : 32

romimage: file=/home/ubuntu/Desktop/bochs/share/bochs/BIOS-bochs-latest
vgaromimage: file=/home/ubuntu/Desktop/bochs/share/bochs/VGABIOS-lgpl-latest

boot: disk

log: bochs.out

mouse:enabled=0
keyboard:keymap=/home/ubuntu/Desktop/bochs/share/bochs/keymaps/x11-pc-us.map

ata0:enabled=1,ioaddr1=0x1f0,ioaddr2=0x3f0,irq=14
ata0-master: type=disk, path="hd60M.img", mode=flat,cylinders=121,heads=16,spt=63

#gdbstub:enabled=1,port=1234,text_base=0,data_base=0,bss_base=0

创建启动磁盘

1
bin/bximage

然后在输入框依次输入以下,输入一个,按一次回车

1
2
3
4
5
1
hd
flat
60
hd60M.img

测试代码,以下内容用作测试:

1
2
cd ..
touch mbr.s
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
SECTION MBR vstart=0x7c00
mov ax,0x0000 ;设置栈指应该是程序一开始就应该做的事情,这个值是参照1m内存空间布局图选择的,以后会刻意避开
mov ss,ax
mov ax,0x7c00
mov sp,ax

mov ax,0x0600
mov bx,0x0700 ;BH是设置缺省属性,属性是指背景色,前景色,是否闪烁等,例如07H表示黑底白字,70H表示灰底黑字等等。
mov cx,0x0000
mov dx,0x184f ;这个看书p61,同时看其中关于页的知识
int 0x10

mov ax,0x0300
mov bx,0x0000
int 0x10

mov ax,0x0000
mov es,ax
mov ax,message
mov bp,ax
mov ax,0x1301
mov bx,0x0007 ;设置字体属性,02是黑底绿字,07是黑底白字
mov cx,0x000c
int 0x10

jmp $
message db "Hello World!"
times 510-($-$$) db 0
db 0x55,0xaa

安装编译器nasm 命令:

1
sudo apt install nasm

编译

1
nasm -o test mbr.s

写入虚拟机启动磁盘

1
dd if=/home/ubuntu/Desktop/test of=/home/ubuntu/Desktop/bochs/hd60M.img bs=512 count=1 conv=notrunc

启动虚拟机查看效果(在bochs目录下)

1
2
3
cd bochs

bin/bochs -f bochsrc.disk

启动之后,输入c即可看见Hello world!

二 编写 MBR 主引导记录,让我们开始掌权

2.1 计算机的启动过程

任何程序执行必须先载入内存,所谓的载入内存,大概上分为两个部分。

(1)程序被加载器(软件或硬件)加载到内存某个区域。

(2)CPU 的 cs:ip 寄存器被指向这个程序的起始地址

从按下主机上的 power 键后,第一个运行的软件是 BIOS。

2.2 软件接力第一棒,BIOS

BIOS 全称叫 Base Input & Output System,即基本输入输出系统。

2.2.1 实模式下的 1MB 内存布局

这里我们举例Intel 8086在实模式下的内存布局

先从低地址看,地址 0~0x9FFFF 处是 DRAM(Dynamic Random Access Memory),即动态随机访问 内存,我们所装的物理内存就是 DRAM。

看顶部的 0xF0000~0xFFFFF,这 64KB 的内 存是 ROM。这里面存的就是 BIOS 的代码。BIOS 的主要工作是检测、初始化硬件,怎么初始化的?硬件自己提 供了一些初始化的功能调用,BIOS 直接调用就好了。BIOS 还做了一件伟大的事情,建立了中断向量表,这样 就可以通过“int 中断号”来实现相关的硬件调用,当然 BIOS 建立的这些功能就是对硬件的 IO 操作,也就是输 入输出,但由于就 64KB 大小的空间,不可能把所有硬件的 IO 操作实现得面面俱到,而且也没必要实现那么多, 毕竟是在实模式之下,对硬件支持得再丰富也白搭,精彩的世界是在进入保护模式以后才开始,所以挑一些重要 的、保证计算机能运行的那些硬件的基本 IO 操作,就行了。这就是 BIOS 称为基本输入输出系统的原因。

在CPU眼里,插在主板上的物理内存并不是它眼里的“全部的内存”,归根结底的原因是这样的:在计算机中,并不是只有咱们插在主板上的内存条需要通过地址总线访问, 还有一些外设同样是需要通过地址总线来访问的,这类设备还很多呢。若把全部的地址总线都指向物理内 存,那其他设备该如何访问呢?由于这个原因,只好在地址总线上提前预留出来一些地址空间给这些外设 用,这片连续的地址给显存,这片连续的地址给硬盘控制器等。留够了以后,地址总线上其余的可用地址 再指向 DRAM,也就是指插在主板上的内存条、我们眼中的物理内存。

2.2.2 BIOS 是如何苏醒的

BIOS 本身是个程序,程序要执行,就要有个入口地址才行,此入口地址便是 0xFFFF0。在开机的一瞬间,也就是接电的一瞬间,CPU 的 cs:ip 寄存器被强制初始化为 0xF000:0xFFF0。由于 开机的时候处于实模式,再重复一遍加深印象,在实模式下的段基址要乘以16,也就是左移4位,于是0xF000: 0xFFF0 的等效地址将是 0xFFFF0。上面说过了,此地址便是 BIOS 的入口地址。但是此处并不是真正的代码,因为由于实模式下的寄存器宽度是 16 位,0xFFFF0+16 已经超过了其最大值 0xFFFFF。溢出的部分就会回卷到 0,又会重新开始,即 0xFFFF0+16 等于 0,0xFFFF0+17 等于 1。所以此处的代码为跳转代码。真正的代码储存在跳转代码的所跳转的位置,接下来 BIOS 便马不停蹄地检测内存、显卡等外设信息,当检测通过,并初始化好硬件后,开始在内 存中 0x000~0x3FF 处建立数据结构,中断向量表 IVT 并填写中断例程。

2.2.3 为什么是 0x7c00

计算机执行到这份上,BIOS 也即将完成自己的历史使命了,BIOS 最后一项工作校验启动盘中位于 0 盘 0 道 1 扇区的内容。如果此扇区末尾的两个字节分别是魔数 0x55 和 0xaa,BIOS 便认为此扇区中确实存在可执 行的程序(在此先剧透一下,此程序便是久闻大名的主引导记录 MBR),便加载到物理地址 0x7c00,随 后跳转到此地址,继续执行。至于为什么是 0x7c00,8086CPU 要求物理地址 0x0~0x3FF 存放中断向量表,所以此处不能动了,再选新的地方看看。 按 DOS 1.0 要求的最小内存 32KB 来说,MBR 希望给人家尽可能多的预留空间,这样也是保全自己 的作法,免得过早被覆盖。所以 MBR 只能放在 32KB 的末尾。 MBR 本身也是程序,是程序就要用到栈,栈也是在内存中的,MBR 虽然本身只有 512 字节,但还要为其 所用的栈分配点空间,所以其实际所用的内存空间要大于 512 字节,估计 1KB 内存够用了。 结合以上三点,选择32KB中的最后1KB最为合适,那此地址是多少呢?32KB换算为十六进制为0x8000, 减去 1KB(0x400)的话,等于 0x7c00。这就是倍受质疑的 0x7c00 的由来,这下清楚了。 可见,加载 MBR 的位置取决于操作系统本身所占内存大小和内存布局。

2.3 让 MBR 先飞一会儿

MBR 的大小必须是 512 字节,这是为了保证 0x55 和 0xaa 这两个 魔数恰好出现在该扇区的最后两个字节处,即第 510 字节处和第 511 字节处,这是按起始偏移为 0 算起的。 由于我们的 bochs 模拟的是 x86 平台,所以是小端字节序,故其最后两个字节内容是 0xaa55

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
                                ;主引导程序 
;------------------------------------------------------------
SECTION MBR vstart=0x7c00
mov ax,cs ;此时cs寄存器为0,自然可以用来将ax寄存器置0
mov ds,ax
mov es,ax
mov ss,ax
mov fs,ax
mov sp,0x7c00

; 清屏 利用0x06号功能,上卷全部行,则可清屏。
; -----------------------------------------------------------
;INT 0x10 功能号:0x06 功能描述:上卷窗口
;------------------------------------------------------
;输入:
;AH 功能号= 0x06
;AL = 上卷的行数(如果为0,表示全部)
;BH = 上卷行属性
;(CL,CH) = 窗口左上角的(X,Y)位置
;(DL,DH) = 窗口右下角的(X,Y)位置
;无返回值:
mov ax, 0x600 ;ah中输入功能号
mov bx, 0x700 ;设置上卷行属性,0x70表示用黑底白字的属性填充空白行
mov cx, 0 ;左上角: (0, 0)
mov dx, 0x184f ;右下角: (80,25),
;VGA文本模式中,一行只能容纳80个字符,共25行。
;下标从0开始,所以0x18=24,0x4f=79
int 0x10 ;int 0x10

;;;;;;;;; 下面这三行代码是获取光标位置 ;;;;;;;;;
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,07是黑底白字)
int 0x10 ; 执行BIOS 0x10 号中断
;;;;;;;;; 打字字符串结束 ;;;;;;;;;;;;;;;

jmp $ ; 使程序悬停在此

message db "1 MBR"
times 510-($-$$) db 0
db 0x55,0xaa


注:使用dd命令时需要重新创建硬盘

创建启动磁盘

1
bin/bximage

然后在输入框依次输入以下,输入一个,按一次回车

1
2
3
4
5
1
hd
flat
60
hd60M.img
1
nasm -o mbr mbr.s

然后用dd命令写入bochs的虚拟硬盘,命令:

1
dd if=/home/ubuntu/Desktop/test of=/home/ubuntu/Desktop/bochs/hd60M.img bs=512 count=1 conv=notrunc

启动,之后默认为6输入c

1
bin/bochs –f bochsrc.disk

可以看到有结果出现

三 完善MBR

3.1 地址,section,vstart浅尝辄止

3.1.1 什么是地址

地址只是数字,描述各种符号在源程序中的位置,它是源代码文件中各符号偏移文件开头的距离。由 于指令和变量所占内存大小不同,故它们相对于文件开头的偏移量参差不齐。源文件就像旅店一样,里面 的符号(指令、变量等)就像旅店里的房间,有单人间、双人间,虽然大小不同,但它们也需要被旅店管 理员编号,也就是每个房间都有房间号,这样房客通过房间号便能找到自己的住所。

3.1.2 什么是section

简单点来说就是方便开发人员理清程序用的,关键字 section 并没有对程序中的地址产生任何影响,即在默 认情况下,有没有 section 都一个样,section 中数据的地址依然是相对于整个文件的顺延,仅仅是在逻辑 上让开发人员梳理程序之用

3.1.3 什么是vstart

vstart 的作用是为 section 内的数据指定 个虚拟的起始地址,也就是根据此地址,在文件中是找不到 相关数据的,是虚拟的,假的,文件中的所有符号都不在这个地址上。vstart 只是告诉编译器以新的数字作为后面数据的地址的起始值,它本身没改变数据本 身在文件中的地址(相对于文件开头的偏移〉