本文介绍实模式与保护模式的基础知识与汇编代码实现。
实模式和保护模式的基础知识
在 IA32 中,CPU 有两种工作模式:实模式和保护模式。在保护模式下,CPU 有更强的寻址能力。
保护模式的起源
最开始的程序寻址是直接的“段:偏移”模式,这样的好处是所见即所得,程序员指定的地址就是物理地址,物理地址对程序员是可见的。但是,由此也带来两个问题:
- 无法支持多任务
- 程序的安全性无法得到保证(用户程序可以改写系统空间或者其他用户的程序内容)。
实模式将整个物理内存看成分段的区域,程序代码和数据位于不同区域,系统程序和用户程序没有区别对待,而且每一个指针都是指向"实在"的物理地址。
这样一来,用户程序的一个指针如果指向了系统程序区域或其他用户程序区域,并改变了值,那么对于这个被修改的系统程序或用户程序,其后果就很可能是灾难性的。
为了克服这种低劣的内存管理方式,处理器厂商开发出保护模式:物理内存地址不能直接被程序访问,程序内部的地址(虚拟地址)要由操作系统转化为物理地址去访问,从而保护进程地址空间,程序对此一无所知。
保护模式和实模式不同的寻址方式
从80386开始,cpu有三种工作方式:实模式,保护模式和虚拟8086模式。只有在刚刚启动的时候是real-mode,等到linux操作系统运行起来以后就运行在保护模式。虚拟8086模式是运行在保护模式中的实模式,为了在32位保护模式下执行纯16位程序。它不是一个真正的CPU模式,还属于保护模式。
两者的区别主要体现在寻址方式上。
在实模式情况下,一个地址有段和偏移两部分组成,物理地址的计算公式为:
Physical Address = Segment * 16 + Offset
对 80286 或更高系列的PC中,即使A20Gate被打开,在实模式下所能够访问的内存最大也只能为10FFEFH,尽管它们的地址总线所能够访问的能力都大大超过这个限制。为了能够访问10FFEFH以上的内存,则必须进入保护模式。
在保护模式中,仍然采用了“段+偏移”的模式。虽然段值仍然由原来的16位cs/ds等寄存器指定,但此时这些寄存器中存放的不再是段基址,而是一个索引:从这个索引,可以找到一个表项,里面存放了段基址等很多属性,这个表项称为段描述符,这个表就称为GDT[1]。
GDT
现在看[SECTION .gdt]
这个段,这里SECTION定义了一个gdt段,SECTION并没有什么特别的语义,仅仅是一种组织代码和存储的方式,这里这样做是为了之后很好的扩展,其中的 Descriptor 是一个数据结构,是一个宏,大小为 8 字节。
![bfbf9b575b9371f9388079b9c32252de.png](https://i-blog.csdnimg.cn/blog_migrate/f0b57314ef68e62c457e552a9bebf72a.jpeg)
Descriptor 代码解析:
- 第一行表示取第二个参数的0-15位(2个字节的大小)作为宏的第一、二个字节
- 第二行表示取第一个参数的0-15位(2个字节的大小)作为宏的第三、四个字节
- 第三行先把第一个参数右移16位(2字节)最低位就变成了之前的第16位,在和0FF求与,就是取第一个参数的16-23位,作为宏的第五个字节。
- 第四行“|”号前半部分,参数2右移8位(1字节)最低为变为之前的第8位,和0F00求与,0F00为1的位置为第8-11位,在原始的第二个参数中就变成了16-19位((8-11)+8)。后半部分第三个参数和0F0FF求与,就是第三个参数的0-7位及12-15位,之后前后部分求或得到宏的第六七字节。
- 第五行第一个参数先右移24位,低位变成原来的第24位,然后与0FF求与,就是第一个参数的24-31位作为第宏的第八个字节。[2]
[SECTION .gdt] 段并列定义了 3 个 Descriptor,GdtLen 表示 GDT 的长度,GdtPtr 是个小的数据结构,前两个字节表示 GDT 界限,后四个字节表示 GDT 基地址。
![75664a27fd904150d261263c0eb9240a.png](https://i-blog.csdnimg.cn/blog_migrate/9c0d19ac7392e1559d0a3ae58f8acbad.png)
这里需要补充说明一下,db、dw、dd 的含义。
- 首先,它们都是汇编伪指令。
- db 定义字节类型变量,一个字节数据占 1 个字节单元,读完一个,偏移量加 1 。
- dw 定义字类型变量,一个字数据占 2 个字节单元,读完一个,偏移量加 2 。
- dd 定义双字类型变量,一个双字数据占 4 个字节单元,读完一个,偏移量加 4 。
然后,还定义了两个 Selector
![0c52c4994d04187f99c6863c516070f9.png](https://i-blog.csdnimg.cn/blog_migrate/a17b20b6f1cb08c07aa2ce6de2d631d6.png)
Intel 8086 是 16 位的 CPU,由 16 位的寄存器、16 位的数据总线,和 20 位的地址总线,以及 1MB 的寻址能力。一个地址由段和偏移两部分组成,物理地址遵循这样的计算公式:
Physical Address = Segment * 16 + Offset
其中,段值和偏移都是 16 位的。
从 80386 开始,Intel 家族的 CPU 进入 32 位时代,所以可以寻址 4GB 的空间。在 32 位的情况下,地址仍然使用“段:偏移量”的形式来表示,但“段”的概念发生了根本性的变化。在实模式下,段值还是可以看作是地址的一部分;而在保护模式下,虽然段值仍然由 16 位的 cs、ds 寄存器表示,但它实际上成了一个索引,指向数据结构中的一个表项。表项中详细定义了段的起始地址、界限、属性等内容。
这个数据结构就是 GDT,而 GDT 中的表项被称为描述符(Descriptor)。
下面示意图表示的是代码段和数据段描述符。第二部分的属性最为复杂。
![1ece70748f461cbc0f36c284794fef61.png](https://i-blog.csdnimg.cn/blog_migrate/339e2af8341b5de476e6a1e5ddc6cf7b.jpeg)
本例的 GDT 共有 3 个描述符,分别是 DESC_DUMMY、DESC_CODE32 和 DESC_VIDEO。其中,DESC_VIDEO 的段基址是 0B8000h,指向的是显存。
32 位的代码分析
![68773b86bb6450cdc86aada946fb0d3c.png](https://i-blog.csdnimg.cn/blog_migrate/cf2ce83a6500028df6c0f6e97e757410.jpeg)
这里的[BITS 32]
指定了这一段的代码是32位代码段,即保护模式段:
![61d0212025f8ab77d2ed665d6ebc02bc.png](https://i-blog.csdnimg.cn/blog_migrate/7679e22aba8cb9758fe89afb31d934ac.png)
这两句将段寄存器gs的值变成了 SelectorVideo,由上面的解释可以看到 SelectorVideo 就是LABEL_DESC_VIDEO 相对于 GDT 的偏移,这里 GDT 的基址为 0,基本就可以认为gs现在就是对应显存的描述符 LABEL_DESC_VIDEO。
![8f071422b6680fdecb30147994ea275b.png](https://i-blog.csdnimg.cn/blog_migrate/aa946400f1cfbdacd9f771360a3aca58.png)
GTD 中的每个描述符定义一个段。那么 cs、ds 等段寄存器是如何与具体的段联系起来的呢?看上去段寄存器 gs 的值变成了 SelectorVideo,而在上文中 SelectorVideo 是这样定义的:
SelectorVideo equ LABEL_DESC _VIDEO - LABEL_GDT
看上去,SelectorVideo 是 DESC_VIDEO 这个描述符相对于 GDT 基址的偏移。而实际上,SectorVideo 有一个专门的名称:选择子(Selector)。段选择子就是一个数字,一共有16位。结构如下:
![78fa2e57786ec4777196e8a0398bf0bd.png](https://i-blog.csdnimg.cn/blog_migrate/279fa7c73e2b7dc5d33b7fb725b0a355.png)
- INDEX:在GDT数组或LDT数组的索引号
- TI:Table Indicator,这个值为0表示查找GDT,1则查找LDT
- RPL:请求特权级。以什么样的权限去访问段[3]。
倘若 TI 和 RPL 都为 0,那么选择子就是对应描述符相对于 GDT 基址的偏移。
16 位实模式代码分析——从实模式到保护模式的转换
![c975f5ed6fc07d9d0604b8ce18e28c3d.png](https://i-blog.csdnimg.cn/blog_migrate/e599e2485202fbd6870e67369c7bd8f0.jpeg)
首先是初始化相关段寄存器和对堆栈初始化,就是对 ss 和 sp 赋值。其中,SS 存放栈的段地址;SP(stack pointer) 存放栈的偏移地址。
![786f9c2203be3c10cc3512d4d989f6c1.png](https://i-blog.csdnimg.cn/blog_migrate/d6cd7ecd46f50fb9fad78fac6e6ff7b1.png)
从实模式到保护模式的转换由 5 个步骤组成:第一步,准备 GDT,初始化 32 位代码段描述符;第二步,用 lgdt 加载 gdtr;第三步,打开地址线 A20;第四步,置 cr0 的 PE 位;第五步,跳转,进入保护模式。
第一步中的 GDTR 是全局描述符表格寄存器;第二步的 lgdt 是一个指令,用于加载 gdtr。
eax就是ax的32位版本了。在初始化 32 位代码段描述符部分,首先将 DESC_CODE_32 的物理地址(即 [SECTION .s32] 段的物理地址)赋给 eax,然后把它分为三个部分赋给描述符 DESC_CODE_32 中的相应位置。
![ac8f52f30010991f53c1cca76fe49836.png](https://i-blog.csdnimg.cn/blog_migrate/3d057ca84183162674c36b411d53d2aa.png)
这里的 [LABEL_DESC_CODE32 + 2] 相当于在 LABEL_DESC_CODE32 描述符之后的两个字节的位置,就是 LABEL_DESC_CODE 的三四字节,就是上图中段基址的位置,也是描述符代码中dw %1 & 0FFFFh这一行,shr eax, 16 表示 eax 右移16位,之后同理,总之就是赋值了LABEL_DESC_CODE32 描述符中段基址的部分。
![49fd309a0559ad8b2f23c250e6295f25.png](https://i-blog.csdnimg.cn/blog_migrate/076a0f5f105605f083ab6f0223455c7a.png)
第三步的打开 A20 地址线,正如在前面的知识点中讲到的,IBM 为了保持向上的兼容,使用 8042 键盘控制器控制第 20 个地址位。默认情况下,该地址线是关闭的,即第 20 个地址位总是 0。
A20地址线并不是打开保护模式的关键,只是在保护模式下,不打开A20地址线,你将无法访问到所有的内存。
这里的 in 和 out 语义分别是:
- in DES, SRC: 从 SRC 端口读取 1 字节数据到 DES
- OUT DES, SRC: 将 SRC 的值写入 DES 端口
把 cr0 的第 0 位置为 1,我们就进入了保护模式。cr0 的内容结构如下图所示:
![b361df085560adec1c9bd080644dfe5a.png](https://i-blog.csdnimg.cn/blog_migrate/a053bf6c09f37fe1ac02096fcd87b9e7.png)
CR0 是控制寄存器,其中包含了6个预定义标志。控制寄存器是一些特殊的寄存器,它们可以控制CPU的一些重要特性。[4]
- 第 0 位是保护允许位 PE (Protected Enable),用于启动保护模式,如果PE位置 1,则保护模式启动,如果 PE=0,则在实模式下运行。
- 第 1 位是监控协处理位 MP(Moniter coprocessor),它与第3位一起决定:当 TS=1 时操作码 WAIT 是否产生一个“协处理器不能使用”的出错信号。
- CR0的第2位是模拟协处理器位 EM (Emulate coprocessor),如果EM=1,则不能使用协处理器,如果EM=0,则允许使用协处理器。
- 第 3 位是任务转换位 (Task Switch),当一个任务转换完成之后,自动将它置 1。随着TS=1,就不能使用协处理器。
- 第4位是微处理器的扩展类型位 ET(Processor Extension Type),其内保存着处理器扩展类型的信息,如果 ET=0,则标识系统使用的是287协处理器,如果 ET=1,则表示系统使用的是 387 浮点协处理器。
- CR0的第 31 位是分页允许位(Paging Enable),它表示芯片上的分页部件是否允许工作。
而此时 cs 的值仍然是实模式下的值,因此我们需要把代码段的选择子装入 cs。运行跳转
jmp dword SelectorCode32:0
根据寻址机制我们知道,这个跳转的目标是描述符 DESC_CODE_32 对应的段的首地址,即标号 LABEL_SEG_CODE32 的地方。
参考
- ^1 https://blog.csdn.net/trochiluses/article/details/8954527
- ^http://gloriaswaterpool.com/2018/12/05/%E5%AD%A6%E4%B9%A0%E4%B8%80%E4%B8%AA%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F-3/
- ^2 https://blog.csdn.net/q1007729991/article/details/52538080
- ^3 https://blog.csdn.net/whatday/article/details/24851197