80386保护模式--GDT,LDT,TSS,调用门,特权级转移,附pmtest5代码详解

       教材选择一个操作系统的实现,作者于渊,看此书前最好了有汇编语言,保护模式下的汇编(参考清华杨季文的80X86汇编语言程序设计教程),C语言,计算机组成原理,微机接口,操作系统相关知识。

一、80386的寄存器结构

       80386微处理器共有7类34个寄存器,通用寄存器组、段寄存器、指令指针和标志寄存器、系统地址寄存器、控制寄存器、调试寄存器、测试寄存器。前四类寄存器的示意图1。其中描述符高速缓冲寄存器0-31存放段首地址,0-19存放段界限,0-11存放段属性,LDTR与此类似。后面我们会看到当装入选择子后,对应的描述符恰好是32位,20位,12位共8个字节,正好能装入高速缓存寄存器。GDTR中0-31为GDTR的首地址,0-15为GDTR的界限,为什么GDTR的界限比其他的少4位呢,因为我们GDTR不需要那么大。另外控制寄存器CR0,CR1,CR2,CR3,如图2。CR0的处理器工作模式如图3。

                                            80386保护模式--GDT,LDT,TSS,调用门,特权级转移,附pmtest5代码详解_第1张图片                                                                          

                             图1  通用寄存器组、段寄存器、指令指针和标志寄存器、系统地址寄存器示意图 

                                           80386保护模式--GDT,LDT,TSS,调用门,特权级转移,附pmtest5代码详解_第2张图片

                                                                               图2 控制寄存器

            80386保护模式--GDT,LDT,TSS,调用门,特权级转移,附pmtest5代码详解_第3张图片

                                                            图3 PG/PE位与处理器工作模式


 二、80386描述符

       此部分请看http://blog.csdn.net/jltxgcy/article/details/8656101,里面详细介绍了描述符包括,存储段描述符(代码段,数据段,堆栈段),系统描述符(任务状态段TSS,局部描述符表LDT),门描述符(调用门,任务门,中断门,陷阱门),还介绍了选择子,同时介绍了pm.inc中的代码。


三、80386 16位实模式与32位保护模式下指令执行

    1、16位实模式下指令执行过程

    ①代码被加载到内存,段寄存器被赋值,从CS左移4位加上IP的值,形成20位地址,再加上高12位地址全部为0,形成32位地址,到此地址取得命令。注意由于没有开A20地址线,所以还保留8086的地址环绕现象。

    ②IP=IP+所指指令的长度。

    ③执行指令(个别指令执行的时候修改CS,IP),跳到①继续执行。


    2、32位保护模式下指令执行过程(省略了严格的检查机制)

    段选择子装入CS,CS描述符高速缓存寄存器装入代码段描述符,包括首地址,界限,属性。

    ①CS描述符高速缓存寄存器中基地址+EIP(偏移),形成32位地址,到此地址取得命令

    ②EIP=EIP+所指指令的长度

    ③执行指令(个别指令执行的时候修改CS描述符高速缓存寄存器,EIP),跳到①继续执行。


四、特权级变化时堆栈的变化及调用门特权级规则

    1、特权级变化时堆栈变化如图4,一般用call之前都会push参数,所以会出现参数。

           80386保护模式--GDT,LDT,TSS,调用门,特权级转移,附pmtest5代码详解_第4张图片

                          图4 有特权级变换的转移时堆栈变化

    每个任务最多在4个特权级间转移,所以每个任务需要四个堆栈,可是我们只有一个SS和ESP,那么当发生堆栈切换,我们该从哪里获得其他堆栈的SS和ESP呢?实际上这里涉及到一个新的食物TSS,它是一个数据结构,里面包含着许多字段,32位TSS如图5。

    TSS包含很多字段,但是在这里,我们只关心偏移4到偏移27的3个SS和3个ESP。当堆栈发生变化时,内层的SS和ESP就是从这里取得的,比如,我们当前所在的是ring3,当转移到ring1时,堆栈将被自动切换到由ss1和esp1指定的位置。由于只有在外层到内层(低特权级到高特权级)切换时新堆栈才会从TSS中取得,所以TSS中没有位于最外层的ring3的堆栈信息。

                               80386保护模式--GDT,LDT,TSS,调用门,特权级转移,附pmtest5代码详解_第5张图片

                                                            图 5 32位(每个是4个字节)的TSS


       2、调用门特权级规则

                         80386保护模式--GDT,LDT,TSS,调用门,特权级转移,附pmtest5代码详解_第6张图片

       CPL小于等于DPL_G,RPL小于等于DPL_G,因为访问门控制符就像访问数据段,访问数据段也是CPL小于等于DPL_D,RPL小于等于DPL D。G代表门描述符的特权级,D代表数据段描述符的特权级。

