操作系统实践之第二章(特权级变换*实战篇)

纸上得来终觉浅,绝知此事还是得捣鼓捣鼓才行啊。理论与实践还是要两手抓两手都要硬嘛。

从理论上来说,在ret指令执行前,堆栈中应该已经准备好了目标代码段的CS、EIP,以及SS和ESP,另外还可能有参数。需要注意的是,这些既可以使处理器压入栈的,也可以使由我们自己压入栈的。ret指令执行前的堆栈可能如下图示:


这样,执行ret后就可以转移到低特权级的代码中 了。我们在前面写过的代码中添加可以添加一个ring3的代码段和一个ring3的堆栈段,如下:

LABEL_DESC_CODE_RING3: Descriptor 0,SegCodeRing3Len-1, DA_C+DA_32+DA_DPL3
……
LABEL_DESC_STACK3:     Descriptor 0,      TopOfStack3, DA_DRWA+DA_32+DA_DPL3
……
LABEL_DESC_VIDEO:      Descriptor 0B8000h,     0ffffh, DA_DRW+DA_DPL3
……
SelectorCodeRing3	equ	LABEL_DESC_CODE_RING3	- LABEL_GDT + SA_RPL3
……
SelectorStack3		equ	LABEL_DESC_STACK3	- LABEL_GDT + SA_RPL3
……
; 堆栈段ring3
[SECTION .s3]
ALIGN	32
[BITS	32]
LABEL_STACK3:
	times 512 db 0
TopOfStack3	equ	$ - LABEL_STACK3 - 1
; END of [SECTION .s3]
……
; CodeRing3
[SECTION .ring3]
ALIGN	32
[BITS	32]
LABEL_CODE_RING3:
	mov	ax, SelectorVideo
	mov	gs, ax

	mov	edi, (80 * 14 + 0) * 2
	mov	ah, 0Ch
	mov	al, '3'
	mov	[gs:edi], ax

	jmp	$
SegCodeRing3Len	equ	$ - LABEL_CODE_RING3
; END of [SECTION .ring3]

这段ring3的代码一目了然,只是由于这段代码运行在ring3,而在其中要写显存而访问到VIDEO段,为了不会产生错误,我们把VIDEO段的DPL修改为了3。

另外,新段对应的描述符的属性加上了DA_DPL3,让它的DPL变成了3,相应的选择子的SA_RPL3将RPL也置为了3。初始化描述符的代码与前面相似,就不再重复了。这样,代码段和堆栈段就准备好了,下一步就是为retf指令准备SS、ESP、CS、EIP这些寄存器的目标值了,具体操作如下:

push	SelectorStack3
push	TopOfStack3
push	SelectorCodeRing3
push	0
retf	

至此需要修改的就改完了,我们将程序编译为test.com然后拷贝到pm.img中,可以运行程序看到如下结果(书本完整源码pmtest5a.asm):

看到最后红色的3就表明我们已经由ring0跳转到ring3了!历史性的特权级间跳转成功!

进入ring3之后就可以体验一下调用门了,先将调用门的描述符和选择子以及[SECTION  .ring3]的代码稍作修改。

; 门                               目标选择子,偏移,DCount, 属性
LABEL_CALL_GATE_TEST: Gate SelectorCodeDest,   0,     0, DA_386CGate+DA_DPL3
……
SelectorCallGateTest	equ	LABEL_CALL_GATE_TEST	- LABEL_GDT + SA_RPL3
……
; CodeRing3
[SECTION .ring3]
ALIGN	32
[BITS	32]
LABEL_CODE_RING3:
	mov	ax, SelectorVideo
	mov	gs, ax
	mov	edi, (80 * 14 + 0) * 2
	mov	ah, 0Ch
	mov	al, '3'
	mov	[gs:edi], ax

	call	SelectorCallGateTest:0

	jmp	$
SegCodeRing3Len	equ	$ - LABEL_CODE_RING3
; END of [SECTION .ring3]

在进入死循环前,我们使用了调用门指令,修改相应的描述符和选择子是为了满足CPL和RPL都小于等于调用门DPL的条件。但是现在还不能运行,因为从低特权级到高特权级的转移需要TSS的帮助,下面我们就来准备一个TSS。

LABEL_DESC_TSS:        Descriptor 0,          TSSLen-1, DA_386TSS
……
SelectorTSS		equ	LABEL_DESC_TSS		- LABEL_GDT
……
; TSS
[SECTION .tss]
ALIGN	32
[BITS	32]
LABEL_TSS:
		DD	0			; Back
		DD	TopOfStack		; 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位图基址
		DB	0ffh			; I/O位图结束标志
TSSLen		equ	$ - LABEL_TSS

上面我们只初始化了0级堆栈,添加初始化TSS描述符的代码后,TSS就算是准备好了,我们需要在特权级转换之前加载它。

call	DispReturn

mov	ax, SelectorTSS
ltr	ax

push	SelectorStack3
push	TopOfStack3
push	SelectorCodeRing3
push	0
retf

到目前为止,我们已经成功地实现了由高特权级到低特权级或者由低特权级到高特权级的双向转换,并且熟悉了调用门这种经典门描述符的使用。

为了可以让程序顺利地返回实模式,我们将调用局部任务的代码加入到调用门的目标代码(SECTION  .sdest)。最后,程序将由这里进入局部任务,然后原路返回实模式。

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

LABEL_SEG_CODE_DEST:
	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
	lldt	ax

	jmp	SelectorLDTCodeA:0	; 跳入局部任务,将打印字母 'L'。

	;retf

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

最后的执行结果如下图(完整代码pmtest5.asm):


总结一下过程:一个是从高特权级到低特权级的跳转如何确定是跳转到哪一级呢?这个是由当时对应堆栈中的CS、EIP等决定的。那从低特权级到高特权级又是如何确定的呢?根据目标代码段的DPL(新的CPL)从TSS中选择应该切换至哪个SS和ESP。







  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值