我用的环境是vmware16+centos7.9。
安装前环境依赖:
sudo yum -y install gtk2 gtk2-devel
sudo yum -y install libXt libXt-devel
sudo yum -y install libXpm libXpm-devel
sudo yum -y install SDL SDL-devel
sudo yum -y install libXrandr-devel.x86_64
sudo yum -y install xorg-x11-server-devel
sudo yum -y install gcc-c++
安装bochs:
wget https://jaist.dl.sourceforge.net/project/bochs/bochs/2.6.2/bochs-2.6.2.tar.gz
tar -xvzf bochs-2.6.2.tar.gz
cd bochs-2.6.2
三部曲一:
这里注意一下,在- -prefix=/usr/local/share/bochs \ 命令中,将/usr/local/share/bochs换成自己的路径。我的是下载且解压缩之后,默认给我放到/usr/local/share/bochs这里的,也可以自己该。
./configure \
--prefix=/usr/local/share/bochs \
--enable-debugger \
--enable-disasm \
--enable-iodebug \
--enable-x86-debugger \
--with-x \
--with-x11 \
LDFLAGS='-pthread' \
LIBS='-lX11'
三部曲二:
在当前目录下(应该是bochs-2.6.2)执行make指令。
[root@zklEdu bochs-2.6.2]# make
三部曲三:
没问题的话紧接着执行make install指令。
[root@zklEdu bochs-2.6.2]# make install
配置bochs:
搞完三部曲之后就要搞bochsrc.disk了。
vim bochsrc.disk
在bochsrc.disk中键入以下内容(个人建议自已亲自敲一下,直接从网上复制的很可能不靠谱,都是泪~):
megs: 32
romimage: file=/usr/local/share/bochs/share/bochs/BIOS-bochs-latest
vgaromimage: file=/usr/local/share/bochs/share/bochs/VGABIOS-lgpl-latest
boot: disk
log: bochs.out
mouse: enabled=0
keyboard_mapping: enabled=1, map=/usr/local/share/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、这里的 romimage: file=/usr/local/share/bochs/share/bochs/BIOS-bochs-latest和
vgaromimage: file=/usr/local/share/bochs/share/bochs/VGABIOS-lgpl-latest,路径要注意,跟上面- -prefix=/usr/local/share/bochs \指令中的路径一样,我的都是/usr/local/share/bochs,然后后面的都一样。
2、这里的 ata0-master: type=disk, path=“hd60M.img”, mode=flat, cylinders=121, heads=16, spt=63,因为后面创建硬盘还是会加的,这里就一起写了。
3、这里的 #gdbstub: enabled=1, port=1234, text_base=0, data_base=0, bss_base=0是把这句话注掉了,因为前面说了不要打开- -enable-gdb-stub,所以这里不注释掉,会报错。(吐槽一下书上没注释掉这句话,我傻傻地照着书抄,虽然前面说了 T-T)
然后就是创建磁盘了:
我是直接键入bin/bximage,然后照着提示往下做,提示挺清晰的。
依次键入hd、flat、60M、hd60M.img。
运行bochs:
bin/bochs -f bochsrc.disk
有意思的来了,会发现出现的界面跟书上不一样,只有一大块黑框:
这时需要再键入一个小c(吐槽哇~还好有万能的网友):
然后才会出现想要的界面(一个panic T-T):
再接下来就是MBR的事了:
首先:
yum install -y nasm
然后编写mbr.S,其内容为:
SECTION MBR vstart=0x7c00
mov ax,cs
mov ds,ax
mov es,ax
mov ss,ax
mov fs,ax
mov sp,0x7c00
mov ax, 0x600
mov bx, 0x700
mov cx, 0
mov dx, 0x184f
int 0x10
mov ah, 3
mov bh, 0
int 0x10
mov ax, message
mov bp, ax
mov cx, 48
mov ax, 0x1301
mov bx, 0x2
int 0x10
jmp $
message db"I believe I will succeed in the end"
times 510-($-$$) db 0
db 0x55,0xaa
接着就是编译:
nasm -o mbr.bin mbr.S
再然后就是用dd了(还是老问题,注意路径):
dd if=/usr/local/share/mbr.bin of=/usr/local/share/bochs/hd60M.img bs=512 count=1 conv=notrunc
然后运行bochs(别忘了那个小写的c):
bin/bochs -f bochsrc.disk
看成品(真心不容易 T-T):
让MBR使用硬盘:
在mbr.S同级目录下创建include目录,并在include目录下创建boot.inc,其内容为:
LOADER_BASE_ADDR equ 0x900
LOADER_START_SECTOR equ 0x2
然后就是修改原来的mbr.S,新的mbr.S的内容为:
;主引导程序
;------------------------------------------------------
%include "boot.inc"
SECTION MBR vstart=0x7c00
mov ax,cs
mov ds,ax
mov es,ax
mov ss,ax
mov fs,ax
mov sp,0x7c00
mov ax,0xb800 ;用于文本模式显示适配器
mov gs,ax
;清屏利用0x06号功能
; -----------------------------------------------------------
;INT 0x10 功能号:0x06 功能描述:上卷窗口
;------------------------------------------------------
;输入:
;AH 功能号= 0x06
;AL = 上卷的行数(如果为0,表示全部)
;BH = 上卷行属性
;(CL,CH) = 窗口左上角的(X,Y)位置
;(DL,DH) = 窗口右下角的(X,Y)位置
;无返回值:
mov ax,0x600
mov bx,0x700
mov cx,0 ;左上角(0,0)
mov dx,0x184f ;右下角(80,25)
; VGA文本模式中,一行只能容纳80个字符,共25行。
; 下标从0开始,所以0x18=24,0x4f=79
int 0x10 ; int 0x10
;输出背景色绿色,前景色红色,并且跳动的字符串
mov byte[gs:0x00],'1'
mov byte[gs:0x01],0xA4
mov byte[gs:0x02],' '
mov byte[gs:0x03],0xA4
mov byte[gs:0x04],'M'
mov byte[gs:0x05],0xA4
mov byte[gs:0x06],'B'
mov byte[gs:0x07],0xA4
mov byte[gs:0x08],'R'
mov byte[gs:0x09],0xA4
mov eax,LOADER_START_SECTOR ;起始扇区的lba地址
mov bx,LOADER_BASE_ADDR ;写入的地址
mov cx,1 ;待读入的扇区数
call rd_disk_m_16 ;以下读取程序的起始部分(一个扇区)
jmp LOADER_BASE_ADDR
; -----------------------------------------------------
;功能:读取硬盘n个扇区
rd_disk_m_16:
;eax=LBA扇区号
;bx=将数据写入的内存地址
;cx=读入的扇区数
;------------------------------------------------------
mov esi,eax ;备份eax
mov di,cx ;备份cx
;读写硬盘:
;第1步:设置要读取的扇区数
mov dx,0x1f2
mov al,cl
out dx,al ;读取的扇区数
mov eax,esi ;恢复ax
;第2步:将LBA地址写入0x1f3~0x1f6
;LBA地址7~0位写入0x1f3
mov dx,0x1f3
out dx,al
;LBA地址15~8位写入0x1f4
shr eax,8
mov dx,0x1f4
out dx,al
;LBA地址23~16位写入端口0x1f5
shr eax,8
mov dx,0x1f5
out dx,al
;LBA地址24~27位 device
shr eax,8
and al,0x0f ;lba第24~27位
or al,0xe0 ;设置7~4位为1110表示lba模式
mov dx,0x1f6
out dx,al
;第3步:向0x1f7端口写入读命令,0x20
mov dx,0x1f7
mov al,0x20
out dx,al
;第4步:检查硬盘状态
.not_ready:
;同一端口,写时表示写入命令字,读时标色读入硬盘状态
nop
in al,dx
and al,0x88 ;第4位为1表示硬盘控制器
;第7位为1表示硬盘忙
cmp al,0x08
jnz .not_ready ;若未准备好,继续等待
;第5步:从0x1f0端口读取数据
mov ax,di
mov dx,256
mul dx ;每次读入一个字,需要256*扇区数
mov cx,ax
mov dx,0x1f0
.go_on_read:
in ax,dx
mov [bx],ax
add bx,2
loop .go_on_read
ret
; -----------------------------------------------------
;功能:填充510+0x55+0xaa
;------------------------------------------------------
times 510-($-$$) db 0
db 0x55,0xaa
接着就是编译了:
nasm -I include/ -o mbr.bin mbr.S
再然后就是用dd命令将mbr.bin写入虚拟硬盘(注意路径):
dd if=./mbr.bin of=/usr/local/share/bochs/hd60M.img bs=512 count=1 conv=notrunc
然后就是实现简单的内核加载器,loader.S的内容为:
%include "boot.inc"
section loader vstart=LOADER_BASE_ADDR
mov byte[gs:0x00],'1'
mov byte[gs:0x01],0xA4
mov byte[gs:0x02],' '
mov byte[gs:0x03],0xA4
mov byte[gs:0x04],'L'
mov byte[gs:0x05],0xA4
mov byte[gs:0x06],'O'
mov byte[gs:0x07],0xA4
mov byte[gs:0x08],'A'
mov byte[gs:0x09],0xA4
mov byte[gs:0x0a],'D'
mov byte[gs:0x0b],0xA4
mov byte[gs:0x0c],'E'
mov byte[gs:0x0d],0xA4
mov byte[gs:0x0e],'R'
mov byte[gs:0x0f],0xA4
jmp $
还是要编译:
nasm -I include/ -o loader.bin loader.S
用dd命令(注意路径):
dd if=./loader.bin of=/usr/local/share/bochs/hd60M.img bs=512 count=1 seek=2 conv=notrunc
回到bochs目录中,然后运行bochs:
bin/bochs -f bochsrc.disk
接下来是进入保护模式:
首先要修改的是mbr.S,将其中的读入扇区数部分进行修改:
mov cx,4 ;原来的是 mov cx,1
call rd_disk_m_16
完整的新的mbr.S代码为:
;主引导程序
;------------------------------------------------------
%include "boot.inc"
SECTION MBR vstart=0x7c00
mov ax,cs
mov ds,ax
mov es,ax
mov ss,ax
mov fs,ax
mov sp,0x7c00
mov ax,0xb800 ;用于文本模式显示适配器
mov gs,ax
;清屏利用0x06号功能
; -----------------------------------------------------------
;INT 0x10 功能号:0x06 功能描述:上卷窗口
;------------------------------------------------------
;输入:
;AH 功能号= 0x06
;AL = 上卷的行数(如果为0,表示全部)
;BH = 上卷行属性
;(CL,CH) = 窗口左上角的(X,Y)位置
;(DL,DH) = 窗口右下角的(X,Y)位置
;无返回值:
mov ax,0x600
mov bx,0x700
mov cx,0 ;左上角(0,0)
mov dx,0x184f ;右下角(80,25)
; VGA文本模式中,一行只能容纳80个字符,共25行。
; 下标从0开始,所以0x18=24,0x4f=79
int 0x10 ; int 0x10
;输出背景色绿色,前景色红色,并且跳动的字符串
mov byte[gs:0x00],'2'
mov byte[gs:0x01],0xA4
mov byte[gs:0x02],' '
mov byte[gs:0x03],0xA4
mov byte[gs:0x04],'M'
mov byte[gs:0x05],0xA4
mov byte[gs:0x06],'B'
mov byte[gs:0x07],0xA4
mov byte[gs:0x08],'R'
mov byte[gs:0x09],0xA4
mov eax,LOADER_START_SECTOR ;起始扇区的lba地址
mov bx,LOADER_BASE_ADDR ;写入的地址
mov cx,4 ;待读入的扇区数
call rd_disk_m_16 ;以下读取程序的起始部分(一个扇区)
jmp LOADER_BASE_ADDR
; -----------------------------------------------------
;功能:读取硬盘n个扇区
rd_disk_m_16:
;eax=LBA扇区号
;bx=将数据写入的内存地址
;cx=读入的扇区数
;------------------------------------------------------
mov esi,eax ;备份eax
mov di,cx ;备份cx
;读写硬盘:
;第1步:设置要读取的扇区数
mov dx,0x1f2
mov al,cl
out dx,al ;读取的扇区数
mov eax,esi ;恢复ax
;第2步:将LBA地址写入0x1f3~0x1f6
;LBA地址7~0位写入0x1f3
mov dx,0x1f3
out dx,al
;LBA地址15~8位写入0x1f4
shr eax,8
mov dx,0x1f4
out dx,al
;LBA地址23~16位写入端口0x1f5
shr eax,8
mov dx,0x1f5
out dx,al
;LBA地址24~27位 device
shr eax,8
and al,0x0f ;lba第24~27位
or al,0xe0 ;设置7~4位为1110表示lba模式
mov dx,0x1f6
out dx,al
;第3步:向0x1f7端口写入读命令,0x20
mov dx,0x1f7
mov al,0x20
out dx,al
;第4步:检查硬盘状态
.not_ready:
;同一端口,写时表示写入命令字,读时标色读入硬盘状态
nop
in al,dx
and al,0x88 ;第4位为1表示硬盘控制器
;第7位为1表示硬盘忙
cmp al,0x08
jnz .not_ready ;若未准备好,继续等待
;第5步:从0x1f0端口读取数据
mov ax,di
mov dx,256
mul dx ;每次读入一个字,需要256*扇区数
mov cx,ax
mov dx,0x1f0
.go_on_read:
in ax,dx
mov [bx],ax
add bx,2
loop .go_on_read
ret
; -----------------------------------------------------
;功能:填充510+0x55+0xaa
;------------------------------------------------------
times 510-($-$$) db 0
db 0x55,0xaa
另一个要更新的文件是boot.inc,新的完整代码如下:
;-------- loader & kernel --------
LOADER_BASE_ADDR equ 0x900
LOADER_START_SECTOR equ 0x2
;-------- gdt描述符属性 --------
DESC_G_4K equ 10000000_00000000_00000000b
DESC_D_32 equ 01000000_00000000_00000000b
DESC_L equ 00100000_00000000_00000000b;64位代码标记,此处标记位0便可
DESC_AVL equ 00000000_00000000_00000000b;暂时没用设为0
DESC_LIMIT_CODE2 equ 1111_00000000_00000000b
DESC_LIMIT_DATA2 equ DESC_LIMIT_CODE2
DESC_LIMIT_VIDEO2 equ 0000_00000000_00001011b
DESC_P equ 10000000_00000000b
DESC_DPL_0 equ 00000000_00000000b
DESC_DPL_1 equ 00100000_00000000b
DESC_DPL_2 equ 01000000_00000000b
DESC_DPL_3 equ 01100000_00000000b
DESC_S_CODE equ 00010000_00000000b
DESC_S_DATA equ DESC_S_CODE
DESC_S_SYS equ 00000000_00000000b
;x=1,c=0,r=0,a=0代码段是可执行的,非一致性,不可读,已访问位a清0
DESC_TYPE_CODE equ 1000_00000000b
;x=0,c=0,r=1,a=0数据段是不可执行的,向上扩张,可写,已访问位a清0
DESC_TYPE_DATA equ 0010_00000000b
DESC_CODE_HIGH4 equ (0x00<<24)+DESC_G_4K+DESC_D_32+DESC_L+DESC_AVL+DESC_LIMIT_CODE2+DESC_P+DESC_DPL_0+DESC_S_CODE+DESC_TYPE_CODE+0x00
DESC_DATA_HIGH4 equ (0x00<<24)+DESC_G_4K+DESC_D_32+DESC_L+DESC_AVL+DESC_LIMIT_DATA2+DESC_P+DESC_DPL_0+DESC_S_DATA+DESC_TYPE_DATA+0x00
DESC_VIDEO_HIGH4 equ (0x00<<24)+DESC_G_4K+DESC_D_32+DESC_L+DESC_AVL+DESC_LIMIT_VIDEO2+DESC_P+DESC_DPL_0+DESC_S_DATA+DESC_TYPE_DATA+0x00
;-------- 选择子属性 --------
RPL0 equ 00b
RPL1 equ 01b
RPL2 equ 10b
RPL3 equ 11b
TI_GDT equ 000b
TI_LDT equ 100b
最后就是loader.S,新的完整代码如下:
%include "boot.inc"
section loader vstart=LOADER_BASE_ADDR
LOADER_STACK_TOP equ LOADER_BASE_ADDR
jmp loader_start
;构建gdt及其内部的描述符
GDT_BASE: dd 0x00000000
dd 0x00000000
CODE_DESC: dd 0x0000FFFF
dd DESC_CODE_HIGH4
DATA_STACK_DESC:dd 0x0000FFFF
dd DESC_DATA_HIGH4
;B0000 B7FFF 32KB 黑白显示适配器
VIDEO_DESC: dd 0x80000007;limit=(0xbffff-0xb8000)/4k=0x7
dd DESC_VIDEO_HIGH4
GDT_SIZE equ $-GDT_BASE
GDT_LIMIT equ GDT_SIZE-1
times 60 dq 0;预留60个描述符空位
SELECTOR_CODE equ (0x0001<<3)+TI_GDT+RPL0
SELECTOR_DATA equ (0x0002<<3)+TI_GDT+RPL0
SELECTOR_VIDEO equ (0x0003<<3)+TI_GDT+RPL0
gdt_ptr dw GDT_LIMIT
dd GDT_BASE
loadermsg db '2 loader in real.'
loader_start:
; -----------------------------------------------------------
;INT 0x10 功能号:0x13 功能描述:打印字符串
;------------------------------------------------------
;输入:
;AH 功能号= 0x13H
;BH = 页码
;BL = 属性(若AL=00H或01H)
;CX = 字符串长度
;ES:BP=字符串地址
;AL=输出方式
; 0——字符串中只含显示字符,其显示属性在BL中。显示后,光标位置不变
; 1——字符串中只含显示字符,其显示属性在BL中。显示后,光标位置改变
; 2——字符串中含显示字符和显示属性。显示后,光标位置不变
; 3——字符串中含显示字符和显示属性。显示后,光标位置改变
;无返回值
mov sp,LOADER_BASE_ADDR
mov bp,loadermsg
mov cx,17
mov ax,0x1301 ;AH=13,AL=01
mov bx,0x001f ;页号为0,蓝底粉红字(BL=1fh)
mov dx,0x1800
int 0x10 ;10h号中断
;-------- 准备进入保护模式 --------
;1:打开A20
;2:加载gdt
;3:将cr0的pe位置1
;-------- 打开A20 --------
in al,0x92
or al,0000_0010B
out 0x92,al
;-------- 加载GDT --------
lgdt [gdt_ptr]
;-------- 将cr0的pe位置1 --------
mov eax,cr0
or eax,0x00000001
mov cr0,eax
jmp dword SELECTOR_CODE:p_mode_start;刷新流水线
[bits 32]
p_mode_start:
mov ax,SELECTOR_DATA
mov ds,ax
mov es,ax
mov ss,ax
mov esp,LOADER_STACK_TOP
mov ax,SELECTOR_VIDEO
mov gs,ax
mov byte[gs:160],'p'
mov byte[gs:161],0x1f
jmp $
编译:
nasm -I include/ -o mbr.bin mbr.S
nasm -I include/ -o loader.bin loader.S
写入虚拟硬盘:
dd if=./mbr.bin of=/usr/local/share/bochs/hd60M.img bs=512 count=1 conv=notrunc
dd if=./loader.bin of=/usr/local/share/bochs/hd60M.img bs=512 count=4 seek=2 conv=notrunc
回到bochs目录下运行:
bin/bochs -f bochsrc.disk