在前面简单介绍了保护模式以及主要的lgdt表的属性,以及怎么进入保护模式简要说明就是:
- 设置需要使用的段属性表
- 设置lgdt属性表地址以及长度限制
- 打开A20地址
- 关闭中断(因为没有设置中断表lidt)
- 设置CR0寄存器打开保护模式
- 跳入保护模式执行
这只是最简单的进入保护模式吗,不涉及特权级的变化以及系统段的改变,中断的影响,因此我们继续分析特权变化,系统段,以及中断。
最主要的段属性描述符以及段描述符索引如图:
代码段和数据段描述符
选择子(Selector)的结构
gdtr示意图
CR0寄存器
寄存器cr0的第0位是PE位,此位为0时,CPU运行于实模式,为1时,CPU运行于保护模式。原来我们已经闭合了进入保护模式的开关,也就是说,“mov cr0, eax”这一句之后,系统就运行于保护模式之下了。但是,此时cs的值仍然是实模式下的值,我们需要把代码段的选择子装入cs。
再次回忆段属性表的主要部分
- 段界限
- 段基址
- G粒度位与段界限联合使用 G=1表示4KB单位,G=0字节单位
- D/B位指示32位代码段和数据段指明32位代码段使用EIP,数据段默认压栈4字节使用ESP ,这位为1表示32位,为0跟实模式一样。
- AVL保留不使用
- P存在位,指明段是否在内存中存在
- DPL该段特权级指明
- S位S=1是代码段/数据段,S=0是系统段/门描述
- TYPE描述代码段数据段时,指明段的读写执行属性,系统段/门描述符时,指明是什么类型的描述符
TYPE值的详细解释
一致代码段指的是当转移的目标是一个特权级更高的一致代码段,当前的特权级会被延续下去,而向特权级更高的非一致代码段的转移会引起常规保护错误(general-protection exception,#GP),除非使用调用门或者任务门。如果系统代码不访问受保护的资源和某些类型的异常处理(比如,除法错误或溢出错误),它可以被放在一致代码段中。为避免低特权级的程序访问而被保
护起来的系统代码则应放到非一致代码段中。要注意的是,如果目标代码的特权级低的话,无论它是不是一致代码段,都不能通过call或者jmp转移进去,尝试这样的转移将会导致常规保护错误。
所有的数据段都是非一致的,这意味着不可能被低特权级的代码访问到。然而,与代码段不同的是,数据段可以被更高特权级的代码访问到,而不需要使用特定的门。
也就是高特权级不能访问低特权及代码,能访问低特权级数据,低特权级能直接访问高特权级的一致代码段,当前特权级不会变,访问非一致代码段需要使用调用门或者任务门。
总结:相同特权级能互相访问,高特权级可以访问低特权级数据,不能访问低特权级代码,低特权级可以访问高特权级一致代码段,如果要访问高特权级非一致代码段需要使用调用门和中断门。
以上说明了各种代码段的分类,以及代码段数据段的权限关系。接着我么说一下全局描述符表GDT类似的局部描述符表LDT:
GDT里面保存了LDT段的基地址以及相关属性,使用LLDT加载了该选择段的时候,在使用jmp跳转到局部描述符时候会在此ldt表里面选择并跳转执行。LDT 0 段可以使用不用保留。这里我们举例使用LDT
; 宏 ------------------------------------------------------------------------------------------------------
;
; 描述符
; 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 字节
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 陷阱门类型值
;----------------------------------------------------------------------------
; 描述符类型值说明
; 其中:
; 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 陷阱门类型值
;----------------------------------------------------------------------------
ldtaddrcs equ ((0<<3)|(1<<2))
org 0x7c00
jmp start
;定义LGDT表
;定义LDT表
;设置lgdt
;设置ldt
;打开A20地址线使能32位地址
;打开保护模式
;跳转ldt执行
section testldt align=32
;定义段描述符和属性,在代码中初始化地址
codenul: Descriptor 0,0,0 ;定义空段
codegdt: Descriptor 0,0,DA_LDT ;定义LDT段描述符
codeldt: Descriptor 0,0,DA_C + DA_32 ;ldt描述符
lgdtlen equ codeldt-codenul
lgdtr:
dw 0xFFFF
dd 0x000000000
lldtr:
dw 0xFFFF
dd 0x000000000
section startcode align=32
[bits 16]
start:
;init ldt attr
mov ax,ldtstartLen
mov [codeldt],ax
mov ax,ldts
mov [codeldt+2],ax
;init gdt attr
mov ax,8
mov [codegdt],ax
mov ax,codeldt
mov [codegdt+2],ax
;init lgdtr
mov ax,lgdtlen
mov [lgdtr],ax
mov ax,codenul
mov [lgdtr+2],ax
;init lldtr
mov ax,8
mov [lldtr],ax
mov ax,codeldt
mov [lldtr+2],ax
lgdt [lgdtr]
in al,0x92 ;a20地址
or al,0x02
out 0x92,al
mov eax,cr0
or eax,0x01
mov cr0,eax
cli
mov ax,0x08 ;lldt gdt的第一个就是他的地址
lldt ax
jmp ldtaddrcs:0
section ldtstart align=32
[bits 32]
ldts:
mov eax,0x12345678
mov ebx,0x12345678
add eax,ebx
jmp $
ldtstartLen equ $ - ldts
END_CHECK:
times 510-206 db 0
db 0x55,0xaa
注意 $表示当前行,行首的标号,$$当前段的起始汇编地址。
关键点:在GDT表中定义LDT描述符段,存放的LDT描述符表的地址和大小,并注明该属性为ldt段描述符,其他属性为0就行(P要等于1表示存在,D/B G avl为0,S位一定为0表示描述符是系统的一个段而不是数据或者代码段),而在LDT描述符表中描述符属性为代码段,32位即可(代码段是16位长度,就写16位)。然后分别初始化GDT表和LDT中的地址和段限长即可。
因为操作系统经常使用ldt所以写在这里然后继续聊特权级,代码段的特权级定义在代码段的描述表中(DPL),自身运行代码的特权级在CS的低两位中(CPL)。
CPL(Current Privilege Level):CPL是当前执行的程序或任务的特权级。它被存储在cs和ss的第0位和第1位上。在通常情况下,CPL等于代码所在的段的特权级。当程序转移到不同特权级的代码段时,处理器将改变CPL。在遇到一致代码段时,情况稍稍有点特殊,一致代码段可以被相同或者更低特权级的代码访问。当处理器访问一个与CPL特权级不同的一致代码段时,CPL不会被改变。
DPL(Descriptor Privilege Level):DPL表示段或者门的特权级。它被存储在段描述符或者门描述符的DPL字段中,正如我们先前所看到的那样。当当前代码段试图访问一个段或者门时,DPL将会和CPL以及段或门选择子的RPL相比较,根据段或者门类型的不同,DPL将会被区别对待,下面介绍一下各种类型的段或者门的情况。
数据段:DPL规定了可以访问此段的最低特权级。比如,一个数据段的DPL是1,那么只有运行在CPL为0或者1的程序才有权访问它。
非一致代码段(不使用调用门的情况下):DPL规定访问此段的特权级。比如,一个非一致代码段的特权级为0,那么只有CPL为0的程序才可以访问它。
调用门:DPL规定了当前执行的程序或任务可以访问此调用门的最低特权级(这与数据段的规则是一致的)。
一致代码段和通过调用门访问的非一致代码段:DPL规定了访问此段的最高特权级。比如,一个一致代码段的DPL是2,那么CPL为0和1的程序将无法访问此段。
TSS:DPL规定了可以访问此TSS的最低特权级(这与数据段的规则是一致的)。
RPL(Requested Privilege Level):RPL是通过段选择子的第0位和第1位表现出来的。处理器通过检查RPL和CPL来确认一个访问请求是否合法。即便提出访问请求的段有足够的特权级,如果RPL不够也是不行的。也就是说,如果RPL的数字比CPL大(数字越大特权级越低),那么RPL将会起决定性作用,反之亦然。
操作系统过程往往用RPL来避免低特权级应用程序访问高特权级段内的数据。当操作系统过程(被调用过程)从一个应用程序(调用过程)接收到一个选择子时,将会把选择子的RPL设成调用者的特权级。于是,当操作系统用这个选择子去访问相应的段时,处理器将会用调用过程的特权级(已经被存到RPL中),而不是更高的操作系统过程的特权级(CPL)进行特权检验。这样,RPL就保证了操作系统不会越俎代庖地代表一个程序去访问一个段,除非这个程序本身是有权限的。
8086 可以访问 1MB 内存。其中,0x00000~9FFFF 属于常规内存,由内存条提供;0xF0000~0xFFFFF 由主板上的一个芯片提供,即 ROM-BIOS。间还有一个 320KB 的空洞,即 0xA0000~0xEFFFF。传统上,这段地址空间由特定的外围设备来提供,其中就包括显卡。512字节的起始扇区用来做往后的代码演示空间可能不够用,因此为了好计算和使用我们使用
0x10000-0x20000这64kb做代码示例区域用硬盘1-128扇区做代码存储:
读取64kb扇区代码程序:
;读硬盘到内存
mov dx,0x1f2 ;读取128个扇区
mov al,0x00
out dx,al
mov dx,0x1f3 ;逻辑1号扇区
mov al,0x01
out dx,al ;LBA 地址 7~0
inc dx ;0x1f4
mov al,0x00
out dx,al ;LBA 地址 15~8
inc dx ;0x1f5
out dx,al ;LBA 地址 23~16
inc dx ;0x1f6
mov al,0xe0 ;LBA 模式,主硬盘,以及 LBA 地址 27~24
out dx,al
mov dx,0x1f7
mov al,0x20 ;读命令
out dx,al
mov dx,0x1f7
.waits:
in al,dx
and al,0x88
cmp al,0x08
jnz .waits ;不忙,且硬盘已准备好数据传输
mov ax,0x1000;设置数据区域
mov ds,ax
mov bx,0x00
mov cx,0x8000
mov dx,0x1f0
readw:
in ax,dx
mov [bx],ax
add bx,2
loop readw
jmp 0x1000:0
fill:
times 510 - ($-$$) db 0x99
db 0x55,0xaa
把写的示例代码放置以逻辑1号扇区开始。
特权段跳转例子:
;----------------------------------------------------------------------------
; 描述符类型值说明
; 其中:
; 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 陷阱门类型值
;----------------------------------------------------------------------------
;----------------------------------------------------------------------------
; 选择子类型值说明
; 其中:
; 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 字节
;
; 门
; usage: Gate Selector, Offset, DCount, Attr
; Selector: dw
; Offset: dd
; DCount: db
; Attr: db
%macro Gate 4
dw (%2 & 0FFFFh) ; 偏移 1 (2 字节)
dw %1 ; 选择子 (2 字节)
dw (%3 & 1Fh) | ((%4 << 8) & 0FF00h) ; 属性 (2 字节)
dw ((%2 >> 16) & 0FFFFh) ; 偏移 2 (2 字节)
%endmacro ; 共 8 字节
; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
;该代码从0x10000处开始存储,从硬盘的逻辑1号扇区开始存储,程序进来的时候cs=ds=0x10000
jmp start
section desc align=32
description_start_addr:
null1_descrip: Descriptor 0,0,0
text2_descrip: Descriptor 0x10000,0,DA_CR+DA_32 ;存在的可读可执行代码段特权级为0,
text3_descrip: Descriptor 0x10000,0,DA_CR+DA_DPL3+DA_32 ;存在的可读可执行代码段特权级为3
data4_descrip: Descriptor 0x10000,0,DA_DRW+DA_32 ;存在的可读写数据段属性值
lgdtr:
dw 0x0000
dd 0x00010000
descriplen equ lgdtr-null1_descrip
text2len equ text2end-text2start-1
text3len equ text3end-text3start-1
testdatalen equ testend - testdata-1
selecttext2 equ text2_descrip-null1_descrip
selecttext3 equ text3_descrip-null1_descrip
selectdata4 equ data4_descrip-null1_descrip
section text1 align=32
bits 16
text1start:
start:
mov ax,0x00001
mov bx,0x00001
add ax,bx
mov ax,text2len
mov [text2_descrip],ax ;limit
mov ax,text2start
mov [text2_descrip+2],ax ;低16位写进去
mov ax,text3len
mov [text3_descrip],ax ;limit
mov ax,text3start
mov [text3_descrip+2],ax ;低16位写进去
mov ax,testdatalen
mov [data4_descrip],ax ;limit
mov ax,testdata
mov [data4_descrip+2],ax ;低16位写进去
;interrupt close
cli
;open a20
in al,0x92
or al,0x02
out 0x92,al
;set lgdtr
mov ax,descriplen
mov [lgdtr],ax
mov ax,null1_descrip
mov [lgdtr+2],ax
lgdt [lgdtr]
;open prtection
mov eax,cr0
or eax,0x01
mov cr0,eax
jmp selecttext2:0 ;跳转到特权级为3的代码段
text1end:
section text2 align=32
bits 32
text2start:
mov ax,0x00002
mov bx,0x00002
add ax,bx
mov ax,selectdata4
mov es,ax
mov eax,[es:0]
jmp $
text2end:
section text3 align=32
bits 32
text3start:
mov ax,0x00003
mov bx,0x00003
add ax,bx
jmp $
text3end:
section data4 align=32
bits 32
testdata:
times 256 dw 0x1234
testend:
这段代码从实模式跳转到特权级为0的代码段,能正常跳转执行,能访问特权级为0的数据段。
把数据段改为3在做测试:
data4_descrip: Descriptor 0x10000,0,DA_DRW+DA_32+DA_DPL3 ;存在的可读写数据段属性值
能正常访问数据
把数据段特权级改为0,代码段特权级改为3
;----------------------------------------------------------------------------
; 描述符类型值说明
; 其中:
; 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 陷阱门类型值
;----------------------------------------------------------------------------
;----------------------------------------------------------------------------
; 选择子类型值说明
; 其中:
; 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 字节
;
; 门
; usage: Gate Selector, Offset, DCount, Attr
; Selector: dw
; Offset: dd
; DCount: db
; Attr: db
%macro Gate 4
dw (%2 & 0FFFFh) ; 偏移 1 (2 字节)
dw %1 ; 选择子 (2 字节)
dw (%3 & 1Fh) | ((%4 << 8) & 0FF00h) ; 属性 (2 字节)
dw ((%2 >> 16) & 0FFFFh) ; 偏移 2 (2 字节)
%endmacro ; 共 8 字节
; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
;该代码从0x10000处开始存储,从硬盘的逻辑1号扇区开始存储,程序进来的时候cs=ds=0x10000
jmp start
section desc align=32
description_start_addr:
null1_descrip: Descriptor 0,0,0
text2_descrip: Descriptor 0x10000,0,DA_CCOR+DA_32+DA_DPL3 ;存在的可执行可读一致代码段属性值
text3_descrip: Descriptor 0x10000,0,DA_CR+DA_DPL3+DA_32 ;存在的可读可执行代码段特权级为3
data4_descrip: Descriptor 0x10000,0,DA_DRW+DA_32+DA_DPL3 ;存在的可读写数据段属性值
lgdtr:
dw 0x0000
dd 0x00010000
descriplen equ lgdtr-null1_descrip
text2len equ text2end-text2start-1
text3len equ text3end-text3start-1
testdatalen equ testend - testdata-1
selecttext2 equ text2_descrip-null1_descrip
selecttext3 equ text3_descrip-null1_descrip
selectdata4 equ data4_descrip-null1_descrip
section text1 align=32
bits 16
text1start:
start:
mov ax,0x00001
mov bx,0x00001
add ax,bx
mov ax,text2len
mov [text2_descrip],ax ;limit
mov ax,text2start
mov [text2_descrip+2],ax ;低16位写进去
mov ax,text3len
mov [text3_descrip],ax ;limit
mov ax,text3start
mov [text3_descrip+2],ax ;低16位写进去
mov ax,testdatalen
mov [data4_descrip],ax ;limit
mov ax,testdata
mov [data4_descrip+2],ax ;低16位写进去
;interrupt close
cli
;open a20
in al,0x92
or al,0x02
out 0x92,al
;set lgdtr
mov ax,descriplen
mov [lgdtr],ax
mov ax,null1_descrip
mov [lgdtr+2],ax
lgdt [lgdtr]
;open prtection
mov eax,cr0
or eax,0x01
mov cr0,eax
jmp selecttext2:0 ;跳转到特权级为3的代码段
text1end:
section text2 align=32
bits 32
text2start:
mov ax,0x00002
mov bx,0x00002
add ax,bx
mov ax,selectdata4
mov es,ax
mov eax,[es:0]
jmp $
text2end:
section text3 align=32
bits 32
text3start:
mov ax,0x00003
mov bx,0x00003
add ax,bx
jmp $
text3end:
section data4 align=32
bits 32
testdata:
times 256 dw 0x1234
testend:
一致代码段也不能从高特权级到低特权级
这里发现代码不能跳转,说明实模式时代码特权级为0不能跳转到低特权级代码那么怎么才能去低特权级运行呢,需要使用返回指令,这里涉及到特权级堆栈的切换在后面会讲解。
程序从一个代码段转移到另一个代码段之前,目标代码段的选择子会被加载到cs中。作为加载过程的一部分,处理器将会检查描述符的界限、类型、特权级等内容。如果检验成功,cs将被加载,程序控制将转移到新的代码段中,从eip指示的位置开始执行。
程序控制转移的发生,可以是由指令jmp、call、ret、sysenter、sysexit、int n 或iret引起的,也可以由中断和异常机制引起。
使用jmp或call指令可以实现下列4种转移:
1. 目标操作数包含目标代码段的段选择子。
2. 目标操作数指向一个包含目标代码段选择子的调用门描述符。
3. 目标操作数指向一个包含目标代码段选择子的TSS。
4. 目标操作数指向一个任务门,这个任务门指向一个包含目标代码段选择子的TSS。
这4 种方式可以看做是两大类,一类是通过jmp和call的直接转移(上述第1种),另一类是通过某个描述符的间接转移(上述第2、3、4种)。
对通过jmp或call进行直接转移已经有过一些讨论,
- 如果目标是非一致代码段,要求CPL必须等于目标段的DPL,同时要求RPL小于等于DPL;
- 如果目标是一致代码段,则要求CPL大于或者等于目标段的DPL,RPL此时不做检查。当转移到一致代码段中后,CPL会被延续下来,而不会变成目标代码段的DPL。也就是说,通过jmp和call所能进行的代码段间转移是非常有限的
- 对于非一致代码段,只能在相同特权级代码段之间转移。遇到一致代码段也最多能从低到高,而且CPL不会改变。如果想自由地进行不同特权级之间的转移,显然需要其他几种方式,即运用门描述符或者TSS
一个门描述了由一个选择子和一个偏移所指定的线性地址,程序正是通过这个地址进行转移的。门描述符分为4种:
- 调用门(Call gates)
- 中断门(Interrupt gates)
- 陷阱门(Trap gates)
- 任务门(Task gates)
什么是调用门:
调用门是一种类似于段描述符的描述符,格式如图:
描述符和我们前面提到的描述符有很大不同,它主要是定义了目标代码对应段的选择子、入口地址的偏移和一些属性等。可是,虽然这样的结构跟代码段以及数据段描述符大不相同,我们仍然看到,第5个字节(BYTE5)却是完全一致的,都表示属性。在这个字节内,各项内容的含义与前面提到的描述符也别无二致,这显然是必要的,以便识别描述符的类型。在这里,S位将是0 表示是系统段或者门描述符,ParamCount 表示调用门特权级转移,从现在堆栈拷贝堆栈参数到新堆栈的字节数(程序返回时 使用带参数的 retf ParamCount 返回)。
在type系统段们描述符里面有写,其中,中断门和陷阱门是特殊的调用门。
无特权级转移调用门示例:
;----------------------------------------------------------------------------
; 描述符类型值说明
; 其中:
; 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 陷阱门类型值
;----------------------------------------------------------------------------
;----------------------------------------------------------------------------
; 选择子类型值说明
; 其中:
; 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 字节
;
; 门
; usage: Gate Selector, Offset, DCount, Attr
; Selector: dw
; Offset: dd
; DCount: db
; Attr: db
%macro Gate 4
dw (%2 & 0FFFFh) ; 偏移 1 (2 字节)
dw %1 ; 选择子 (2 字节)
dw (%3 & 1Fh) | ((%4 << 8) & 0FF00h) ; 属性 (2 字节)
dw ((%2 >> 16) & 0FFFFh) ; 偏移 2 (2 字节)
%endmacro ; 共 8 字节
; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
;该代码从0x10000处开始存储,从硬盘的逻辑1号扇区开始存储,程序进来的时候cs=ds=0x10000
jmp start
section desc align=32
null1_descrip: Descriptor 0,0,0
text2_descrip: Descriptor 0x10000,0,DA_CCOR+DA_32 ;存在的可执行可读一致代码段属性值
text3_descrip: Descriptor 0x10000,0,DA_CR+DA_32 ;存在的可读可执行代码段特权级为3
data4_descrip: Descriptor 0x10000,0,DA_DRW+DA_32+DA_DPL3 ;存在的可读写数据段属性值
call5_descrip: Gate selecttext3,0,0,DA_386CGate
lgdtr:
dw 0x0000
dd 0x00010000
descriplen equ lgdtr-null1_descrip
text1len equ text1end-start-1
text2len equ text2end-text2start-1
text3len equ text3end-text3start-1
testdatalen equ testend - testdata-1
selecttext2 equ text2_descrip-null1_descrip
selecttext3 equ text3_descrip-null1_descrip
selectdata4 equ data4_descrip-null1_descrip
selectcall5 equ call5_descrip-null1_descrip
section text1 align=32
bits 16
text1start:
start:
mov ax,0x00001
mov bx,0x00001
add ax,bx
mov ax,text2len
mov [text2_descrip],ax ;limit
mov ax,text2start
mov [text2_descrip+2],ax ;低16位写进去
mov ax,text3len
mov [text3_descrip],ax ;limit
mov ax,text3start
mov [text3_descrip+2],ax ;低16位写进去
mov ax,testdatalen
mov [data4_descrip],ax ;limit
mov ax,testdata
mov [data4_descrip+2],ax ;低16位写进去
;interrupt close
cli
;open a20
in al,0x92
or al,0x02
out 0x92,al
;set lgdtr
mov ax,descriplen
mov [lgdtr],ax
mov ax,null1_descrip
mov [lgdtr+2],ax
lgdt [lgdtr]
;open prtection
mov eax,cr0
or eax,0x01
mov cr0,eax
jmp selecttext2:0 ;跳转到特权级为3的代码段
text1end:
section text2 align=32
bits 32
text2start:
mov ax,0x00002
mov bx,0x00002
add ax,bx
mov ax,selectdata4
mov es,ax
mov eax,[es:0]
jmp selectcall5:0
text2end:
section text3 align=32
bits 32
text3start:
mov ax,0x00003
mov bx,0x00003
add ax,bx
jmp $
text3end:
section data4 align=32
bits 32
testdata:
times 256 dw 0x1234
testend:
调用门描述符放在GDT表中,调用时跟跳转其他代码段一样,只不过调用门里面放置的不是程序的基地址和限制而是GDT的段选择子和在这个选择子代码段里面的偏移地址
以上代码都是在最高特权级0操作,没有改变切换特权级代码,切换特权级级代码需要使用retf reti 指令,一般都以为他们和call配对使用,其实不然返回指令也能单独使用,这里需要注意的是使用 call 进行 短跳转 的时候只压栈EIP ,长跳转时依次 CS EIP 。
短跳转堆栈变化图:
长跳转堆栈变化图:
call一个调用门也是长调用,情况应该跟上面所说的长调用差不多才对。可是,正如我们已经提到的,由于一些原因堆栈发生了切换,也就是说,call指令执行前后的堆栈已经不再是同一个。
调用门堆栈变化图:
这里只涉及两个堆栈。事实上,由于每一个任务最多都可能在4个特权级间转移(0-3特权级),所以,每个任务实际上需要4个堆栈。可是,我们只有一个ss和一个esp,那么当发生堆栈切换,我们该从哪里获得其余堆栈的ss和esp呢?实际上,这里涉及一样新事物TSS(Task-State Stack),它是一个数据结构,里面包含多个字段。
比如,我们当前所在的是ring3,当转移至ring1时,堆栈将被自动切换到由ss1和esp1指定的位置。由于只是在由外层到内层(低特权级到高特权级)切换时新堆栈才会从TSS中取得,所以TSS中没有位于最外层的ring3的堆栈信息。
TSS包含很多个字段,但是在这里,我们暂时关注偏移4到偏移27的3个ss和3个esp。当发生堆栈切换时,内层的ss和esp就是从这里取得的。
我们当前所在的是ring3,当转移至ring1时,堆栈将被自动切换到由ss1和esp1指定的位置。由于只是在由外层到内层(低特权级到高特权级)切换时新堆栈才会从TSS中取得,所以TSS中没有位于最外层的ring3的堆栈信息。
- 根据目标代码段的DPL(新的CPL)从TSS中选择应该切换至哪个ss和esp。
- 从TSS中读取新的ss和esp。在这过程中如果发现ss、esp或者TSS界限错误都会导致无效TSS异常(#TS)。
- 对ss描述符进行检验,如果发生错误,同样产生#TS 异常。
- 暂时性地保存当前ss和esp的值。
- 加载新的ss和esp。
- 将刚刚保存起来的ss和esp的值压入新栈。
- 从调用者堆栈中将参数复制到被调用者堆栈(新堆栈)中,复制参数的数目由调用门中Param Count一项来决定。如果Param Count是零的话,将不会复制参数。
- 将当前的cs和eip压栈。
- 加载调用门中指定的新的cs和eip,开始执行被调用者过程。
调用门返回过程:
1. 检查保存的cs中的RPL以判断返回时是否要变换特权级。
2. 加载被调用者堆栈上的cs和eip(此时会进行代码段描述符和选择子类型和特权级检验)。
3. 如果ret指令含有参数,则增加esp的值以跳过参数,然后esp将指向被保存过的调用者ss和esp。注意,ret的参数必须对应调用门中的Param Count 的值(其实也就是Param Count *4)。
4. 加载ss和esp,切换到调用者堆栈,被调用者的ss和esp被丢弃。在这里将会进行ss描述符、esp以及ss段描述符的检验。
5. 如果ret指令含有参数,增加esp的值以跳过参数(此时已经在调用者堆栈中)。
6. 检查ds、es、fs、gs的值,如果其中哪一个寄存器指向的段的DPL小于CPL(此规则不适用于一致代码段),那么一个空描述符会被加载到该寄存器。
综上所述,使用调用门的过程实际上分为两个部分,一部分是从低特权级到高特权级,通过调用门和call指令来实现;另一部分则是从高特权级到低特权级,通过ret指令来实现。这里需要注意调用门的特权规则要求。
调用门的特权级规则:
假设我们想由代码A转移到代码B,运用一个调用门G,即调用门G中的目标选择子指向代码B的段。实际上,我们涉及了这么几个要素:CPL、RPL、代码B的DPL(记做DPL_B)、调用门G的DPL(记做DPL_G)。A访问G这个调用门时,规则相当于访问一个数据段,要求CPL和RPL都小于或者等于DPL_G。换句话说,CPL和RPL需在更高的特权级上。
注意:如果是一致代码段的话,要求DPL_B<=CPL;如果是非一致代码段的话,call指令和jmp指令又有所不同。在用call指令时,要求DPL_B<=CPL;在用jmp指令时,只能是DPL_B=CPL。
这里我们写代码测试跳转到低特权级:
堆栈先如此设计:
;----------------------------------------------------------------------------
; 描述符类型值说明
; 其中:
; 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 陷阱门类型值
;----------------------------------------------------------------------------
;----------------------------------------------------------------------------
; 选择子类型值说明
; 其中:
; 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 字节
;
; 门
; usage: Gate Selector, Offset, DCount, Attr
; Selector: dw
; Offset: dd
; DCount: db
; Attr: db
%macro Gate 4
dw (%2 & 0FFFFh) ; 偏移 1 (2 字节)
dw %1 ; 选择子 (2 字节)
dw (%3 & 1Fh) | ((%4 << 8) & 0FF00h) ; 属性 (2 字节)
dw ((%2 >> 16) & 0FFFFh) ; 偏移 2 (2 字节)
%endmacro ; 共 8 字节
; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
;该代码从0x10000处开始存储,从硬盘的逻辑1号扇区开始存储,程序进来的时候cs=ds=0x10000
jmp start
section desc align=32
null1_descrip: Descriptor 0,0,0
text2_descrip: Descriptor 0x10000,0,DA_CR+DA_32 ;存在的可执行可读代码段属性值
text3_descrip: Descriptor 0x10000,0,DA_CR+DA_32+DA_DPL3 ;存在的可读可执行代码段
data4_descrip: Descriptor 0x10000,0,DA_DRW+DA_32+DA_DPL3 ;存在的可读写数据段属性值
call5_descrip: Gate selecttext3,0,0,DA_386CGate
sp0_6_descrip: Descriptor 0x10000,0,DA_DRW+DA_32+DA_DPL0 ;特权级0堆栈段
sp3_7_descrip: Descriptor 0x10000,0,DA_DRW+DA_32+DA_DPL3 ;特权级3堆栈段
;data8_descrip: Descriptor 0x10000,0,DA_DRW+DA_32+DA_DPL3 ;存在的可读写数据段属性值
;tss_9descrip: Gate selecttss_8,0,0,DA_386TSS
lgdtr:
dw 0x0000
dd 0x00010000
descriplen equ lgdtr-null1_descrip
text1len equ text1end-start-1
text2len equ text2end-text2start-1
text3len equ text3end-text3start-1
testdatalen equ testend - testdata-1
sp0_5len equ sp0end5 - sp0data5
sp3_6len equ sp3end6 - sp3data6
data8len equ ltrend7 - ltrdata7-1
selecttext2 equ text2_descrip-null1_descrip
selecttext3 equ text3_descrip-null1_descrip
selectdata4 equ data4_descrip-null1_descrip
selectcall5 equ call5_descrip-null1_descrip
selectsp0_6 equ sp0_6_descrip-null1_descrip
selectsp3_7 equ sp3_7_descrip-null1_descrip
;selecttss_8 equ tss_9descrip-null1_descrip
section text1 align=32
bits 16
text1start:
start:
mov ax,0x00001
mov bx,0x00001
add ax,bx
;text2
mov ax,text2len
mov [text2_descrip],ax ;limit
mov ax,text2start
mov [text2_descrip+2],ax ;低16位写进去高16位已经在初始化的时候设置了0x10000
;text3
mov ax,text3len
mov [text3_descrip],ax ;limit
mov ax,text3start
mov [text3_descrip+2],ax ;低16位写进去高16位已经在初始化的时候设置了0x10000
;data4
mov ax,testdatalen
mov [data4_descrip],ax ;limit
mov ax,testdata
mov [data4_descrip+2],ax ;低16位写进去高16位已经在初始化的时候设置了0x10000
;sp0 5
mov ax,sp0_5len
mov [sp0_6_descrip],ax ;limit
mov ax,sp0data5
mov [sp0_6_descrip+2],ax ;低16位写进去 高16位已经在初始化的时候设置了0x10000
;sp3 6
mov ax,sp3_6len
mov [sp3_7_descrip],ax ;limit
mov ax,sp3data6
mov [sp3_7_descrip+2],ax ;低16位写进去 高16位已经在初始化的时候设置了0x10000
;tss data7
; mov ax,data8len
; mov [data8_descrip],ax ;limit
; mov ax,ltrdata7
; mov [data8_descrip+2],ax ;低16位写进去 高16位已经在初始化的时候设置了0x10000
;interrupt close
cli
;open a20
in al,0x92
or al,0x02
out 0x92,al
;set lgdtr
mov ax,descriplen
mov [lgdtr],ax
mov ax,null1_descrip
mov [lgdtr+2],ax
lgdt [lgdtr]
;open prtection
mov eax,cr0
or eax,0x01
mov cr0,eax
jmp selecttext2:0 ;跳转到text2
text1end:
section text2 align=32
bits 32
text2start:
mov ax,0x00002
mov bx,0x00002
add ax,bx
mov ax,selectdata4
mov es,ax
mov eax,[es:0]
;设置堆栈
mov ax,selectsp0_6
mov ss,ax
mov ax,sp0_5len
sub ax,4
mov sp,ax
;ltr selecttss_8
;先压栈ss 注意对于es ss fs gs cs 压栈会把他们扩展到32位高16位补0,所以压入4字节
mov eax,selectsp3_7
or eax,0x03
push eax
;先压栈sp
mov eax,sp3_6len
push eax
;先压栈cs
mov eax,selecttext3
or eax,0x03
push eax
;压栈eip
mov eax,0
push eax
retf
text2end:
section text3 align=32
bits 32
text3start:
mov ax,0x00003
mov bx,0x00003
add ax,bx
jmp $
text3end:
section data4 align=32
bits 32
testdata:
times 256 dw 0x1234
testend:
section sp0_5 align=32
bits 32
sp0data5:
times 256 dw 0x1234
sp0end5:
section sp3_6 align=32
bits 32
sp3data6:
times 256 dw 0x1234
sp3end6:
section ltr_7 align=32
bits 32
ltrdata7:
dd 0x0000
;sp0
dd sp0_5len
dd selectsp0_6
;sp1
dd 0x0000
dd 0x0000
;sp2
dd 0x0000
dd 0x0000
;sp3
dd sp3_6len
dw selectsp3_7
times 256 dw 0x0000
ltrend7:
结果如图,跳转到第text3代码段 ss , cs特权级都为3了
再从低优先跳转到高优先级测试:
;----------------------------------------------------------------------------
; 描述符类型值说明
; 其中:
; 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 陷阱门类型值
;----------------------------------------------------------------------------
;----------------------------------------------------------------------------
; 选择子类型值说明
; 其中:
; 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 字节
;
; 门
; usage: Gate Selector, Offset, DCount, Attr
; Selector: dw
; Offset: dd
; DCount: db
; Attr: db
%macro Gate 4
dw (%2 & 0FFFFh) ; 偏移 1 (2 字节)
dw %1 ; 选择子 (2 字节)
dw (%3 & 1Fh) | ((%4 << 8) & 0FF00h) ; 属性 (2 字节)
dw ((%2 >> 16) & 0FFFFh) ; 偏移 2 (2 字节)
%endmacro ; 共 8 字节
; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
;该代码从0x10000处开始存储,从硬盘的逻辑1号扇区开始存储,程序进来的时候cs=ds=0x10000
jmp start
section desc align=32
null1_descrip: Descriptor 0,0,0
text2_descrip: Descriptor 0x10000,0,DA_CR+DA_32 ;存在的可执行可读代码段属性值
text3_descrip: Descriptor 0x10000,0,DA_CR+DA_32+DA_DPL3 ;存在的可读可执行代码段
data4_descrip: Descriptor 0x10000,0,DA_DRW+DA_32 ;存在的可读写数据段属性值
call5_descrip: Gate selecttext8,0,0,DA_386CGate+DA_DPL3
sp0_6_descrip: Descriptor 0x10000,0,DA_DRW+DA_32+DA_DPL0 ;特权级0堆栈段
sp3_7_descrip: Descriptor 0x10000,0,DA_DRW+DA_32+DA_DPL3 ;特权级3堆栈段
text8_descrip: Descriptor 0x10000,0,DA_CR+DA_32 ;存在的可执行可读代码段属性值
data8_descrip: Descriptor 0x10000,0,DA_386TSS+DA_DPL3 ;存在的可读写数据段属性值
lgdtr:
dw 0x0000
dd 0x00010000
descriplen equ lgdtr-null1_descrip
text1len equ text1end-start-1
text2len equ text2end-text2start-1
text3len equ text3end-text3start-1
testdatalen equ testend - testdata-1
sp0_5len equ sp0end5 - sp0data5
sp3_6len equ sp3end6 - sp3data6
data8len equ ltrend7 - ltrdata7-1
text8len equ text8end-text8start-1
selecttext2 equ text2_descrip-null1_descrip
selecttext3 equ text3_descrip-null1_descrip
selectdata4 equ data4_descrip-null1_descrip
selectcall5 equ ((call5_descrip-null1_descrip)|0x03)
selectsp0_6 equ sp0_6_descrip-null1_descrip
selectsp3_7 equ sp3_7_descrip-null1_descrip
selectdata8 equ data8_descrip-null1_descrip
selecttext8 equ text8_descrip-null1_descrip
section text1 align=32
bits 16
text1start:
start:
mov ax,0x00001
mov bx,0x00001
add ax,bx
;text2
mov ax,text2len
mov [text2_descrip],ax ;limit
mov ax,text2start
mov [text2_descrip+2],ax ;低16位写进去高16位已经在初始化的时候设置了0x10000
;text3
mov ax,text3len
mov [text3_descrip],ax ;limit
mov ax,text3start
mov [text3_descrip+2],ax ;低16位写进去高16位已经在初始化的时候设置了0x10000
;data4
mov ax,testdatalen
mov [data4_descrip],ax ;limit
mov ax,testdata
mov [data4_descrip+2],ax ;低16位写进去高16位已经在初始化的时候设置了0x10000
;sp0 5
mov ax,sp0_5len
mov [sp0_6_descrip],ax ;limit
mov ax,sp0data5
mov [sp0_6_descrip+2],ax ;低16位写进去 高16位已经在初始化的时候设置了0x10000
;sp3 6
mov ax,sp3_6len
mov [sp3_7_descrip],ax ;limit
mov ax,sp3data6
mov [sp3_7_descrip+2],ax ;低16位写进去 高16位已经在初始化的时候设置了0x10000
;text8
mov ax,text8len
mov [text8_descrip],ax ;limit
mov ax,text8start
mov [text8_descrip+2],ax ;低16位写进去 高16位已经在初始化的时候设置了0x10000
;tss data7
mov ax,data8len
mov [data8_descrip],ax ;limit
mov ax,ltrdata7
mov [data8_descrip+2],ax ;低16位写进去 高16位已经在初始化的时候设置了0x10000
;interrupt close
cli
;open a20
in al,0x92
or al,0x02
out 0x92,al
;set lgdtr
mov ax,descriplen
mov [lgdtr],ax
mov ax,null1_descrip
mov [lgdtr+2],ax
lgdt [lgdtr]
;open prtection
mov eax,cr0
or eax,0x01
mov cr0,eax
jmp selecttext2:0 ;跳转到text2
text1end:
section text2 align=32
bits 32
text2start:
mov ax,0x00002
mov bx,0x00002
add ax,bx
;设置堆栈
mov ax,selectdata4
mov ss,ax
mov ax,testdatalen
sub ax,4
mov sp,ax
mov ax,selectdata8
ltr ax
;先压栈ss
mov eax,selectsp3_7 ;注意对于es ss fs gs cs 压栈会把他们扩展到32位高16位补0,所以压入4字节
or eax,0x03
push eax
;先压栈sp
mov eax,sp3_6len
push eax
;先压栈cs
mov eax,selecttext3
or eax,0x03
push eax
;压栈eip
mov eax,0
push eax
retf
text2end:
section text3 align=32
bits 32
text3start:
mov ax,0x00003
mov bx,0x00003
add ax,bx
mov ax,selectcall5
or ax,0x03
mov bx,ax
call selectcall5:0
text3end:
section data4 align=32
bits 32
testdata:
times 256 dw 0x1234
testend:
section sp0_5 align=32
bits 32
sp0data5:
times 256 dw 0x1234
sp0end5:
section sp3_6 align=32
bits 32
sp3data6:
times 256 dw 0x1234
sp3end6:
section ltr_7 align=32
bits 32
ltrdata7:
DD 0 ; Back
DD sp0_5len-4 ; 0 级堆栈
DD selectsp0_6 ;
DD 0 ; 1 级堆栈
DD 0 ;
DD 0 ; 2 级堆栈
DD 0 ;
DD 0 ; CR3
DD 0 ; EIP
DD 0 ; EFLAGS
DD 0 ; EAX
DD 0 ; ECX
DD 0 ; EDX
DD 0 ; EBX
DD 0 ; ESP
DD 0 ; EBP
DD 0 ; ESI
DD 0 ; EDI
DD 0 ; ES
DD 0 ; CS
DD 0 ; SS
DD 0 ; DS
DD 0 ; FS
DD 0 ; GS
DD 0 ; LDT
DW 0 ; 调试陷阱标志
DW $ - ltrdata7 + 2 ; I/O位图基址
DB 0ffh ; I/O位图结束标志
ltrend7:
section text8 align=32
bits 32
text8start:
mov ax,0x00008
mov bx,0x00008
add ax,bx
jmp $
text8end:
以上代码先初始化保护模式 然后返回到低特权级代码段访问,再使用调用门跳转到高特权级,仔细观察堆栈和特权级的变化。也就是ss寄存器和cs寄存器的低3位,在使用调用门的时候特权级从3变为0,堆栈也是用了新的堆栈段
保护模式中断的处理:
保护模式使用中断与实模式相差较大,需要在像设置调用门一样,在idt中设置中断描述符或者陷阱描述符,任务描述符。中断描述符,陷阱描述符,任务描述符与调用门描述符格式基本一致。只是中断描述符,陷阱描述符,任务描述符 ParmaCount 保留为0.
中断与IDT寄存器密切相关(IDT类似于gdt),IDT中的描述符可以是下面三种之一:
- 中断门描述符
- 陷阱门描述符
- 任务门描述符
中断向量到中断处理程序的对应过程如图:
所以在保护模式使用中断时需要先设置idt描述符表,中断描述符格式如图:
每个中断对应一个描述符,保护模式中断异常向量如图:
外部中断使用的8259A:
NMI不可屏蔽,因为它与IF是否被设置无关。NMI中断对应的中断向量号为2,可屏蔽中断与CPU的关系是通过对可编程中断控制器8259A建立起来的。它是中断机制中所有外围设备的一个代理,这个代理不但可以根据优先级在同时发生中断的设备中选择应该处理的请求,而且可以通过对其寄存器的设置来屏蔽或打开相应的中断。与CPU相连的不是一片,而是两片级联的8259A,每个8259A有8根中断信号线,于是两片级联总共可以挂接15个不同的外部设备。这些设备发出的中断请求与中断向量对应起来是通过对8259A的设置完成的。在BIOS初始化它的时候,IRQ0~IRQ7被设置为对应向量号08h~0Fh,而在保护模式下向量号08h-0Fh已经被占用了,所以我们不得不重新设置主从8259A。8259A是可编程中断控制器,对它的设置并不复杂,是通过向相应的端口写入特定的ICW(Initialization CommandWord)来实现的。主8259A对应的端口地址是20h和21h,从8259A对应的端口地址是A0h和A1h。ICW共有4个,每一个都是具有特定格式的字节。为了先对初始化8259A的过程有一个概括的了解,我们过一会儿再来关注每一个ICW的格式,现在,先来看一下初始化过程:
1. 往端口20h(主片)或A0h(从片)写入ICW1。
2. 往端口21h(主片)或A1h(从片)写入ICW2。
3. 往端口21h(主片)或A1h(从片)写入ICW3。
4. 往端口21h(主片)或A1h(从片)写入ICW4
这里我们写一个总的测试代码:从0特权级, 到 3特权级, 设置tss,测试调用门调用跳转高特权级代码运行,测试调用门带参数跳转,测试调用门的带参数返回,设置idt测试软件中断代码:
;----------------------------------------------------------------------------
; 描述符类型值说明
; 其中:
; DA_ : Descriptor Attribute
; D : 数据段
; C : 代码段
; S : 系统段
; R : 只读
; RW : 读写
; A : 已访问
; 其它 : 可按照字面意思理解
;----------------------------------------------------------------------------
DG_4KB EQU 0x8000 ;limit * 4KB
DG_BYTE EQU 0x0000 ;limit * 1byte
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 陷阱门类型值
;----------------------------------------------------------------------------
;----------------------------------------------------------------------------
; 选择子类型值说明
; 其中:
; 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 字节
;
; 门
; usage: Gate Selector, Offset, DCount, Attr
; Selector: dw
; Offset: dd
; DCount: db
; Attr: db
%macro Gate 4
dw (%2 & 0FFFFh) ; 偏移 1 (2 字节)
dw %1 ; 选择子 (2 字节)
dw (%3 & 1Fh) | ((%4 << 8) & 0FF00h) ; 属性 (2 字节)
dw ((%2 >> 16) & 0FFFFh) ; 偏移 2 (2 字节)
%endmacro ; 共 8 字节
; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
;^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^所有 equ 定义地方^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
;描述符里面的限制
text1_limit equ text1_end - text1_start - 1
text2_limit equ text2_end - text2_start - 1
text3_limit equ text3_end - text3_start - 1
text4_limit equ text4_end - text4_start - 1
text5_limit equ text5_end - text5_start - 1
gdt_limit equ gdtr_desc_end - gdtr_desc_start - 1
idt_limit equ idtr_desc_end - idtr_desc_start - 1
sp0_limit equ data2_sp0_end - data2_sp0_start - 1
sp3_limit equ data3_sp3_end - data3_sp3_start - 1
data4sp0_limit equ data4_sp0_end - data4_sp0_start - 1
tss_limit equ tss_end - tss_start - 1
;选择描述符
select_text2 equ text2_descrip - gdtr_desc_start
select_text3 equ (text3_descrip - gdtr_desc_start)|SA_RPL3
select_text4 equ (text4_descrip - gdtr_desc_start);|SA_RPL3
select_text5 equ text5_descrip - gdtr_desc_start
select_data1 equ data1_descrip - gdtr_desc_start
select_sp0 equ data2_sp0_descrip - gdtr_desc_start
select_sp3 equ (data3_sp3_descrip - gdtr_desc_start)|SA_RPL3
select_data4sp0 equ (data4_sp0_descrip - gdtr_desc_start)
select_tss equ tss_descrip - gdtr_desc_start
select_gate1 equ (gate1_descrip - gdtr_desc_start)|SA_RPL3
;内存地址定义
ADDR_4MSize equ 1024*1024*4
;代码说明:
;从实模式代码段1跳转到代码段2保护模式,再跳转到低特权级,测试调用门,再测试返回
;^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^代码段^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
jmp text1_start ;这是从boot跳转到此处 ds=0x1000 cs=0x1000
;~~~~~~~~~~~~~~~~text1~~~~~~~~~~~~~~~~
section text1 align=32
bits 16
text1_start:
mov eax,0x01
init_protect:
;设置 gdt表
;代码段 2
mov ax,text2_start
mov [text2_descrip+2],ax
;堆栈 0 level
mov ax,data2_sp0_start
mov [data2_sp0_descrip+2],ax
;堆栈 3 level
mov ax,data3_sp3_start
mov [data3_sp3_descrip+2],ax
;代码段 3
mov ax,text3_start
mov [text3_descrip+2],ax
;代码段 4
mov ax,text4_start
mov [text4_descrip+2],ax
;代码段 5
mov ax,text5_start
mov [text5_descrip+2],ax
;tss
mov ax,tss_start
mov [tss_descrip+2],ax
;堆栈 4,0 level
mov ax,data4_sp0_start
mov [data4_sp0_descrip+2],ax
;init gdt
init_gdt:
;text2 descrip
mov ax,gdt_limit
mov [gdtr_48_start],ax
mov ax,gdtr_desc_start
mov [gdtr_48_start+2],ax
lgdt [gdtr_48_start]
;打开A20地址
in al,0x92
or al,0x02
out 0x92,al
;打开保护模式
cli
mov eax,cr0
or eax,0x01
mov cr0,eax
jmp select_text2:0
text1_end:
;~~~~~~~~~~~~~~~~text2~~~~~~~~~~~~~~~~
section text2 align=32
bits 32
text2_start:
mov eax,0x02
mov ax,select_data1
mov es,ax
mov ebp,ADDR_4MSize
mov dword [es:ebp],0x12345678 ;测试读写4M字节处检验地址访问
;init idt
init_idt:
mov ax,idt_limit
mov [idtr_48_start],ax
mov ax,idtr_desc_start
mov [idtr_48_start+2],ax
lidt [idtr_48_start]
;为下一个任务调用调用门做准备
mov ax,select_tss
ltr ax
;初始化0特权级堆栈
mov ax,select_sp0
mov ss,ax
mov eax,sp0_limit
mov esp,eax
init_8259A:
mov al, 011h
out 020h, al ; 主8259, ICW1.
call io_delay
out 0A0h, al ; 从8259, ICW1.
call io_delay
mov al, 020h ; IRQ0 对应中断向量 0x20
out 021h, al ; 主8259, ICW2.
call io_delay
mov al, 028h ; IRQ8 对应中断向量 0x28
out 0A1h, al ; 从8259, ICW2.
call io_delay
mov al, 004h ; IR2 对应从8259
out 021h, al ; 主8259, ICW3.
call io_delay
mov al, 002h ; 对应主8259的 IR2
out 0A1h, al ; 从8259, ICW3.
call io_delay
mov al, 001h
out 021h, al ; 主8259, ICW4.
call io_delay
out 0A1h, al ; 从8259, ICW4.
call io_delay
mov al, 11111111b ; 仅仅开启定时器中断
;mov al, 11111111b ; 屏蔽主8259所有中断
out 021h, al ; 主8259, OCW1.
call io_delay
mov al, 11111111b ; 屏蔽从8259所有中断
out 0A1h, al ; 从8259, OCW1.
call io_delay
sti
;跳转到特权级3代码段
push select_sp3
push sp3_limit
push select_text3
push 0 ;因为描述符里面的基地址就是text3的开始地址所以我们就直接压栈地址0即可
retf
io_delay:
nop
nop
nop
nop
ret
text2_end:
;~~~~~~~~~~~~~~~~text3~~~~~~~~~~~~~~~~
section text3 align=32
bits 32
text3_start:
mov eax,0x03
mov ebx,0x04
mov ecx,0x05
mov edx,0x06
push eax
push ebx
push ecx
push edx
call select_gate1:0
mov eax,data2_sp0_start
mov ebx,data3_sp3_start
mov ecx,data4_sp0_start
mov edx,gdtr_desc_start
int 0x80
jmp $
text3_end:
;~~~~~~~~~~~~~~~~text4~~~~~~~~~~~~~~~~
section text4 align=32
bits 32
text4_start:
mov eax,0x04
retf 16
jmp $
text4_end:
;~~~~~~~~~~~~~~~~text4~~~~~~~~~~~~~~~~
section text5 align=32
bits 32
text5_start:
mov eax,eax
mov ebx,ebx
mov ecx,ecx
mov edx,edx
;pop eax ;有错误码需要弹出错误码
iretd
text5_end:
;^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^idt描述符段^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
;idt descrip
section idtr_desc align=32
idtr_desc_start:
%rep 256
Gate select_text5,0,0,(DA_386IGate|DA_DPL3) ;宏定义最好加上括号
%endrep
; times 256*2 -2 dd 0x0000_0000
idtr_desc_end:
;^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^gdt代码段^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
;gdt descrip
section gdtr_desc align=32
gdtr_desc_start:
null1_descrip: Descriptor 0,0,0
text2_descrip: Descriptor 0x10000,text2_limit,(DA_CR|DA_32) ;text2代码段 宏定义最好加上括号
data1_descrip: Descriptor 0x00000,0x8000-1,(DA_DRW|DA_32|DA_DPL3|DG_4KB) ;32M字节描述所有数据宏定义最好加上括号
data2_sp0_descrip: Descriptor 0x10000,sp0_limit,(DA_DRW|DA_32)
data3_sp3_descrip: Descriptor 0x10000,sp3_limit,(DA_DRW|DA_32|DA_DPL3)
text3_descrip: Descriptor 0x10000,text3_limit,(DA_CR|DA_32|DA_DPL3) ;text3代码段宏定义最好加上括号
tss_descrip: Descriptor 0x10000,tss_limit,(DA_386TSS)
text4_descrip: Descriptor 0x10000,text4_limit,(DA_CR|DA_32);|DA_DPL3 ;text4代码段宏定义最好加上括号
data4_sp0_descrip: Descriptor 0x10000,data4sp0_limit,(DA_DRW|DA_32)
gate1_descrip: Gate select_text4,0,4,(DA_386CGate|DA_DPL3)
text5_descrip: Descriptor 0x10000,text5_limit,(DA_CR|DA_32) ;text5代码段宏定义最好加上括号
gdtr_desc_end:
;^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^tss 数据段^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
section tss align=32
tss_start:
dd 0x00 ;0
dd data4sp0_limit;esp0
dd select_data4sp0;ss0
dd 0x00;esp1
dd 0x00;ss1
dd 0x00;esp2
dd 0x00;ss2
dd 0x00 ;gr3 pdbr
dd 0x00 ;eip
dd 0x00 ;eflag
dd 0x00;eax
dd 0x00;ecx
dd 0x00;edx
dd 0x00;ebx
dd 0x00;esp
dd 0x00;ebp
dd 0x00;esi
dd 0x00;edi
dd 0x00;es
dd 0x00;cs
dd 0x00;ss
dd 0x00;ds
dd 0x00;fs
dd 0x00;gs
dd 0x00;LDT select
dw 0x00;调试陷阱标志
dw $-tss_start+2;设置IO访问使能的位的偏移地址
db 0xff;结尾标志
tss_end:
;^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^数据段^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
section data2_sp0 align=32;数据段初始化用来做堆栈 0特权级
data2_sp0_start:
data2_start equ (data2_sp0_start+0x10000)
times 1024 db 0x00
data2_sp0_end:
section data3_sp3 align=32;数据段初始化用来做堆栈 3特权级
data3_sp3_start:
data3_start equ (data3_sp3_start+0x10000)
times 1024 db 0x00
data3_sp3_end:
section data4_sp0 align=32;数据段初始化用来做堆栈 3特权级
data4_sp0_start:
times 1024 db 0x00
data4_sp0_end:
;^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^gdtr idtr指针设置处^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
;设置idtr
section idtr_addr align=32
idtr_48_start:
dw 0x0000
dd 0x0001_0000
idtr_48_end:
;设置gdtr
section gdtr_addr align=32
gdtr_48_start:
dw 0x0000
dd 0x0001_0000
gdtr_48_end:
压栈特权级转移:
转移到低特权级代码段
调用门跳转前处理器状态:
调用门跳转后处理器状态:
调用门返回后处理器状态:
中断前处理器状态:
中断后出处理器状态:
中断返回后处理器状态:
以上是对中断门,调用门,特权级转移,栈切换,的代码测试。 可以把中断门描述符改为陷阱门描述符,在陷阱门中断函数执行时不清除eflags 的中断标志位。(中断门时eflag=0x00000006,陷阱门时eflag=0x00000206),陷阱门和中断门的区别就在于此,陷阱门不清除 if 标志位允许中断,中断门清除 if 标志位,不允许中断。
陷阱门中断执行时结果如图:
描述符总结:
代码段描述符,数据段描述符放在gdt和ldt中格式如图:
ldt描述符:
tss描述符:
调用门 描述符放在gdt中格式如图:
中断门,陷阱门,放在idt中,(任务门描述符可以放在GDT,LDT和IDT中)任务门描述符:
由此可以看出 门描述符格式基本一致,代码段,数据段,tss段ldt段,ldt段描述符基本一致。
参考资料:
《x86汇编语言-从实模式到保护模式》 作者:李忠
《自己动手写操作系统》 作者:于渊
《Orange‘s一个操作系统的实现》 作者:于渊
《汇编语言》 作者:王爽