实模式与保护模式

来源:互联网 发布:生有涯而知无涯意思 编辑:程序博客网 时间:2024/06/06 03:34
本文乃fireaxe原创,使用GPL发布,可以自由拷贝,转载。但转载请保持文档的完整性,并注明原作者及原链接,严禁用于任何商业用途。
作者:fireaxe_hq@hotmail.com
博客:fireaxe.blog.chinaunix.net 
 
 
1.1         保护模式的概念
1.1.1  什么是保护模式

在IA32下,CPU有两种工作模式:实模式和保护模式。在实模式下,段寄存器含有段值,为访问存储器形成物理地址时,处理器引用相应的某个段寄存器并将其值乘以16,形成20位的段基地址。计算公式如下:物理地址 = 段值*16 + 偏移。

其中段值和偏移都是16位的,这样通过“段:偏移”的方式达到了1MB的寻址能力。

在保护模式下,寄存器是32位的,但是为了兼容性,地址仍然用“段:偏移”的方式来表示,只不过这时的保护模式下的“段”的概念已经发生了根本的改变,虽然段值仍然由原来的cs、ds等寄存器表示,但是此时它仅仅是一个索引,叫做选择子,指向一个数据结构(叫做GDT,Global Descriptor Table全局描述符表或者LDT,Local Descriptor Table局部描述符表)的一个表项(叫做Descriptor描述符)。


1.1.2  地址转换方式

实模式下的地址转换方式是段地址加偏移量。段地址左移4位加偏移量得到线性地址。假设我们在ES中存入0x1000,在DI中存入0xFFFF,则线性地址ES:DI=0x1000*0x10+0xFFFF=0x1FFFF。

保护模式下的地址转换方式也是段地址加偏移量。段地址不是直接放在段寄存器中,而是以段寄存器中的值为偏移量(选择子)、Gdtr寄存器中的值作为基址,找到段地址。假设上面的数据不变ES=0x1000,DI=0xFFFF,现在ES:DI等于什么呢?公式如下:(注:0x1000=1000000000000b= 10 0000 0000 0 00)ES:DI=全局描述符表中第0x200项描述符给出的段基址+0xFFFF。实模式下称ES为“段寄存器”,而到了保护模式就说是“选择子”。其实它们都是一种映射,只是映射规则不同而已:在实模式下这个“地址转换方式”是“左移4位”;在保护模式下是“查全局/局部描述表”。前者是系统定义的映射方式,后者是用户自定义的转换方式。


1.1.3  GDT描述符

保护模式下引入描述符来描述各种数据段,所有的描述符均为8个字节(0-7),由第5个字节说明描述符的类型,类型不同,描述符的结构也有所不同。若干个描述符集中在一起组成描述符表,而描述符表本身也是一种数据段,也使用描述符进行描述。从现在起,“地址转换”由描述符表来完成,从这个意义上说,描述符表是一张地址转换函数表。

(1) P:  存在(Present)位。

P=1 表示描述符对地址转换是有效的,或者说该描述符所描述的段存在,即在内存中;

P=0 表示描述符对地址转换无效,即该段不存在。使用该描述符进行内存访问时会引起异常。

(2) DPL:  表示描述符特权级(Descriptor Privilege level),共2位。它规定了所描述段的特权级,用于特权检查,以决定对该段能否访问。

(3) S:   说明描述符的类型。

对于存储段描述符而言,S=1,以区别与系统段描述符和门描述符(S=0)。

(4) TYPE: 说明存储段描述符所描述的存储段的具体属性。

数据段类型

类型值           说明

       ----------------------------------

       0            只读

       1            只读、已访问

       2            读/写

       3            读/写、已访问

       4            只读、向下扩展

       5            只读、向下扩展、已访问

       6            读/写、向下扩展

       7            读/写、向下扩展、已访问

 

代码段类型   

类型值           说明

----------------------------------

       8            只执行

       9            只执行、已访问

       A            执行/读

       B            执行/读、已访问

       C            只执行、一致码段

       D            只执行、一致码段、已访问

       E            执行/读、一致码段

       F            执行/读、一致码段、已访问

 

系统段类型

类型编码       说明

       ----------------------------------

       0            <未定义>

       1            可用286TSS

       2            LDT

       3            忙的286TSS

       4            286调用门

       5            任务门

       6            286中断门

       7            286陷阱门

       8            未定义

       9            可用386TSS

       A            <未定义>

       B            忙的386TSS

       C            386调用门

       D            <未定义>

       E            386中断门

       F            386陷阱门

 

