第三章pmtest2源码解析 实模式到保护模式再到实模式

<span style="font-size:14px;">摘要:本节,通过代码解析,帮你解决如下问题:保护模式和实模式下面的偏移量有什么不同?保护膜是和实模式下段基地址是一个意思吗?长跳转指令jmp 0:entry为什么能够在一个代码段中更改另一个代码段的代码?</span>
<span style="font-size:14px;">
</span><span style="font-size:18px;color:#ff0000;">一、总体思路剖析:</span>
<span style="font-size:14px;">pmtest2,其实实现的是从实模式到保护模式,然后从保护模式回到实模式,最后回归到dos。其中,一开始就进入了实模式,然后在实模式下初始化段描述符,处理GDT等,进入保护模式,在保护模式下完成了一些显示字符串和拷贝读取字符串的功能;最后通过一个normal段,回到实模式,然后通过中断,回到dos程序中。这个过程具体代码如下:</span>

; ==========================================
; pmtest2.asm
; 编译方法:nasm pmtest2.asm -o pmtest2.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_TEST:	Descriptor	0500000h,            0ffffh, DA_DRW
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
SelectorTest		equ	LABEL_DESC_TEST		- 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

	; 为加载 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, SelectorTest
	mov	es, 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:	; 显示完毕
	xchg	bx,bx
	call	DispReturn

	call	TestRead
	call	TestWrite
	call	TestRead

	; 到此停止
	jmp	SelectorCode16:0

; ------------------------------------------------------------------------
TestRead:
	xor	esi, esi
	mov	ecx, 8
.loop:
	mov	al, [es:esi]
	call	DispAL
	inc	esi
	loop	.loop

	call	DispReturn

	ret
; TestRead 结束-----------------------------------------------------------


; ------------------------------------------------------------------------
TestWrite:
	push	esi
	push	edi
	xor	esi, esi
	xor	edi, edi
	mov	esi, OffsetStrTest	; 源数据偏移
	cld
.1:
	lodsb
	test	al, al
	jz	.2
	mov	[es:edi], al
	inc	edi
	jmp	.1
.2:

	pop	edi
	pop	esi

	ret
; TestWrite 结束----------------------------------------------------------


; ------------------------------------------------------------------------
; 显示 AL 中的数字
; 默认地:
;	数字已经存在 AL 中
;	edi 始终指向要显示的下一个字符的位置
; 被改变的寄存器:
;	ax, edi
; ------------------------------------------------------------------------
DispAL:
	push	ecx
	push	edx

	mov	ah, 0Ch			; 0000: 黑底    1100: 红字
	mov	dl, al
	shr	al, 4
	mov	ecx, 2
.begin:
	and	al, 01111b
	cmp	al, 9
	ja	.1
	add	al, '0'
	jmp	.2
.1:
	sub	al, 0Ah
	add	al, 'A'
.2:
	mov	[gs:edi], ax
	add	edi, 2

	mov	al, dl
	loop	.begin
	add	edi, 2

	pop	edx
	pop	ecx

	ret
; DispAL 结束-------------------------------------------------------------


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

<h1><span style="font-size:18px;color:#ff0000;">二、代码解惑</span></h1>
<span style="font-size:14px;">
<strong><span lang="en-US">1.align 32</span>是什么意思?为什么要写<span lang="en-US">align 32</span>?</strong>
答:<span lang="en-US">align</span>是一个让数据对齐的宏。通常<span lang="en-US">align</span>的对象是<span lang="en-US">1</span>、<span lang="en-US">4</span>、<span lang="en-US">8</span>等。这里的<span lang="en-US">align 32</span>是没有意义的,本来就是只有<span lang="en-US">32b</span>的地址总线宽度,怎么还<span lang="en-US">32</span>对齐?不可能。

