操作系统入门(
五) -
载入32
位保护模式代码为c
做准备
经过上千次的试验,终于还是没有能够在vmware实现用一段程序载入kernel.img然后执行32位的kernel代码,无数次的跳出
无奈呀,后来知道实际上是因为gdt全局描述表没有正确载入造成的问题,至于能跳出这样提示的代码,我想没有人愿意去看吧,错的有什么好看的?
于是乎找来了鼎鼎大名的
Bochs来帮忙
http://bochs.sourceforge.net/
它能对自己做的操作系统进行调试,如果早些用它的话,我就不会等了3年才又再玩起操作系统来了,说不定已经出了一些成果了呢。
但是自己笨没有办法,还是多向别人学习吧。
前面做过的程序仅仅是把16位的kernel.img载入到0x90000地址的地方执行,如果要在kernel.img里面再设置保护模式,那么,那个时候还需要从磁盘提取数据的程序,一大堆,现在我发现启动程序里面还可以把设置保护模式的程序加上而没有超过512字节,所以先加上了,试试看用C混合汇编得到的32位代码的执行效果,以后再看看怎么增强kernel.img
下面是启动程序
;;
文件
:fatboot.asm
;;
作用
:
从
7c00h
处启动
;;
把
kernel.img
载入到
0x90000
的地方
;;
用
32
位方式执行
;;
有文件系统
,1.44M 512bits/80sec
软盘启动,
;;
创建日期
:2004/01/30 flyback
;;
修改日期
:2006/04/24 flyback
;; http://blog.csdn.net/flyback
;; ===================================
%define loadpoint 0x9000 ;
载入点,初始化程序载入到
9000h
的地方
%define loadoffset 0x0
bits 16
ORG 0x7c00 ;
启动入口地址
main:
jmp short start ;
跳转到开始程序入口
nop ;
;
引导区文件系统数据
;----------------------------------------------------------------------------
brOEM DB ' -My-0S.' ; 0003h -
引导程序的名字
brBPS DW 0x200 ; 000Bh -
每扇区的字节数
512
brSPC DB 0x01 ; 000Dh -
每簇扇区数
brResCount DW 0x0001 ; 000Eh -
保留扇区数
brFATs DB 0x02 ; 0010h - FAT
备份数
brRootEntries DW 0x00e0 ; 0011h -
根目录入口数
brSectorCount
DW 2880 ; 0013h -
磁盘容量扇区数
< 32MB
brMedia DB 240 ; 0015h -
媒体描述符
brSPF DW 9 ; 0016h -
每
FAT
扇区数
brSPH DW 18 ; 0018h -
每磁道扇区数
brHPC DW 2 ; 001Ah -
盘面数
brHidden DD 0 ; 001Ch -
隐藏扇区数
brSectors DD 0 ; 0020h -
如果大于
32m
的扇区总数
DB 0 ; 0024h -
物理驱动器号
DB 0 ; 0025h -
系统保留
DB 29H ; 0026h -
扩展扇区标记(包含
29h
)
brSerialNum DD 00000006H ; 0027h -
卷
ID
brLabel DB 'teachosdisk' ; 002Bh -
卷标
brFSID DB 'FAT12 ' ; 0036h -
系统保留
;------------------------------------------------------------------------
start:
cli ;
关中断,防止意外中断打断程序执行
mov ax, cs ;
mov ds, ax ;
设置数据段
mov es, ax ;
mov ax, 0x0000;
设置堆栈段
mov ss, ax
mov sp, 0ffffh ;
堆栈入口
sti ;
开中断
mov si, loadmsg ;
调用显示载入信息
call pntchr
;
把磁盘目录信息载入到
7c00:0200
的地方
loadroot:
mov cx, 0
mov dx, 0
mov ax, 0x0020 ; 32
个字节
/
文件
mul WORD [brRootEntries] ; 32*224
(文件数)
= 7168
(最多所有文件所占的字节数)
div WORD [brBPS] ; 7168/512 = 14 (Root dir)
文件的目录描述字节占用的扇区数
14
xchg ax, cx ; CX = 14
mov al, [brFATs] ; 2
mul word [brSPF] ; 9 * 2 = 18
add ax, word [brResCount] ; 18 + 1 now AX = 19
mov WORD[datasector], ax ;
目录起始的扇区
19
add WORD[datasector], cx ;
数据区起始扇区
33
;
目录放入
0x0200
内存
mov bx, 0x0200
call ReadSec
;
比较目录中是否有
init.img
文件存在
mov cx, WORD [brRootEntries] ; CX:224
在目录的所有文件中寻找
mov di, 0x0200 ;
从目录入口处开始
0x200
.LOOP:
push cx ;
保护
CX = 224
mov cx, kernellen ; 8
个文件名称和
3
个扩展名称
lea si, [kernelname] ;
push di
repe cmpsb ; stop compare if cx >0 or the
; first unequal is met(zf=1)!
pop di
je loadfiledec ;if zf = 1, jmp LOAD_FAT!(Seek OK!)
pop cx
add di, 0x0020 ;
跳到下一个目录入口
loop .LOOP ; cx
自动减
jmp failure
;
载入文件描述子节
loadfiledec:
;
把启动的镜像文件
*.img
文件的起始单元保存起来
mov si, CRLF
call pntchr
mov si, loadfat
call pntchr
lea si, [kernelname]
call pntchr
mov dx, WORD [di + 0x001A] ;the file's first cluster
mov WORD [cluster], dx ; store the first cluster
;
计算
fat
的大小并保存到
07c00:0x0200(ds:bx)
xor ax, ax
mov al, BYTE [brFATs] ; al:2
mul WORD [brSPF] ; AH:18 = 9*2
mov cx, ax ; CX:18
;
读取数据放入到
0x7c00:0x0200 ds:bx
mov ax, WORD [brResCount] ; AX:1
mov bx, 0x0200 ;
call ReadSec ; AX:1 CX:18 BX:0200
;
读取
img
放入
9000:0x0000
处
mov ax, loadpoint
mov es, ax
mov bx, loadoffset
push bx
LOAD_IMAGE:
mov ax, WORD [cluster] ; cluster to read
pop bx ; buffer to read into
call ClusterLBA ; convert cluster to LBA
; AX:[cluster] ES:BX[9000:0000](dest)
xor cx, cx
mov cl, BYTE [brSPC] ; CL:1
call ReadSec ; AX:LBA CX:1 BX:0000 ES:0100
push bx
;
计算下一个单元
mov ax, WORD [cluster] ; identify current cluster
mov cx, ax ; copy current cluster
mov dx, ax ; copy current cluster
shr dx, 0x0001 ; divide by two
add cx, dx ; sum for (3/2)
mov bx, 0x200 ; location of FAT in memory
add bx, cx ; index into FAT
mov dx, WORD [bx] ; read two bytes from FAT
test ax, 0x0001
jnz .ODD_CLUSTER
.EVEN_CLUSTER:
and dx, 0000111111111111b ; take low twelve bits
jmp .DONE
.ODD_CLUSTER:
shr dx, 0x0004 ; take high twelve bits
.DONE:
mov WORD [cluster], dx ; store new cluster
cmp dx, 0x0FF0 ; test for end of file,>=0x0FF0: end or bad!
jb LOAD_IMAGE ; if dx<0x0ff0 (CF=1), jmp Load_image
DONE: ; this Label is not used!
jmp gotopm
failure:
mov si, CRLF
call pntchr
mov si, loadfail
call pntchr
hlt
; ----------------
子程序区
-------------------------
;*********************
显示字符串
********************************************
;
;
;
;***************************************************************************
pntchr:
pusha
.pnt:
lodsb ;
从
DS:SI
装载一个字符到
AL
or al,al ;
jz endpntchr ;
如果
al = 0,
返回
;
mov ah,0x0E ;
mov bx,0x004a ;
int 0x10 ;
调用
bios
中断显示字符
jmp .pnt ;
;
endpntchr: ;
popa
ret ;
返回
;*************************************************************************
; PROCEDURE ReadSec
;
从
ax+1
的地方把
cx
个扇区载入到
es:bx
的内存
;
注意扇区是从
2
开始,
1
扇区已经读入了
;*************************************************************************
ReadSec:
.Main:
.secloop:
push ax
push bx
push cx ;protect ax, bx, cx
call LBACHS ;
调用转换
mov ah, 0x02 ; BIOS
读取扇区命令
mov al, 0x01 ;
一个扇区
mov ch, BYTE [Track] ; track
mov cl, BYTE [Sector] ; sector
mov dh, BYTE [Head] ; head
mov dl, 0 ;
因为是
a:
所以为
0
int 0x13 ;
调用中断
jnc .SUCCESS ; test for read error
xor ax, ax
pop cx
pop bx
pop ax
jnz .secloop
jmp failure ;
错误则显示出错信息并停止
hlt
.SUCCESS
mov si, Progress
call pntchr
pop cx
pop bx
pop ax
add bx, WORD [brBPS] ;
读取下一个扇区的内容
inc ax
loop .Main ; cx -= 1, if cx != 0, jmp .main
ret
;*************************************************************************
; PROCEDURE LBACHS
;
转换逻辑块访问为读取磁盘所使用的磁道,盘面,扇区
;
相对扇区
= (
逻辑扇区
/
每磁道扇区数
) + 1
;
相对盘面
= (
逻辑扇区
/
每磁道扇区数
) MOD
盘面数
;
相对磁道
=
逻辑扇区
/ (
每磁道扇区数
*
盘面数
)
;*************************************************************************
LBACHS:
xor dx, dx ; dx = 0
div WORD [brSPH] ; div m16: ax/18 ->
商
:ax
余数
:dx
inc dl ;
mov BYTE [Sector], dl ;sector No relative to the Track
xor dx, dx ; dx = 0
div WORD [brHPC] ; ax/2 ->
商
:ax
余数
:dx
mov BYTE [Head], dl ;
mov BYTE [Track], al ;
ret
;*************************************************************************
; PROCEDURE ClusterLBA
;
转换单元访问到直接扇区访问
; LBA = (cluster - 2) * sectors per cluster
;*************************************************************************
ClusterLBA:
sub ax, 0x0002 ; zero base cluster number
xor cx, cx
mov cl, BYTE [brSPC] ; convert byte to word
mul cx
add ax, WORD [datasector] ; base data sector
ret
;--------------------
数据区
-------------------------------
loadmsg db 'Boot',0 ;
要显示的字符窜以
0
结尾
loadfail db 'Load Fail',0 ;
载入失败信息
Sector db 0x00
Head db 0x00
Track db 0x00
datasector dw 0x0000 ; 33
数据区起始扇区号
rootaccess equ 0x0200 ;
存放目录数据的偏移地址
cluster dw 0x0000
CRLF db 13,10,0
Progress db ".", 0
loadfat db 'Load', 0 ;
字符串,回车,换行,
0
kernelname db "KERNEL IMG" ; 11 chars
kernellen equ $ - kernelname ;
长度
gdtr :
dw gdtend - gdt - 1 ; gdt
的长度
dd gdt ; gdt
的物理地址
gdt:
gdt0:
dw 0,0,0,0 ;
据说是一定要为
0
否则会有错
codesel_gdt:
dw 0xffff ;
界限
Limit
值
= 0x100000 * 0x1000 = 4GB
dw 0 ;
基地址
= 0
dw 0x9A00 ;
表示代码段
可读可执行
dw 0x00CF ;
粒度(不知道是什么意思)
datasel_gdt:
dw 0xffff ; 4GB
dw 0x0 ;
基地址
dw 0x9200 ;
数据段
可读可写
dw 0x00CF ;
粒度
gdtend:
codesel equ codesel_gdt - gdt
datasel equ datasel_gdt - gdt
;
从这里启动
32
位的模式
gotopm:
cli
lgdt [gdtr]
mov eax, cr0
or eax,1
mov cr0, eax
jmp dword codesel:loadpoint * 0x10 + loadoffset
; -----------------------------------------------------------------
times 510 - ($ - $$) db 0 ;
保证
boot
区有
512
个字节
dw 0AA55h ; boot
区标记
times 1474560 - ( $ - $$) db 0 ;
大小为
1.44M
暗红色的地方做了修改
下面是kernel
;; =================
;;
文件
:
;; KERNEL.asm
;;
作用
:
;;
在
0x90000
处启动
;;
显示一串信息
;;
创建日期
:
;; 2006/04/24 flyback
;; http://blog.csdn.net/flyback
;; =================
[bits 32]
org 0x90000
kernel:
jmp kernel_start
msg db 'Portected mode! Start at 0x90000',0
pos dd 0
; re calculate
kernel_start:
cli
lgdt[cs:gdtrs]
mov eax, cr0
or eax,1
mov cr0, eax
mov ax, datasels
mov ds, ax
mov es, ax
mov ss, ax
mov gs, ax
mov fs, ax
mov esp, 0x90000
lea esi, [msg]
call disp
hlt
disp:
push esi
push di
push ax
; mov ax, datasels
; mov es,ax
.lop:
mov edi,dword [pos]
add edi, 0xb8000
mov al, byte [esi]
mov ah, 3
mov [es:edi], ax
inc dword [pos]
inc dword [pos]
inc esi
test al, al
jnz .lop
pop ax
pop di
pop esi
ret
gdtrs :
dw gdtends - gdts - 1 ; gdt
的长度
dd gdts ; gdt
的物理地址
gdts:
gdt0s:
dw 0,0,0,0 ;
据说是一定要为
0
否则会有错
codesel_gdts:
dw 0xffff ;
界限
Limit
值
= 0x100000 * 0x1000 = 4GB
dw 0 ;
基地址
= 0
dw 0x9A00 ;
表示代码段
可读可执行
dw 0x00CF ;
粒度(不知道是什么意思)
datasel_gdts:
dw 0xffff ; 4GB
dw 0x0 ;
基地址
dw 0x9200 ;
数据段
可读可写
dw 0x00CF ;
粒度
gdtends:
codesels equ codesel_gdts - gdts
datasels equ datasel_gdts – gdts
把他们分别编译成fatboot.img和KERNEL.IMG文件然后用WinImage把fatboot.img打开,把kernel.img文件拖入WinImage
保存完毕后就可以使用bochs来调试了
首先要安装好bochs后要写一个批处理文件,一个配置文件
config.txt
# configuration file generated by Bochs
config_interface: textconfig
display_library: win32
megs: 32
romimage: file=E:/
操作系统
/Bochs-2.1.1/Bochs-2.1.1/BIOS-bochs-latest, address=0xf0000
vgaromimage: E:/
操作系统
/Bochs-2.1.1/Bochs-2.1.1/VGABIOS-elpin-2.40
boot: floppy
floppya: 1_44="fatboot.img", status=inserted
floppyb: 1_44="none", status=inserted
ata0: enabled=0
ata1: enabled=0
ata2: enabled=0
ata3: enabled=0
parport1: enabled=1, file="parport.out"
com1: enabled=1, dev=""
usb1: enabled=1, ioaddr=0xff80, irq=10
# no sb16
floppy_bootsig_check: disabled=0
vga_update_interval: 300000
keyboard_serial_delay: 250
keyboard_paste_delay: 100000
floppy_command_delay: 500
ips: 1000000
text_snapshot_check: 0
mouse: enabled=0
private_colormap: enabled=0
i440fxsupport: enabled=0
clock: sync=none, time0=local
# no ne2k
newharddrivesupport: enabled=1
# no loader
log: bochsout.txt
logprefix: %t%e%d
debugger_log: debug.txt
panic: action=ask
error: action=report
info: action=report
debug: action=ignore
pass: action=fatal
keyboard_mapping: enabled=0, map=
keyboard_type: mf
user_shortcut: keys=none
# no cmosimage
上面的内容是用bochs.exe在执行的时候回答选项然后保存下来的
start.bat
bochsdbg -f config.txt –q
bochsdbg是bochs的调试程序
-f
参数指定一个名叫config.txt的文件作为配置
-q
参数跳过bochs的内置菜单直接运行
然后运行start.bat
执行的命令有不少,可以看bochs的文档internal-debugger.html一节
常用的是下面的一些
c
继续执行
s [count]单步执行count步指令,没有count就是1
ctrl-c
中断程序执行,出现命令行方式进入单步执行的状态
q
退出bochs
b
address 在address地址处设置断点
u
address address显示在address到address处的指令
info r
显示所有寄存器信息
info b
显示设置的断点
dump_cpu
显示cpu相关信息
…
然后我们先设置在0x7c00启动地址的断点b 0x7c00
然后让它继续运行c
到了0x7c00它就会停下来
这时候可以让它单步运行s
然后每次敲回车它就可以单步了
如果对上面的程序不放心,那么就用单步调试吧!,最后出现的结果
说明我们的kernel可以载入到0x90000的地方正确运行了