运行一个简单的操作系统
本文是基于《自己动手写操作系统》的第一章和第二章总结的。
源码编译
org 07c00h
mov ax, cs
mov ds, ax
mov es, ax
call DispStr
jmp $
DispStr:
mov ax, BootMessage
mov bp, ax
mov ax, 01301h
mov bx, 000ch
mov dl, 0
int 10h
ret
BootMessage: db "Hello, OS world!"
times 510-($-$$) db 0
dw 0xaa55
在linux
下用nasm
进行编译,得到boot.bin二进制文件。编译指令为:
nasm -o boot.bin boot.asm
镜像制作
通过linux
下的命令dd
制作镜像文件boot.img
,命令为:
dd if=boot.bin of=boot.img bs=512 count=1
虚拟机安装配置
本文采用VirtualBox
虚拟机软件,相关下载和安装操作可以参考博文。下面我们需要创建一个新的虚拟机:
然后我们需要将虚拟机设置为从我们制作的镜像boot.img
中启动:(1)添加软盘;(2)设置启动扫描顺序
运行
点击启动即可运行虚拟机。
但是不知为何只显示一个H,而不是整个字符串!!
源码分析
为了分析上述问题,我们将对源码展开分析。
反汇编
在linux
下采用ndisasm
将二进制文件boot.bin
反汇编1,观察其中具体执行的指令:
ndisasm -o 0x7c00 boot.bin >> disboot.asm
00007C00 8CC8 mov ax,cs
00007C02 8ED8 mov ds,ax
00007C04 8EC0 mov es,ax
00007C06 E80200 call 0x7c0b
00007C09 EBFE jmp short 0x7c09
首先是原汇编代码中的三条mov
指令,给数据段寄存器赋值。然后就调用位于0x7c0b
内存处的指令,也就是用来显示字符串的代码段DispStr
:
00007C0B B81B7C mov ax,0x7c1b
00007C0E 89C5 mov bp,ax
00007C10 B80113 mov ax,0x1301
00007C13 BB0C00 mov bx,0xc
00007C16 B200 mov dl,0x0
00007C18 CD10 int 0x10
00007C1A C3 ret
这段代码将字符串的内存地址0x7c1b
复制给了寄存器bp
,之后的几行代码的解释分别为2:
mov bp,ax
es:bp = 串地址.mov ax, 01301h
ah = 13,al = 01h,ah是设定服务模式,13h的意思是显示字符串。al设置光标位置,01h的意思是光标跟随字符串,具体参考博文。mov bx, 000ch
bx用来设置字符串的属性,颜色、闪烁、背景等等,这里表示页号为 0(bh = 0)黑底红字(bl = 0Ch,高亮)。mov dl, 0
将值 0 存储到寄存器 dl 中。这个值告诉计算机我们要将字符显示在屏幕的左上角。int 10h
这是一个中断指令,会将控制权转移给 BIOS。BIOS 根据先前设置的参数,在屏幕上显示字符。
因此可以知道,之所以只显示一个字符H,应该就是缺少了给cx寄存器赋值这一步,cx是设置字符串的长度。将DispStr
代码段改成如下所示:
DispStr:
mov ax, BootMessage
mov bp, ax
mov ax, 01301h
mov bx, 000ch
mov cx, 0010h ;- 设置cx为16个字符长度
mov dl, 0
int 10h
ret
重新操作一遍之后,就能正确地输出字符串了。
0x07C00
0x07C00
是BIOS
将引导程序加载到的内存地址,那么有一个问题就是,既然这部分代码已知必然会被加载到这个位置,那为什么汇编中还需要语句org 07c00h
?我们下面直接阅读编译的二进制文件:
#- 声明 org 07c00h
Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
00000000: 8C C8 8E D8 8E C0 E8 02 00 EB FE B8 1E 7C 89 C5 .H.X.@h..k~8.|.E
00000010: B8 01 13 BB 0C 00 B9 10 00 B2 00 CD 10 C3 48 65 8..;..9..2.M.CHe
00000020: 6C 6C 6F 2C 20 4F 53 20 77 6F 72 6C 64 21 00 00 llo,.OS.world!..
#- 未声明 org 07c00h
Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
00000000: 8C C8 8E D8 8E C0 E8 02 00 EB FE B8 1E 00 89 C5 .H.X.@h..k~8...E
00000010: B8 01 13 BB 0C 00 B9 10 00 B2 00 CD 10 C3 48 65 8..;..9..2.M.CHe
00000020: 6C 6C 6F 2C 20 4F 53 20 77 6F 72 6C 64 21 00 00 llo,.OS.world!..
可以看到,两种情况下,编译得到的二进制文件只存在微小的区别,即00000000:0D
处。该处的机器指令翻译过来为:mov ax 0x07c1e
和mov ax 0x0001e
,对应原汇编中的mov ax, BootMessage
。当程序加载到了0000:7c00
处,由于段内偏移了7c00
,此时字符串的起始位置就变成了7c00+1e=7c1e
。第一段程序由于二进制指令是B8 1E 7C
,因此会去0000:7c1e
寻找字符串,这是正确的。而第二段程序由于二进制指令是B8 1E 00
,而0000:001e
此时显然不是字符串所在地,因此会发生错误。综上,org 07c00h
这条指令是告诉汇编器,这段程序之后会被加载到段内偏移地址为7c00
的内存上,因此需要在编译的时候,把所有偏移地址都加上这个偏移,才能正确映射。
总结
- 将一段显示字符串的汇编代码编译成二进制,并制作成镜像文件;
- 安装虚拟机软件,加载镜像文件,输出字符串不符合预期;
- 通过反汇编发现,目前的10H中断需要额外指定
cx
寄存器的值; org 07c00h
的作用是告诉汇编器这段代码将会加载在段内偏移地址为7c00
处的内存中,因此需要将代码中所有地址都进行这个偏移。