<strong><span lang="en-US">2.</span>既然我们说<span lang="en-US">PMMessage</span>是表示段内<span lang="en-US">offset</span>,那么我们为什么还要定义一个变量<span lang="en-US">OffsetPMMessage</span>呢?</strong>
<span lang="en-US">$</span>、<span lang="en-US">$$</span>、<span lang="en-US">offsetpmmessage</span>、<span lang="en-US">pmmessage</span>的地址分别是什么?
<span lang="en-US">$:</span>当前行被汇编之后的地址,是实际的线性地址
<span lang="en-US">$$:</span>一个<span lang="en-US">section</span>的开始地方被汇编以后的地址,也是实际的线性地址
<span lang="en-US">pmmessage:</span>偏移地址(相对段的首地址)
既然<span lang="en-US">offset</span>是偏移地址,为什么不直接用<span lang="en-US">offset</span>呢?
要解答这些疑惑,我们首先去寻找段基址:你有没有注意到一个问题,在“初始化段描述符”的部分,我们用来初始化段基地址首地址的寄存器,都是<span lang="en-US">cs</span>和<span lang="en-US">ds</span>(实际上二者的数值相等);从某种角度上说,他们处在“同一个段”中。不过分属于不同的<span lang="en-US">offset</span>对应的部分,我们通过段描述符,实现了不同区域的不同权限管理。因为现在程序很小,我们可以在<span lang="en-US">20b</span>的<span lang="en-US">offset</span>之内包括所有的代码和数据,所以这样做是没有任何问题的。

我们再看看保护模式下发生的变化:本来在实模式下,它们属于同一个段,但是最后段基地显然在初始化段描述符的时候悄然发生了变化(都采用了<span lang="en-US">base*16+offset</span>,而<span lang="en-US">offset</span>是不同的)。好了,再来看看“<span lang="en-US">pmmessage-$$”</span>的真实含义,<span lang="en-US">pmmessage</span>和<span lang="en-US">$$</span>都表示实模式下相对与段基地址的<span lang="en-US">offset</span>;但后来随着段基地址的漂移,<span lang="en-US">$$</span>变成了首地址,所以<span lang="en-US">pmmessage</span>对应于保护模式下的偏移自然也就发生了变化,需要减去<span lang="en-US">$$</span>对应的地址才行。

大部分时候,我们在编程的时候,只需要关注<span lang="en-US">offset</span>就可以了。

<strong><span lang="en-US">3.section</span>和段之间有什么区别和联系?程序代码段执行的第一句,<span lang="en-US">mov ax</span>,<span lang="en-US">cs</span>对应的<span lang="en-US">offset</span>会是<span lang="en-US">0</span>吗?
</strong><span lang="en-US">section</span>和段之间没有必然的联系,一般我们习惯将一个<span lang="en-US">section</span>放在一个段里面,不过这是用户习惯,不是语法要求——我们可以把两个<span lang="en-US">section</span>放在段里面。<span lang="en-US">mov ax</span>,<span lang="en-US">cs</span>在这里,实模式下<span lang="en-US">offset</span>一般不等于<span lang="en-US">0</span>,保护模式下,这一句的偏移一般是<span lang="en-US">0.</span>

<strong><span lang="en-US">4.</span>这里,我们需要根据打印字符串部分总结一下字符串操作的原理、技巧?汇编中判断和循环语句都有哪些?</strong>
参见汇编语言语法简要总结。

<strong><span lang="en-US">5.</span>换行的原理是什么?</strong>
实际上就是靠操作<span lang="en-US">edi</span>来实现,<span lang="en-US">edi=[edi/160]+1</span>

<strong><span lang="en-US">6.</span>保护模式下长跳转指令的原理?<span lang="en-US">jmp 0</span>:<span lang="en-US">xx</span>是如何跳转的。</strong>
从指令结构上来讲,书上的解释已经很明确了——但是<span lang="en-US">jmp 0</span>:<span lang="en-US">xx</span>中的<span lang="en-US">0</span>是如何被改变的呢?因为<span lang="en-US">jmp</span>指令的机器码在编译的时候已经产生了,但是运行之后,它的机器码被改掉了。这样看起来解释得通,但是再仔细想想,是不是有什么地方不对劲?——代码段的内容怎么会可以更改呢?仔细看看代码,又发现了猫腻——原来对代码的改动发生在实模式,这时分段机制还没对代码起到保护作用。

