32位保护模式汇编语言[1]:实模式与保护模式的故事

来源:互联网 发布:火爆网络ann 编辑:程序博客网 时间:2024/04/26 04:22
此文档在WORD软件编辑完成,发布在CSDN网站上会有一定差异。

漫游在机器指令的海洋,徘徊于实模式与保护模式之间,出入之间,自由无限。

详细代码在我的百度网盘
在文章中提到的生成段描述符例程:http://pan.baidu.com/s/1mgtDjok
本文原始WORD版本、插图、源代码:http://pan.baidu.com/s/1mgLYMxi

若发现问题,请在此文档结尾直接回复或是通过电子邮箱<mighten.dai@gmail.com>联系我.


说明:此文档是我的实验感悟第二版,相比第一版做了优化。

1. 去掉了生成段描述符生成的宏,在MBR时期,用测试好代码手工写,进入PM后,调用例程。

2. 增加了运行前期清屏操作

3. 删掉了字符输出的特殊效果

4. 删减了废话字符串,使得使MBR指令缩减


背景:进跟随着处理器更新换代,Intel于1985年设计研发出80386,如今我们所使用的操作系统基本都兼容于Intel所设计的80386。

32位比过去的16位用起来更爽

n 数据总线扩大到32位,使得数据传输速度加快。

n 可寻址内存空间大大扩大,从8086的1MB到80386的4GB

n 兼容性:能用“实模式”继续运行原来的8086/8088上的“老古董”

n 采用多种复杂技术使得指令执行速度变快

80386所提出的32位保护模式核心主要围绕:

n 不同任务之间的保护

n 同一任务之内的保护

 

通过以上使得多任务环境稳定高效。(为了兼容前代CPU 8086/8088, 80368 CPU刚启动时默认处于实模式,需要自己切换。)更多背景内容以后慢慢引进。



第一部分 我们首先看一下 实模式 与 保护模式 切换大体的问题。

CPU 加电启动、检测、初始化后,读取外部存储设备的以结尾低字节为0x55且高字节为0xaa的512字节的数据到内存0x00007c00处,作为MBR (Master Boot Record),使得操作系统得以运行。

 

本次实验平台

1. 操作系统:  Windows 7 Ultimate Service Pack 1

2. CPU: Intel Pentium(R) Dual-Core CPU

3. 工具Bochs x86 Emulator


大体步骤:(运行效果如上)

1.建立全局描述符表;

2.加载全局描述符表;

3.屏蔽中断;

4.打开A20地址线;

5.CR0寄存器PE位置1,打开保护模式;

6.清空指令预取队列,正式进入保护模式。



第二部分 详细解释

全局描述符表(GDT, Global Descriptor Table)的有关资料:

GDT每个表项长度是8字节。主要是对各种段的保护信息的描述。

比如

对数据段写东西时候超过数据段边界了要阻止它;

对只读段写内容时候CPU要阻止它。

 

但是这些零散的保护规章制度怎么才能有效工作呢,需要有一个东西统一存放这些信息,就放入GDT内部(实际上还可以放入别的地方,但暂时忽略掉),放入了GDT内部CPU怎么知道哪里是GDT呢,这里有一个GDTR寄存器指向GDT,指向的方法是维护一个数据结构,存放GDT基地址,GDT界限(= 长度 - 1)

lgdt   [GDT_ptr]

相信大家就明白这句话是干什么了的。


段描述符(Descriptor)

既然是加载到GDTR(Global Descriptor Table Register),自然就想知道加载了什么。查阅Intel 64 and IA-32 Architectures Software Developer’s Manual Volume 3 System Programming 我们得到了如下结构

 

你所见的所有奇葩的格式都是为了兼容80286这个“败家子”16位保护模式的无奈之举。

你足够熟练的话,可以自行对着表格拿演算纸得出64位段描述符,像我一样有些生疏的话,可以在我的网盘里面下载一个我曾经写的生成描述符的例程及其demo

 

目前为止,32位段基址,20位段界限,还有奇葩的格式,这些是我们所需的。

TYPE字段是干嘛的呢?

先别急,回忆一下我们的8086方式(实模式)下的汇编,有哪几个段?

代码段,数据段,堆栈段,附加段

 

实际上堆栈段、附加段也属于数据段,这样,在保护模式下CPU就只认两种段:代码段、数据段

现在没必要掌握所有的,只需记住分类即可。

 

现在准备了这些,你可能会有一个疑问,为什么第一个段描述符为全零?

这是处理机的要求。