(5) G:    段界限粒度(Granularity)位。

       G=0 表示界限粒度为字节;

       G=1 表示界限粒度为4K 字节。

注意,界限粒度只对段界限有效,对段基地址无效,段基地址总是以字节为单位。

 

(6) D:    D位是一个很特殊的位,在描述可执行段、向下扩展数据段或由SS寄存器寻址的段(通常是堆栈段)的三种描述符中的意义各不相同。

   ⑴ 在描述可执行段的描述符中,D位决定了指令使用的地址及操作数所默认的大小。

       ① D=1表示默认情况下指令使用32位地址及32位或8位操作数,这样的代码段也称为32位代码段;

       ② D=0 表示默认情况下,使用16位地址及16位或8位操作数,这样的代码段也称为16位代码段,它与80286兼容。可以使用地址大小前缀和操作数大小前缀分别改变默认的地址或操作数的大小。

   ⑵ 在向下扩展数据段的描述符中,D位决定段的上部边界。

       ① D=1表示段的上部界限为4G;

       ② D=0表示段的上部界限为64K,这是为了与80286兼容。

   ⑶ 在描述由SS寄存器寻址的段描述符中,D位决定隐式的堆栈访问指令(如PUSH和POP指令)使用何种堆栈指针寄存器。

       ① D=1表示使用32位堆栈指针寄存器ESP;

       ② D=0表示使用16位堆栈指针寄存器SP,这与80286兼容。

 

(7) AVL:  软件可利用位。80386对该位的使用未左规定,Intel公司也保证今后开发生产的处理器只要与80386兼容,就不会对该位的使用做任何定义或规定。

描述符类型值说明,其中:

       DA_  : Descriptor Attribute

       D    : 数据段

       C    : 代码段

       S    : 系统段

       R    : 只读

       RW   : 读写

       A    : 已访问

       其它 : 可按照字面意思理解

----------------------------------------------------------------------------

DA_32           EQU       4000h     ; 32 位段

DA_DPL0             EQU         00h     ; DPL = 0

DA_DPL1             EQU         20h     ; DPL = 1

DA_DPL2             EQU         40h     ; DPL = 2

DA_DPL3             EQU         60h     ; DPL = 3

----------------------------------------------------------------------------

 存储段描述符类型值说明

----------------------------------------------------------------------------

DA_DR          EQU       90h  ; 存在的只读数据段类型值

DA_DRW              EQU       92h  ; 存在的可读写数据段属性值

DA_DRWA            EQU       93h  ; 存在的已访问可读写数据段类型值

DA_C            EQU       98h  ; 存在的只执行代码段属性值

DA_CR          EQU       9Ah ; 存在的可执行可读代码段属性值

DA_CCO              EQU       9Ch ; 存在的只执行一致代码段属性值

DA_CCOR            EQU       9Eh ; 存在的可执行可读一致代码段属性值

----------------------------------------------------------------------------

 系统段描述符类型值说明

----------------------------------------------------------------------------

DA_LDT        EQU         82h     ; 局部描述符表段类型值

DA_TaskGate  EQU         85h     ; 任务门类型值

DA_386TSS   EQU         89h     ; 可用 386 任务状态段类型值

DA_386CGate       EQU         8Ch     ; 386 调用门类型值

DA_386IGate EQU         8Eh     ; 386 中断门类型值

DA_386TGate EQU         8Fh     ; 386 陷阱门类型值

----------------------------------------------------------------------------


1.1.4  Gdtr寄存器

保护模式下,地址转换通过查询GDT表实现。那么,系统如何知道GDT在内存中的位置呢?

在80x86系列中使用寄存器Gdtr实现,GDTR寄存器长度为6字节(48位),其中低2字节表示GDT表长度长度(limit),高4字节表示GDT表基址(实际是用于校验Seletor是否越界)。

利用Gdtr寄存器只能找到GDT表的基址,而要找到对应的表项则需要使用Selector了。


1.1.5  Selector选择子

选择子是一个2字节的数,共16位,最低2位表示RPL,第3位表示查表是利用GDT(全局描述符表)还是LDT(局部描述符表)进行,最高13位给出了所需的描述符在GDT描述符表中的地址。(注:13位正好足够寻址8K项)

