实模式与保护模式互相跳转
从这里开始我们就要告别实模式,实现保护模式了。
首先了解一些相关知识。
段描述符的具体格式
段描述符长8个字节64位。
重点说明:
空描述符: ; 这是保护模式要求保留的,第一个段必须是空段,空描述符的64位全是0
dd 0
dd 0
对于代码段字节5:
db 10011010b ; 0x9A 属性描述位,P=1,DPL=0,DT1=1,TYPE=A,指明此是代码段、可读可执行
对于数据段字节5:
db 10010010b ; 0x92 属性描述位,P=1,DPL=0,DT1=1,TYPE=2,指明此是数据段,可读可写
虚拟地址转换物理地址
简单的说:虚拟地址的偏移量+base=线性地址
在没有使用分布机制情况下,线性地址就是物理地址。
这里,我们没有使用分布机制。
改变kernelloader.asm,让它从实模式到保护模式显示一个字母,再返回实模式并显示hello。
kernelloader.asm
[BITS16]
jmp main
gdt_entries equ 5 ;共有五个段描述符:null,os code32,os data32,os code16,os data16
showbase equ 8900h
pe equ 1 ;bit PE in CR0
null equ 0h
os_code32_sel equ 8h ;1,gdt,rpl=00
os_data32_sel equ 10h ;2,gdt,rpl=00
os_code16_sel equ 18h ;3,gdt,rpl=00
os_data16_sel equ 20h ;4,gdt,rpl=00
[SECTION.data]
ns db0x48,0x65,0x6C,0x6C,0x6F,0x20,0x77,0x6F,0x72,0x6C,0x64,0x21,'from ya' ;;hello world!from ya
pdescr times6db0
gdt_table times(gdt_entries*8)db0
end_32 dd0
dw0
hello: ;hello子程序
pushes
movcx,19 ;循环19次
movbx,0 ;从数组[0]开始
movah,0eh
next:
moval,[ns+bx]
int10h
incbx
deccx
jnz next ;cx不为0则继续
popes
ret
main:
movax,1000h
movds,ax
;打开 A 20 地址线
mov ax,0x2401
int 0x15
movword[end_32+0],end32
movword[end_32+4],os_code16_sel ;point to 0018:end32
;[1]built up GDT table
cli
mov eax,gdt_table
;item 0:null descriptor,
mov dword[eax],0
mov dword[eax+4],0
add eax,8
;item 1,OS code32 descriptor,
;Base=00010000h,limit=0fh,G=1,D=1,type=a,dpl=0
mov word[eax],0000fh
mov word[eax+2],0
mov byte[eax+4],01h
mov byte[eax+5],09ah
mov byte[eax+6],0c0h
mov byte[eax+7],00h
add eax,8
;item 2,OS data32 descriptor
;Base=00010000h,Limit=0fffh,G=1,D=1,Type=2,DPL=0
mov word[eax],0fffh
mov word[eax+2],0000h
mov byte[eax+4],01h
mov byte[eax+5],092h
mov byte[eax+6],0c0h
mov byte[eax+7],00h
add eax,8
;item 3,OS code16 descriptor,
;Base=00010000h,limit=0ffffh,G=0,D=0,type=a,dpl=0
mov word[eax],0ffffh
mov word[eax+2],0
mov byte[eax+4],01h
mov byte[eax+5],09ah
mov byte[eax+6],00h
mov byte[eax+7],00h
add eax,8
;item 4,OS data16 descriptor
;Base=00010000h,Limit=0ffffh,G=0,D=1,Type=2,DPL=0
mov word[eax],0ffffh
mov word[eax+2],0000h
mov byte[eax+4],01h
mov byte[eax+5],092h
mov byte[eax+6],40h
mov byte[eax+7],00h
;[2]built false GDT descriptor
mov word[pdescr+0],(gdt_entries*8)
mov dword[pdescr+2],gdt_table+00010000h
lgdt[pdescr]
;[3]enter into protected mode
;刷新CR0
moveax,cr0
or eax,pe
movcr0,eax
jmp flush
flush:
movax,os_data32_sel
movds,ax
moves,ax
movss,ax
movfs,ax
movgs,ax
jmpdword os_code32_sel:start32
;[4]run in protected mode
start32:
cli
movbx,showbase
dw 0ah ;相当于mov ebx,0a8900h
movdi,bx ;相当于es:edi=0010:000a8900=0b8900h
moval,'A'
movah,34h
stosw
movsi,end_32
dw 00h ;相当于mov esi,offset end_32
db 0ffh,2eh ;相当于jmp Fword Ptr [esi] ,jmp to 0018:end32
end32:
;[5]exit to dos
movax,os_data16_sel
movds,ax
moves,ax
movss,ax
movfs,ax
movgs,ax
cli
moveax,cr0
andal,0feh
movcr0,eax ;clear pe bit,exit of protected mode
movcr3,eax
jmp realflush
realflush:
movax,01000h
movds,ax
moves,ax
movss,ax
xorax,ax
movfs,ax
movgs,ax
jmp0x1000:real16
real16:
movword[pdescr+0],0ffffh ;limit
movdword[pdescr+2],0 ;basement
lidt[pdescr]
sti
movah,4ch
int21h
call hello ;返回实模式后显示hello
jmp$
程序中
;打开 A 20 地址线
mov ax,0x2401
int 0x15
......
;刷新CR0
moveax,cr0
or eax,pe
movcr0,eax
jmp flush
flush:
这些语句都是按要求必须这么做的,没什么可说的,大家按套路来就行。
目前kernelloader.bin大小为444字节,占据1个扇区,相应的boot程序中读取扇区的参数值应为1
boot.asm
[BITS16] ;编译成16位的指令
[ORG0x7C00]
jmp main
read_kernelloader: ;读入 kernelloader 程序
push es
.rk:
mov ax,0x1000 ;kernelloader.bin 所在的段基址
mov es,ax
mov bx,0
mov ah,2
mov dl,0
mov ch,0
mov cl,2
mov al,1 ;读入扇区数,每个扇区为 512B
int 0x13
jc .rk
pop es
ret
main: ;主程序
mov ax,0x0 ;boot.bin 程序的段基址
mov ds,ax
call read_kernelloader ;读入 kernelloader 程序
jmpdword 0x1000:0 ;跳转到 kernelloader 处执行
times510-($-$$)db0
db0x55
db0xAA
在bochs中运行效果如下:
16位模式下与32位模式下的编译结果(机器码)是有区别的:
(16位下)mov bx,8900h 机器码为 BB0089
(32位下)mov ebx,68900h 机器码为 BB00890600
两个相差0600
(16位下)mov di,bx 机器码为 89DF
(32位下)mov edi,ebx 机器码为 89DF
两个相同
(16位下)mov si,end_32 机器码为 BE4500
(32位下)mov esi,end_32 机器码为 BE45000000
两个相差0000
(16位下)jmp fword ptr [esi] 机器码为 FF2e
为什么要研究机器码?因为我们程序是在16位模式下编译,但是我们中间这段程序又将要跳转到保护模式,但是在保护模式下运行16位程序显然会出错,怎么解决这个问题?
我们可以将16位模式下与32位模式指令中相差的部分补充上,那么以下的语句就可以理解了:
movbx,showbase
dw 06h ;相当于mov ebx,68900h
movdi,bx ;es:edi=0010:00068900=0b8900h
moval,'A' ;16位与32位相同
movah,34h ;16位与32位相同
stosw ;16位与32位相同
movsi,end_32
dw 00h ;mov esi,offset end_32
db 0ffh,2eh ;相当于jmp Fword Ptr [esi] ,jmp to 0018:end32