A20地址线问题

我们都知道8086地址线20根,编码为A0, A1, A2, ..., A19,当地址是????:FFFF时候再增加时候将产生进位,而更高位是没有的,所以增加后的地址是????:0000,这样就产生了“回绕”的特点。

但是有个问题:80286地址线增加到24根, 80386增加到32根,将进位了,不会产生“回绕”,但实际上当时CPU还没有完全换成32位的,很多程序仍然依靠8086的这个“回绕”的特性在很多计算机上跑着。

为了不失去市场,一种解决方案由此出炉:

在第21根地址线(即A20)上装一个0x92端口控制的电路,强制输出低电平,也就是0,这样以后,当产生地址进位时候,由于A20上强制为零,进位丢失,顺理成章的回绕到????:0000上来,完成了对8086程序的兼容。而下面的代码作用是打开A20门,设置端口0x92标志位,不要干预A20地址线信号,这样就可以访问到超过1MB的存储空间了。

; Configure A20 to enable whole 4 GB  Memory space.

in   al0x92

or   al0000_0010b

out  0x92al


进入保护模式

在CR0(Control Register 0)里面有一个PE(Protection Enable),置位即启用保护模式。

mov   eaxcr0

or    eax1

mov   cr0eax

到此为止,CPU的PM机制已生效。



使用jmp dword: 清空流水线、装载段选择子(Selector)

PE置位后,CPU已经按照32位开始取指令、译码,但是代码段选择子寄存器所指向的代码段无效且CPU流水线中存在无效结果,继续执行会发生错误,需要jmp dword来处理。于是就是所见的

; Now into 32-bit Protection.

;Load CS with  Code_Selector

; EIP with ___32_bit_entry

jmp  dwordCode_Selector:0

 

小插曲:一些编译器不支持类似的指令,所以无奈的编码就由此而生了:

db          0xEA ; Operand Code: jmp

dd             ; Offset Address

dw Code_Selector ; 32-bit Code Segment Selector

 

半路出来的段选择子(Selector)

段选择子其实就是一种说明一个段描述符在全局描述符表(或其他表, 目前我们只认为是从全局描述符表的)内的编号。

从  Intel 64 And IA-32 Architectures Software Developers Manual  Volume 3a: System Programming中我们可以了解到以下信息:

Index(Bits 3 through 15) — Selects one of 8192 descriptors in the GDT or LDT. The processor multiplies the index value by 8 (the number of bytes in a segment descriptor) and adds the result to the base address of the GDT or LDT (from the GDTR or LDTR register, respectively).

索引值 (位3 到 位15) ---从GDT或LDT内8192个选择一个,处理机将用该值乘以8(段描述符的字节大小),然后把得到的结果加到GDT/LDT的基址(从GDTR/LDTR获取)。

 

小插曲:每个段描述符长度为8字节,体现在二进制位上是3个整二进制位,所以第三个段描述符首地址减去第零个段描述符首地址即为第三个段的段选择子。


保护模式下的寻址示意

如图

以代码为例

mov  bxword [ds:esi]

实际上在我的Bochs Emulator对这条语句的解释是这样的:

mov  bxword ptr ds:[esi]

这是两种语法,不用去理它。

接着简述保护模式下寻址方式:(源代码中相关部分优化掉了,详细的请读第一版该文档)


从DS选择子(Selector)取出索引值,进入GDT,找到对应的段描述符,取出段基址,加上偏移地址esi,读出一个两个字节到bx寄存器。

 

很简单吧?从上面可以看出,段描述符里面有段基址,而段基址是从GDT取到的。

 

注意上图内右上角,我们需要注意Offset(Effective Address)  即有效的偏移地址。在逻辑地址中,偏移地址在0到段界限上就是有效偏移。原文:For expand-up segments, the offset in a logical address can range from 0 to the segment limit. 

 

若超界,之前我没有注意到对段基址重新设置以后,引用的段内偏移要减去段基址,结果访问越界,你才怎么了,CPU二话不说,立即重启,后来才知道,访问越界导致引起通用保护异常(#GP, General-Protection Exception),立即死机重启。



后记

进入了32位保护模式后怎么切换回16位的实模式,我做了多次试验,直接从32位代码跳到16位是要被CPU阻止的,原因暂时明白,但解决方案已经找到:

 

32位保护模式代码段工作完成后,跳到16位保护模式代码段作中转站进行如下:

清理PE位,跳到16位实模式代码段关闭A20.


0 0
原创粉丝点击