五、pmtest5.asm代码详细分析如下:如无显示指出数据段寄存器,默认为ds。

;执行此程序显示结果为In Protect Mode now. ^-^
                     ;3 C L
%include	"pm.inc"	; 常量, 宏, 以及一些说明

org	0100h
	jmp	LABEL_BEGIN  ;当程序被加载后CS等段寄存器也被赋予了初值,CS为00000010,IP为0,首地址为000000000 00000100,
	                 ;跳转指令,截取相对地址的最后16位,改变了IP

;所有带:的,例如LABEL_GDT: 都是32位段偏移地址,后来可以截断,条件是前面16位都为0
[SECTION .gdt]
; GDT  在此可以直接赋上段基地址,如果在代码中那么要根据pm.inc的位置来赋值
;GDT 最大长度为2的13次方再乘以8个字节,为64KB
;所有的描述符都是有对应的代码,数据,堆栈,LDT,TSS,调用门则是调用现有的,之所有用调用门,是为了实现不同特权级代码之间的转移
;                      段基址(32位),   段界限(32位)后被截取     , 属性(12位)
LABEL_GDT:             Descriptor 0,                 0, 0		   ;空描述符     
LABEL_DESC_NORMAL:     Descriptor 0,            0ffffh, DA_DRW		   ;Normal描述符
LABEL_DESC_CODE32:     Descriptor 0,    SegCode32Len-1, DA_C+DA_32	   ;非一致,32
LABEL_DESC_CODE16:     Descriptor 0,            0ffffh, DA_C		   ;非一致,16
LABEL_DESC_CODE_DEST:  Descriptor 0,  SegCodeDestLen-1, DA_C+DA_32	   ;非一致,32
LABEL_DESC_CODE_RING3: Descriptor 0, SegCodeRing3Len-1, DA_C+DA_32+DA_DPL3 ;
LABEL_DESC_DATA:       Descriptor 0,	     DataLen-1, DA_DRW             ;Data
LABEL_DESC_STACK:      Descriptor 0,        TopOfStack, DA_DRWA+DA_32	   ;Stack,32
LABEL_DESC_STACK3:     Descriptor 0,       TopOfStack3, DA_DRWA+DA_32+DA_DPL3 ;
LABEL_DESC_LDT:        Descriptor 0,          LDTLen-1, DA_LDT		   ;LDT
LABEL_DESC_TSS:        Descriptor 0,          TSSLen-1, DA_386TSS	   ;TSS
LABEL_DESC_VIDEO:      Descriptor 0B8000h,      0ffffh, DA_DRW+DA_DPL3 ;为了让处于ring3的代码可以访问显存

; 门                                            目标选择子,       偏移, DCount, 属性
LABEL_CALL_GATE_TEST:	Gate		  SelectorCodeDest,          0,      0, DA_386CGate + DA_DPL3 ;CPL小于等于DPL_G,
                                                                                                  ;RPL小于等于DPL_G
; GDT 结束

GdtLen		equ	$ - LABEL_GDT	; GDT长度 $表示当前偏移地址
GdtPtr		dw	GdtLen - 1	; GDT界限 ,截取最后16位 因为2的16次方是64KB
		dd	0		; GDT基地址