<strong><span lang="en-US">7.PMMessage</span>后面定义的<span lang="en-US">dd</span>,但是内容不止<span lang="en-US">4b</span>,该如何处理?</strong>
<span lang="en-US">dd</span>还是<span lang="en-US">db</span>,表示的是后面的一个单位,而不是所有的内容。
例如<span lang="en-US">dd 3</span>,<span lang="en-US">2</span>,<span lang="en-US">4</span>,<span lang="en-US">6</span>,<span lang="en-US">5</span>:这样定义的就是<span lang="en-US">20</span>个字节。

<strong><span lang="en-US">8.movzx</span>指令</strong>
<span lang="en-US">mov eax</span>,<span lang="en-US">bx</span>是非法的,所以要显示的高位补<span lang="en-US">0</span>

<strong><span lang="en-US">9.</span>带有<span lang="en-US">.</span>的<span lang="en-US">loop</span>和一般的<span lang="en-US">label</span>有什么不同 </strong><span lang="en-US">	</span>
这是本地<span lang="en-US">label</span>的意思:
<span lang="en-US">NASM</span>对于那些以一个句点开始的符号会作特殊处理<span lang="en-US">,</span>一个以单个句点开始的 <span lang="en-US">
</span>
<span lang="en-US">Label</span>会被处理成本地<span lang="en-US">label, </span>这意味着它会跟前面一个非本地<span lang="en-US">label</span>相关联<span lang="en-US">.
</span>
比如<span lang="en-US">:
</span>
<span lang="en-US">
</span>
      <span lang="en-US">label1  ; some code 
</span>
     <span lang="en-US">
</span>
      <span lang="en-US">.loop
</span>
              <span lang="en-US">; some more code 
</span>
     <span lang="en-US">
</span>
              <span lang="en-US">jne     .loop
</span>
              <span lang="en-US">ret  
</span>
     <span lang="en-US">
</span>
      <span lang="en-US">label2  ; some code 
</span>
     <span lang="en-US">
</span>
      <span lang="en-US">.loop
</span>
              <span lang="en-US">; some more code 
</span>
     <span lang="en-US">
</span>
              <span lang="en-US">jne     .loop
</span>
              <span lang="en-US">ret  
</span>
<span lang="en-US">
</span>
上面的代码片断中<span lang="en-US">,</span>每一个<span lang="en-US">'JNE'</span>指令跳至离它较近的前面的一行上<span lang="en-US">,</span>因为<span lang="en-US">'.loop'
</span>
的两个定义通过与它们前面的非本地<span lang="en-US">Label</span>相关联而被分离开来了。 <span lang="en-US">
</span>
<strong><span lang="en-US">10.32b</span>数据段和<span lang="en-US">16b</span>数据段有什么区别?<span lang="en-US">32b</span>的堆栈段呢</strong>
对于stack,位数不同将导致压栈和出战的位数不同;对于数据段,没有什么区别;对于代码段,将决定是ecs还是cs等信息;所以,在32b的物理机器上,你可以都定义成16b的段;但是你不能在16b的机器上,定义32b的段

<strong><span lang="en-US">11.</span>保护模式和实模式段地址有什么区别?</strong>
保护模式下<span lang="en-US">32b</span>,不用进行偏移就直接和<span lang="en-US">offset</span>相加;实模式下<span lang="en-US">16b</span>,需要左移四位然后在<span lang="en-US">+offset</span>。

</span><h1><a target=_blank name="t2"></a><span style="font-size:18px;">三、调试情况:</span></h1><span style="font-size:14px;">
打印<span lang="en-US">read</span>和<span lang="en-US">write</span>位置和内容都出现异常,无法回到<span lang="en-US">dos</span>程序之下。</span>
<span style="font-size:14px;">
</span>
<span style="font-size:14px;">解决方法:调整test段的地址,使得它的地址范围降低,然后像其他段描述符一样进行初始化。</span>
<span style="font-size:14px;">遗留问题:没有解决test段在原文情况下不可写的情况,最后证明,这是一个随机会出现问题的地方。</span>



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值