X86架构学习笔记保护模式中断与特权

        在前面简单介绍了保护模式以及主要的lgdt表的属性,以及怎么进入保护模式简要说明就是:

 

  1. 设置需要使用的段属性表
  2. 设置lgdt属性表地址以及长度限制
  3. 打开A20地址
  4. 关闭中断(因为没有设置中断表lidt)
  5. 设置CR0寄存器打开保护模式
  6. 跳入保护模式执行

        这只是最简单的进入保护模式吗,不涉及特权级的变化以及系统段的改变,中断的影响,因此我们继续分析特权变化,系统段,以及中断。

最主要的段属性描述符以及段描述符索引如图:

 代码段和数据段描述符

 选择子(Selector)的结构

 gdtr示意图

 CR0寄存器

        寄存器cr0的第0位是PE位,此位为0时,CPU运行于实模式,为1时,CPU运行于保护模式。原来我们已经闭合了进入保护模式的开关,也就是说,“mov cr0, eax”这一句之后,系统就运行于保护模式之下了。但是,此时cs的值仍然是实模式下的值,我们需要把代码段的选择子装入cs。

再次回忆段属性表的主要部分

  1. 段界限
  2. 段基址
  3. G粒度位与段界限联合使用 G=1表示4KB单位,G=0字节单位
  4. D/B位指示32位代码段和数据段指明32位代码段使用EIP,数据段默认压栈4字节使用ESP ,这位为1表示32位,为0跟实模式一样。
  5. AVL保留不使用
  6. P存在位,指明段是否在内存中存在
  7. DPL该段特权级指明
  8. S位S=1是代码段/数据段,S=0是系统段/门描述
  9. 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 niret引起的,也可以由中断和异常机制引起。

        使用jmp或call指令可以实现下列4种转移:
        1. 目标操作数包含目标代码段的段选择子。
        2. 目标操作数指向一个包含目标代码段选择子的调用门描述符。
        3. 目标操作数指向一个包含目标代码段选择子的TSS。
        4. 目标操作数指向一个任务门,这个任务门指向一个包含目标代码段选择子的TSS。

        这4 种方式可以看做是两大类,一类是通过jmp和call的直接转移(上述第1种),另一类是通过某个描述符的间接转移(上述第2、3、4种)。

        对通过jmp或call进行直接转移已经有过一些讨论,

  1. 如果目标是非一致代码段,要求CPL必须等于目标段的DPL,同时要求RPL小于等于DPL;
  2.  如果目标是一致代码段,则要求CPL大于或者等于目标段的DPL,RPL此时不做检查。当转移到一致代码段中后,CPL会被延续下来,而不会变成目标代码段的DPL。也就是说,通过jmp和call所能进行的代码段间转移是非常有限的
  3.  对于非一致代码段,只能在相同特权级代码段之间转移。遇到一致代码段也最多能从低到高,而且CPL不会改变。如果想自由地进行不同特权级之间的转移,显然需要其他几种方式,即运用门描述符或者TSS

        一个门描述了由一个选择子和一个偏移所指定的线性地址,程序正是通过这个地址进行转移的。门描述符分为4种:

  1. 调用门(Call gates)
  2. 中断门(Interrupt gates)
  3. 陷阱门(Trap gates)
  4. 任务门(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的堆栈信息。

  1. 根据目标代码段的DPL(新的CPL)从TSS中选择应该切换至哪个ss和esp。
  2. 从TSS中读取新的ss和esp。在这过程中如果发现ss、esp或者TSS界限错误都会导致无效TSS异常(#TS)。
  3. 对ss描述符进行检验,如果发生错误,同样产生#TS 异常。
  4.  暂时性地保存当前ss和esp的值。
  5.  加载新的ss和esp。
  6.  将刚刚保存起来的ss和esp的值压入新栈。
  7. 从调用者堆栈中将参数复制到被调用者堆栈(新堆栈)中,复制参数的数目由调用门中Param  Count一项来决定。如果Param Count是零的话,将不会复制参数。
  8.  将当前的cs和eip压栈。
  9.  加载调用门中指定的新的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中的描述符可以是下面三种之一:

  1. 中断门描述符
  2. 陷阱门描述符
  3. 任务门描述符

        中断向量到中断处理程序的对应过程如图:

所以在保护模式使用中断时需要先设置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一个操作系统的实现》                作者:于渊

        《汇编语言》                                                 作者:王爽

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值