<自己动手写操作系统>第三章——pmtest3源码解析:使用LDT

摘要:在本节中,我们将添加一个LDT段,在段中打印一个字符L,从而学习如何利用程序的LDT段。程序的执行流程随之也会发生变化,我们将分析代码和分析代码结构。


一、概要分析:

在pmtest2的基础上,我们增加一个段LDT和一个LDT中的一个段codeA。需要注意的是,LDT存在的前提是GDT,因为LDT本身是一个段中,需要由GDT中的信息,得到基地址。另外,这个场景,给出了段选择子,我们如何去索引描述符呢?思考以后,我们知道,我们需要判断但选择子属于GDT还是LDT中的段——这个需要通过选择子的第二位来判断。


我们来理一下程序的执行流程:
初始在实模式下,初始化GDT段描述符,做好进入保护模式的准备工作,经过一个长跳转,进入
保护模式32b代码段,加载相关段选择子,打印相关字符串信息,加载ldtr,进入局部任务codeA段
局部任务段codeA:打印字符,跳转到GDT中的code16段;
code16段:初始化相关寄存器,然后重新跳回实模式(通过jmp0)


二、代码解惑

注意,selector部分是不暂用GDT的存储空间的,所以计算GDT的时候,不包括selector部分。另外,汇编与一些高级语言相比,之所以在程序上难以理解,主要是因为它的结构化不是很强,也没有什么模块的概念,完全是计算的最底层,通过顺序,判断,跳转三条基本的指令来完成相应的功能。这就导致,程序之间的耦合性比较大,调用函数输入和输出不是很明显,所以会比较困难。另外,有很多处理器架构和汇编本身的语法问题,也给理解带来了额外的困难。为了理解代码,我们需要有如下的意识:


1)一个段往往是为了完成特定的功能(存储数据或者执行代码):比如说,初始的实模式下的段,就是为了进入保护模式,那么就需要设置GDT、CR0、关中断、打开A20、跳转等。进入保护模式下32b的代码段之后,需要打印字符串信息和跳转到LDT中的段,所以需要设置相应的段寄存器....等等,只有从宏观上理解了程序的执行流程,才能写出代码。代码段是逻辑的主体,数据段主要完成数据结构的定义等。
2)另外,要注意的是,很多东西都是通过寄存器的设置来实现的:访问GDT之前要设置GDTR,同理有ss、ds、cs、sp、ldtr等。
代码如下:
; ==========================================
; pmtest3.asm
; 编译方法:nasm pmtest3.asm -o pmtest3.com
; ==========================================


%include	"pm.inc"	; 常量, 宏, 以及一些说明


org	0100h
	jmp	LABEL_BEGIN


[SECTION .gdt]
; GDT
;                                         段基址,       段界限     , 属性
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_DATA:	Descriptor	       0,	DataLen - 1, DA_DRW		; Data
LABEL_DESC_STACK:	Descriptor	       0,        TopOfStack, DA_DRWA + DA_32	; Stack, 32 位
LABEL_DESC_LDT:		Descriptor	       0,        LDTLen - 1, DA_LDT		; LDT
LABEL_DESC_VIDEO:	Descriptor	 0B8000h,            0ffffh, DA_DRW		; 显存首地址
; GDT 结束


GdtLen		equ	$ - LABEL_GDT	; GDT长度
GdtPtr		dw	GdtLen - 1	; GDT界限
		dd	0		; GDT基地址


; GDT 选择子
SelectorNormal		equ	LABEL_DESC_NORMAL	- LABEL_GDT
SelectorCode32		equ	LABEL_DESC_CODE32	- LABEL_GDT
SelectorCode16		equ	LABEL_DESC_CODE16	- LABEL_GDT
SelectorData		equ	LABEL_DESC_DATA		- LABEL_GDT
SelectorStack		equ	LABEL_DESC_STACK	- LABEL_GDT
SelectorLDT		equ	LABEL_DESC_LDT		- LABEL_GDT
SelectorVideo		equ	LABEL_DESC_VIDEO	- LABEL_GDT
; 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


; END of [SECTION .gs]




[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
	mov	[SPValueInRealMode], sp


	; 初始化 16 位代码段描述符
	mov	ax, cs
	movzx	eax, ax
	shl	eax, 4
	add	eax, LABEL_SEG_CODE16
	mov	word [LABEL_DESC_CODE16 + 2], ax
	shr	eax, 16
	mov	byte [LABEL_DESC_CODE16 + 4], al
	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, 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


	; 初始化 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


	; 为加载 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]


	; 关中断
	cli


	; 打开地址线A20
	in	al, 92h
	or	al, 00000010b
	out	92h, al


	; 准备切换到保护模式
	mov	eax, cr0
	or	eax, 1
	mov	cr0, eax


	; 真正进入保护模式
	jmp	dword SelectorCode32:0	; 执行这一句会把 SelectorCode32 装入 cs, 并跳转到 Code32Selector:0  处


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


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:
	mov	ax, SelectorData
	mov	ds, ax			; 数据段选择子
	mov	ax, SelectorVideo
	mov	gs, ax			; 视频段选择子


	mov	ax, SelectorStack
	mov	ss, ax			; 堆栈段选择子


	mov	esp, TopOfStack




	; 下面显示一个字符串
	mov	ah, 0Ch			; 0000: 黑底    1100: 红字
	xor	esi, esi
	xor	edi, edi
	mov	esi, OffsetPMMessage	; 源数据偏移
	mov	edi, (80 * 10 + 0) * 2	; 目的数据偏移。屏幕第 10 行, 第 0 列。
	cld
.1:
	lodsb
	test	al, al
	jz	.2
	mov	[gs:edi], ax
	add	edi, 2
	jmp	.1
.2:	; 显示完毕


	call	DispReturn


	; Load LDT
	mov	ax, SelectorLDT
	lldt	ax


	jmp	SelectorLDTCodeA:0	; 跳入局部任务


; ------------------------------------------------------------------------
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]




; 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


	mov	eax, cr0
	and	al, 11111110b
	mov	cr0, eax


LABEL_GO_BACK_TO_REAL:
	jmp	0:LABEL_REAL_ENTRY	; 段地址会在程序开始处被设置成正确的值


Code16Len	equ	$ - LABEL_SEG_CODE16


; END of [SECTION .s16code]




; 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
; 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 * 12 + 0) * 2	; 屏幕第 10 行, 第 0 列。
	mov	ah, 0Ch			; 0000: 黑底    1100: 红字
	mov	al, 'L'
	mov	[gs:edi], ax


	; 准备经由16位代码段跳回实模式
	jmp	SelectorCode16:0
CodeALen	equ	$ - LABEL_CODE_A
; END of [SECTION .la]


你可能感兴趣的:(保护模式,自己动手写操作系统,ldt)