; GDT 选择子
;低三位永远为0,因为每个描述符占8个字节。第一个是 00000000 00000000 00000000 00001000
;第二个是00000000 00000000 00000000 00002000,第三个是00000000 00000000 00000000 00003000
SelectorNormal		equ	LABEL_DESC_NORMAL	- LABEL_GDT
SelectorCode32		equ	LABEL_DESC_CODE32	- LABEL_GDT
SelectorCode16		equ	LABEL_DESC_CODE16	- LABEL_GDT
SelectorCodeDest	equ	LABEL_DESC_CODE_DEST	- LABEL_GDT
SelectorCodeRing3	equ	LABEL_DESC_CODE_RING3	- LABEL_GDT + SA_RPL3
SelectorData		equ	LABEL_DESC_DATA		- LABEL_GDT
SelectorStack		equ	LABEL_DESC_STACK	- LABEL_GDT
SelectorStack3		equ	LABEL_DESC_STACK3	- LABEL_GDT + SA_RPL3
SelectorLDT		equ	LABEL_DESC_LDT		- LABEL_GDT
SelectorTSS		equ	LABEL_DESC_TSS		- LABEL_GDT
SelectorVideo		equ	LABEL_DESC_VIDEO	- LABEL_GDT

SelectorCallGateTest	equ	LABEL_CALL_GATE_TEST	- LABEL_GDT + SA_RPL3;CPL小于等于DPL_G,RPL小于等于DPL_G
; END of [SECTION .gdt]

[SECTION .data1]	 ; 数据段
ALIGN	32
[BITS	32]
LABEL_DATA:
SPValueInRealMode	dw	0
; 字符串
PMMessage:		db	"In Protect Mode now. ^-^", 0	; 进入保护模式后显示此字符串
OffsetPMMessage		equ	PMMessage - $$ ;相对于数据段首地址的偏移,保护模式下用,$$表示当前段的首偏移地址 
StrTest:		db	"ABCDEFGHIJKLMNOPQRSTUVWXYZ", 0
OffsetStrTest		equ	StrTest - $$
DataLen			equ	$ - LABEL_DATA
; END of [SECTION .data1]


; 全局堆栈段
[SECTION .gs]
ALIGN	32
[BITS	32]
LABEL_STACK:
	times 512 db 0
TopOfStack	equ	$ - LABEL_STACK - 1 ;最后一个位置为ESP的开始,说明栈的最后一个位置没有数据
; END of [SECTION .gs]


; 堆栈段ring3
[SECTION .s3]
ALIGN	32
[BITS	32]
LABEL_STACK3:
	times 512 db 0
TopOfStack3	equ	$ - LABEL_STACK3 - 1
; END of [SECTION .s3]


; TSS ---------------------------------------------------------------------------------------------
[SECTION .tss]
ALIGN	32
[BITS	32]
LABEL_TSS:
		DD	0			; Back
		DD	TopOfStack		; 0 级堆栈,现处于0级
		DD	SelectorStack		; 
		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	$ - LABEL_TSS + 2	; I/O位图基址 截取后16位
		DB	0ffh			; I/O位图结束标志
TSSLen		equ	$ - LABEL_TSS
; TSS ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^