在程序运行时,段寄存器中保存的就是Selector。CPU通过Gdtr寄存器加段寄存器中保存的Selector的方式索引到对应的GDT表项,找到实际的段地址,然后加上指令中的地址,才能得到线性地址。如果没有使用页表结构,则这个线性地址就是物理地址了。

 RPL(Requested Privilege Level): 请求特权级,用于特权检查。

 TI(Table Indicator): 引用描述符表指示位

       TI=0 指示从全局描述符表GDT中读取描述符;

       TI=1 指示从局部描述符表LDT中读取描述符。

----------------------------------------------------------------------------

选择子类型值说明

    SA_ : Selector Attribute

SA_RPL0              EQU       0     ; ┓

SA_RPL1              EQU       1     ; ┣ RPL

SA_RPL2              EQU       2     ; ┃

SA_RPL3              EQU       3     ; ┛

 

SA_TIG         EQU       0     ; ┓TI

SA_TIL          EQU       4     ; ┛

----------------------------------------------------------------------------

 

; usage: Descriptor Base, Limit, Attr

;        Base:  dd

;        Limit:  dd (low 20 bits available)

;        Attr:   dw (lower 4 bits of higher byte are always 0)

%macro Descriptor 3

       dw   %2 & 0FFFFh                            ; 段界限 1                         (2 字节)

       dw   %1 & 0FFFFh                            ; 段基址 1                         (2 字节)

       db    (%1 >> 16) & 0FFh             ; 段基址 2                         (1 字节)

       dw   ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh)      ; 属性 1 + 段界限 2 + 属性 2         (2 字节)

       db    (%1 >> 24) & 0FFh             ; 段基址 3                         (1 字节)

%endmacro ; 共 8 字节


1.1.6  CR0寄存器

CR0是x86系列cpu的保护控制寄存器。CR0的第0位PE位用于设置CPU工作模式。

PE=0:实模式

PE=1:保护模式

 



1.1  进入保护模式的主要步骤:
1.1.1  GDT表

GDT表是存储GDT描述符的数组,每一个描述符对应着一个段。GDT表中的第一项必须为全0。表项类型为结构体Descriptor,结构如下:

; usage: Descriptor Base, Limit, Attr

;    %1    Base:  dd

;    %2    Limit: dd (low 20 bits available)

;    %3    Attr:  dw (lower 4 bits of higher byte are always 0)

%macro Descriptor 3

   dw  %2 & 0FFFFh        ; 段界限 1    (2 字节)

   dw  %1 & 0FFFFh        ; 段基址 1    (2 字节)

   db  (%1 >> 16) & 0FFh  ; 段基址 2    (1 字节)

   dw  ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh) ; 属性 1 + 段界限 2 + 属性 2  (2 字节)

   db  (%1 >> 24) & 0FFh  ; 段基址 3    (1 字节)

%endmacro ; 共 8 字节

根据nasm编译器的规则,结构体定义以%macro开始,以%endmacro结束。结构体名为Descriptor,宏名后的3表示该结构体有三个成员变量。

GDT表的定义方式如下:

[SECTION .gdt]

; GDT                           Base Addr   Limit          Attrib

LABEL_DES_GDT:    Descriptor   0,          0,             0

LABEL_DES_CODE32: Descriptor   0,          Code32Len - 1,  DA_C + DA_32

LABEL_DES_VIDEO:  Descriptor   0B8000h,    0ffffh,         DA_DRW

; GDT end

这里可以看到给结构体赋值的方式,每一项对应结构体中的一个成员变量。以表项LABEL_DES_VIDEO为例,0B8000h对应%1,0ffffh对应%2,DA_DRW对应%3。

注意:表项LABEL_DES_CODE32保存的是我们要跳入的32位代码段的GDT表项,其中Base Addr是0,因为此时还不知道32为代码段的地址,在后面的16为代码段中才会设置它。


1.1.2  Gdtr

GdtLen  equ $ - LABEL_DES_GDT   ; Gdt Len

GdtPtr   dw  GdtLen - 1      ; Gdt Limit

         dd  0               ; Gdt Base Addr

GDT表的地址保存在寄存器Gdtr中,该地址首先通过上面的语句计算而得后存在变量GdtPtr中,之后才加载。GdtPtr低2字节保存GDT表长度,通过“$ - LABEL_DES_GDT”计算而得。GdtPtr高4字节保存GDT表基址,这里先置0,后面会存入地址。

 


1.1.3  Selector选择子

; Gdt Selector

SelectorCode32  equ LABEL_DES_CODE32 - LABEL_DES_GDT

SelectorVideo   equ LABEL_DES_VIDEO - LABEL_DES_GDT

 

选择子Selector中保存的是各段对应GDT表项相对地址,通过上面的语句计算而得。

 


1.1.4  16位段与32位段

