实模式与保护模式互相跳转

从这里开始我们就要告别实模式,实现保护模式了。

首先了解一些相关知识。

段描述符的具体格式

clip_image001

段描述符长8个字节64位。

 

clip_image002

 

重点说明:
  
空描述符:           ; 这是保护模式要求保留的,第一个段必须是空段,空描述符的64位全是0
        dd  0
        dd  0           
   
对于代码段字节5:
        db  10011010b   ; 0x9A
属性描述位,P=1DPL=0DT1=1TYPE=A,指明此是代码段、可读可执行
   
对于数据段字节5:              
        db  10010010b   ;  0x92
属性描述位,P=1DPL=0DT1=1TYPE=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中运行效果如下:

clip_image003

 

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

posted on 2015-11-06 21:23  ya20151015  阅读(175)  评论(0编辑  收藏  举报