[SECTION .s16]
[BITS	16]
LABEL_BEGIN:
	mov	ax, cs
	mov	ds, ax
	mov	es, ax
	mov	ss, ax
	mov	sp, 0100h

	mov	[LABEL_GO_BACK_TO_REAL+3], ax ;,ds作为段基地址LABEL_GO_BACK_TO_REAL地址截取后16位作为偏移,把cs的内容放到里面,
									  ;为了日后恢复实模式做准备
	mov	[SPValueInRealMode], sp   ;同理,也是为了日后恢复实模式做准备

	; 初始化 16 位代码段描述符
	mov	ax, cs
	movzx	eax, ax
	shl	eax, 4
	add	eax, LABEL_SEG_CODE16 ;实模式下段基地址*16+偏移地址=保护模式下段的首地址
	mov	word [LABEL_DESC_CODE16 + 2], ax ;+2 +4 +7的原因请看pm.inc
	shr	eax, 16
	mov	byte [LABEL_DESC_CODE16 + 4], al;LABEL_DESC_CODE16 + 4截取后16位为偏移地址,因为全0所以截取无所谓
	mov	byte [LABEL_DESC_CODE16 + 7], ah

	; 初始化 32 位代码段描述符
	xor	eax, eax
	mov	ax, cs
	shl	eax, 4
	add	eax, LABEL_SEG_CODE32
	mov	word [LABEL_DESC_CODE32 + 2], ax
	shr	eax, 16
	mov	byte [LABEL_DESC_CODE32 + 4], al
	mov	byte [LABEL_DESC_CODE32 + 7], ah

	; 初始化测试调用门的代码段描述符
	xor	eax, eax
	mov	ax, cs
	shl	eax, 4
	add	eax, LABEL_SEG_CODE_DEST
	mov	word [LABEL_DESC_CODE_DEST + 2], ax
	shr	eax, 16
	mov	byte [LABEL_DESC_CODE_DEST + 4], al
	mov	byte [LABEL_DESC_CODE_DEST + 7], ah

	; 初始化数据段描述符
	xor	eax, eax
	mov	ax, ds
	shl	eax, 4
	add	eax, LABEL_DATA
	mov	word [LABEL_DESC_DATA + 2], ax
	shr	eax, 16
	mov	byte [LABEL_DESC_DATA + 4], al
	mov	byte [LABEL_DESC_DATA + 7], ah

	; 初始化堆栈段描述符
	xor	eax, eax
	mov	ax, ds
	shl	eax, 4
	add	eax, LABEL_STACK
	mov	word [LABEL_DESC_STACK + 2], ax
	shr	eax, 16
	mov	byte [LABEL_DESC_STACK + 4], al
	mov	byte [LABEL_DESC_STACK + 7], ah

	; 初始化堆栈段描述符(ring3)
	xor	eax, eax
	mov	ax, ds
	shl	eax, 4
	add	eax, LABEL_STACK3
	mov	word [LABEL_DESC_STACK3 + 2], ax
	shr	eax, 16
	mov	byte [LABEL_DESC_STACK3 + 4], al
	mov	byte [LABEL_DESC_STACK3 + 7], ah

	; 初始化 LDT 在 GDT 中的描述符
	xor	eax, eax
	mov	ax, ds
	shl	eax, 4
	add	eax, LABEL_LDT
	mov	word [LABEL_DESC_LDT + 2], ax
	shr	eax, 16
	mov	byte [LABEL_DESC_LDT + 4], al
	mov	byte [LABEL_DESC_LDT + 7], ah

	; 初始化 LDT 中的描述符
	xor	eax, eax
	mov	ax, ds
	shl	eax, 4
	add	eax, LABEL_CODE_A
	mov	word [LABEL_LDT_DESC_CODEA + 2], ax
	shr	eax, 16
	mov	byte [LABEL_LDT_DESC_CODEA + 4], al
	mov	byte [LABEL_LDT_DESC_CODEA + 7], ah

	; 初始化Ring3描述符
	xor	eax, eax
	mov	ax, ds
	shl	eax, 4
	add	eax, LABEL_CODE_RING3
	mov	word [LABEL_DESC_CODE_RING3 + 2], ax
	shr	eax, 16
	mov	byte [LABEL_DESC_CODE_RING3 + 4], al
	mov	byte [LABEL_DESC_CODE_RING3 + 7], ah

	; 初始化 TSS 描述符
	xor	eax, eax
	mov	ax, ds
	shl	eax, 4
	add	eax, LABEL_TSS
	mov	word [LABEL_DESC_TSS + 2], ax
	shr	eax, 16
	mov	byte [LABEL_DESC_TSS + 4], al
	mov	byte [LABEL_DESC_TSS + 7], ah

	; 为加载 GDTR 作准备
	xor	eax, eax
	mov	ax, ds
	shl	eax, 4
	add	eax, LABEL_GDT		; eax <- gdt 基地址
	mov	dword [GdtPtr + 2], eax	; [GdtPtr + 2] <- gdt 基地址 

	; 加载 GDTR
	lgdt	[GdtPtr] ;GDTR寄存器的值发生改变

	; 关中断
	cli

	; 打开地址线A20
	in	al, 92h
	or	al, 00000010b
	out	92h, al  ;如果不打开会形成地址环绕

	; 准备切换到保护模式
	mov	eax, cr0
	or	eax, 1
	mov	cr0, eax ;cr0的PE位置为1,启动保护模式,这句话很重要,如果不启动保护模式,下面的jmp还是按照实模式下的方式跳转

	; 真正进入保护模式
	jmp	dword SelectorCode32:0	; 执行这一句会把 SelectorCode32 装入 cs, 并跳转到 Code32Selector:0  处
								;dword使0为32位,否则会像jmp	LABEL_BEGIN 被截断
								;此时CS描述符高速缓存寄存器被置为LABEL_SEG_CODE32的描述符,以后就直接从这里取得首地址

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