[SECTION .s16]

[BITS 16]

……

[SECTION .s32]

[BITS 32]

……

由于本程序实现了有实模式到保护模式的跳转,所以需要在程序中定义两个代码段,一个是16位段;一个是32位段。


1.1.5  初始化32位代码段描述符

       ; Init 32 bits code segment descriptor

    xor eax, eax

    mov ax, cs

    shl eax, 4

    add eax, LABEL_CODE32

    mov word [LABEL_DES_CODE32 + 2], ax

    shr eax, 16

    mov byte [LABEL_DES_CODE32 + 4], al

    mov byte [LABEL_DES_CODE32 + 7], ah

 

首先要设置32为代码段对应GDT表项的基址。基址对应着表项中的第2、3、4、7,四个字节。


1.1.6  准备加载gdtr

    ; Prepare for loading gdtr

    xor eax, eax

    mov ax, cs

    shl eax, 4

    add eax, LABEL_DES_GDT

    mov dword [GdtPtr + 2], eax

这段代码用于设置变量GdtPtr,长度为2字节。


1.1.7  加载gdtr

    ; Load gdtr

    lgdt [GdtPtr]

此处加载gdtr寄存器,x86使用专门的指令lgdt实现gdtr寄存器的设置。


1.1.8  关闭中断

    ; close int

    Cli

由于实模式与保护模式下的中断模式不同,所以在实模式/保护模式转换时必须关闭中断。


1.1.9  打开A20

    ; open A20

    in al, 92h

    or al, 00000010b

    out 92h, al

80286及之前的cpu最大寻址空间为1M,超过1M的地址(也就是使用了A20之后的地址线),会开始从0重新计算,即使跳入保护空间,也无法使用超过1M的地址。

80386开始,增加了保护模式,寻址空间变为4G。为了与80286之前的cpu兼容,超过1M的地址平时是关闭的,只有打开A20后才能访问超过1M的地址。

上面只是打开A20的一种方式,但不是唯一的方式。


1.1.10           设置cr0的PE位

    ; open protected mode

    mov eax, cr0

    or  eax, 1

    mov cr0, eax

此处打开cr0的PE位,实际上就使能了保护模式,也就是说,“mov cr0, eax”这一句后,系统就运行于保护模式下了。但是,此时ics的值仍然是实模式下的值,我们需要把代码段的Selector选择子装入cs。


1.1.11           跳入保护模式

    ; jump into protected mode

    jmp dword SelectorCode32:0   

 

这个跳转是为了设置cs寄存器,其目的是选择子SelectorCode32对应的GDT描述符LABEL_DESC_CODE32对应的段首地址,即标号LABEL_    CODE32C处。

该句指令还在16位段中执行,而目标地址却是32位的,从这一点上看,他是混合16位于32位的代码。所以,jmp后的dword就必不可少了。如果没有dword,编译出来的只是16位代码。假设目标地址偏移量为0x12345678,执行jmp SelectorCode32:0x12345678,则等价于jmp SelectorCode32:0x5678。

Nasm编译器的好处之一就是可以通过jmp后添加dword,指定编译后为32位指令。

至此,我们真正跳入了保护模式。

 


1.1.12           显示字符

LABEL_CODE32:

    mov eax, SelectorVideo

    mov gs, eax

    mov edi, (80 * 6 + 6) * 2      ; Line: 6, Column: 6

    mov ah, 0Ch                         ; 0000: background(black)    1100: foreground(red)

    mov al, 'P'

    mov [gs:edi], ax

这是进入保护模式后执行的代码。SelectorVideo选择子对应着显存的基址,我们通过把‘P’放入显存,实现了字符‘P’的显示。





  今天终于实现了由汇编进入c程序,由于C程序都是在32位模式下操作的,所以需要由汇编中进入保护模式,然后才能进入C程序。保护模式下与实模式有一点不同,要注意个段的属性。
     实模式下段地址设置:
 mov ax, cs
 mov ds, ax
 mov ss, ax
 mov es, ax
 mov sp, 01000h
      保护模式下段地址设置:
 mov eax, SelectorStack
 mov ds, eax
 mov ss, eax
 mov es, eax
 mov sp, 01000h
       实模式中可以用代码段寄存器cs来设置堆栈、数据等段寄存器;保护模式中断寄存器是设置了代码段属性的(DA_C + DA_32),所以只能用用于代码段,堆栈、数据段等需要进行数据操作的段,必须设置其他GDT段(属性为DA_DRW)。









 
0 0
原创粉丝点击