LABEL_REAL_ENTRY:		; 从保护模式跳回到实模式就到了这里
	mov	ax, cs
	mov	ds, ax
	mov	es, ax
	mov	ss, ax

	mov	sp, [SPValueInRealMode]

	in	al, 92h		; ┓
	and	al, 11111101b	; ┣ 关闭 A20 地址线
	out	92h, al		; ┛

	sti			; 开中断

	mov	ax, 4c00h	; ┓
	int	21h		; ┛回到 DOS
; END of [SECTION .s16]


[SECTION .s32]; 32 位代码段. 由实模式跳入.
[BITS	32]

LABEL_SEG_CODE32: ;从实模式跳入了保护模式,处于ring0级别
	mov	ax, SelectorData
	mov	ds, ax			; 数据段选择子,改变了DS描述符高速缓存寄存器,以后就从这里取得首地址
	mov	ax, SelectorVideo
	mov	gs, ax			; 视频段选择子,改变了GS描述符高速缓存寄存器,以后就从这里取得首地址

	mov	ax, SelectorStack
	mov	ss, ax			; 堆栈段选择子,改变了SS描述符高速缓存寄存器,以后就从这里取得首地址

	mov	esp, TopOfStack


	; 下面显示一个字符串
	mov	ah, 0Ch			; 0000: 黑底    1100: 红字
	xor	esi, esi
	xor	edi, edi
	mov	esi, OffsetPMMessage	; 源数据偏移
	mov	edi, (80 * 10 + 0) * 2	; 目的数据偏移。屏幕第 11 行, 第 1 列。
	cld ;esi自动加1
.1:
	lodsb     ;从ds:esi中取数据放入al中,以DS描述符高速缓存寄存器,取得首地址,加上esi的偏移形成32位地址,取得数据
	test	al, al
	jz	.2
	mov	[gs:edi], ax ;ah中存放字体的颜色,al中存放字体的内容,以GS描述符高速缓存寄存器,取得首地址,
	                 ;加上edi的偏移形成32位地址,存放数据
	add	edi, 2
	jmp	.1
.2:	; 显示完毕

	call	DispReturn ;EIP变化,用ret返回

	; Load TSS
	mov	ax, SelectorTSS ;加载到TR选择器中,然后TR高速缓存就变化成了TR的描述符
	ltr	ax	; 在任务内发生特权级变换时要切换堆栈,而内层堆栈的指针存放在当前任务的TSS中,所以要设置任务状态段寄存器 TR。

	push	SelectorStack3
	push	TopOfStack3
	push	SelectorCodeRing3
	push	0
	retf	;retf弹出CS和EIP
	      ; Ring0 -> Ring3,历史性转移!将打印数字 '3'。

; ------------------------------------------------------------------------
DispReturn:
	push	eax
	push	ebx
	mov	eax, edi
	mov	bl, 160
	div	bl
	and	eax, 0FFh
	inc	eax
	mov	bl, 160
	mul	bl
	mov	edi, eax
	pop	ebx
	pop	eax

	ret
; DispReturn 结束---------------------------------------------------------

SegCode32Len	equ	$ - LABEL_SEG_CODE32
; END of [SECTION .s32]

; CodeRing3    
[SECTION .ring3]
ALIGN	32
[BITS	32]
LABEL_CODE_RING3:  ;从上面的retf跳转到此,此处为ring3特权级
	mov	ax, SelectorVideo
	mov	gs, ax			; 视频段选择子(目的),改变了GS描述符高速缓存寄存器,下次就从这里取得首地址

	mov	edi, (80 * 14 + 0) * 2	; 屏幕第 14 行, 第 0 列。
	mov	ah, 0Ch			; 0000: 黑底    1100: 红字
	mov	al, '3'
	mov	[gs:edi], ax  ;根据特权级访问原则 CPL小于等于DPL,RPL小于等于DPL,可以访问

	call	SelectorCallGateTest:0	; 测试调用门(有特权级变换),将打印字母 'C'。、
									;已经了TSS做为铺垫
									;CPL小于等于DPL_G,RPL小于等于DPL_G,DPL_B小于等于CPL
	jmp	$ ;最后程序停止不动
SegCodeRing3Len	equ	$ - LABEL_CODE_RING3
; END of [SECTION .ring3]


[SECTION .sdest]; 调用门目标段
[BITS	32]

LABEL_SEG_CODE_DEST: ;由call	SelectorCallGateTest:0进入此段代码 ring3-->ring0
	mov	ax, SelectorVideo
	mov	gs, ax			; 视频段选择子(目的)

	mov	edi, (80 * 12 + 0) * 2	; 屏幕第 12 行, 第 0 列。
	mov	ah, 0Ch			; 0000: 黑底    1100: 红字
	mov	al, 'C'
	mov	[gs:edi], ax

	; Load LDT
	mov	ax, SelectorLDT ;加载到LDTR选择器中,然后LDTR高速缓存就变化成了LDTR的描述符
	lldt	ax

	jmp	SelectorLDTCodeA:0	; 跳入局部任务,将打印字母 'L'。
							;此时CS描述符高速缓存寄存器被置为LABEL_CODE_A的描述符,以后直接从这里取得首地址

	;retf

SegCodeDestLen	equ	$ - LABEL_SEG_CODE_DEST
; END of [SECTION .sdest]


; LDT
[SECTION .ldt]
ALIGN	32
LABEL_LDT:
;                                         段基址       段界限     ,   属性
LABEL_LDT_DESC_CODEA:	Descriptor	       0,     CodeALen - 1,   DA_C + DA_32	; Code, 32 位

LDTLen		equ	$ - LABEL_LDT

; LDT 选择子
SelectorLDTCodeA	equ	LABEL_LDT_DESC_CODEA	- LABEL_LDT + SA_TIL ;TI位为1,指示从局部描述符表LDT中读取描述符
; END of [SECTION .ldt]


; CodeA (LDT, 32 位代码段)
[SECTION .la]
ALIGN	32
[BITS	32]
LABEL_CODE_A:
	mov	ax, SelectorVideo
	mov	gs, ax			; 视频段选择子(目的)

	mov	edi, (80 * 13 + 0) * 2	; 屏幕第 13 行, 第 0 列。
	mov	ah, 0Ch			; 0000: 黑底    1100: 红字
	mov	al, 'L'
	mov	[gs:edi], ax

	; 准备经由16位代码段跳回实模式
	jmp	SelectorCode16:0 ;此时CS描述符高速缓存寄存器被置为LABEL_SEG_CODE16的描述符,以后直接从这里取得首地址
CodeALen	equ	$ - LABEL_CODE_A
; END of [SECTION .la]

; 16 位代码段. 由 32 位代码段跳入, 跳出后到实模式
[SECTION .s16code]
ALIGN	32
[BITS	16]
LABEL_SEG_CODE16:
	; 跳回实模式:
	mov	ax, SelectorNormal
	mov	ds, ax
	mov	es, ax
	mov	fs, ax
	mov	gs, ax
	mov	ss, ax  ;这里所有的步骤都是为了是描述符高速缓存寄存器恢复16代码时候的状态,而CS在jmp	SelectorCode16:0
				;的位置已经修改好了

	mov	eax, cr0
	and	al, 11111110b
	mov	cr0, eax ;退出保护模式

LABEL_GO_BACK_TO_REAL:
	jmp	0:LABEL_REAL_ENTRY	; 段地址会在程序开始处被设置成正确的值,段基地址*16+偏移

Code16Len	equ	$ - LABEL_SEG_CODE16

; END of [SECTION .s16code]






你可能感兴趣的:(TSS,GDT,ldt,任务特权级,调用门)