[TOC]
CPU的操作模式
保护模式
所有IA-32位处理器的本位(native)模式,具有强大的虚拟内存支持和完善的任务保护机制,为现代操作系统提供了良好的多任务运行环境。
实地址模式
简称实模式,即模拟8086处理器的工作模式。实模式提供了一种简单的单任务环境,可以直接访问物理内存和I/O空间,由于操作系统和应用软件运行于同一内存空间和同一优先级上,因此具有极大的安全隐患。DOS运行于实模式下。
虚拟8086模式
保护模式下用于执行8086任务(程序)的准模式。通过该模式,可以吧8086程序当作保护模式的一项任务来执行。从保护模式切换到实模式来运行8086程序需要极大的性能开销,难以实现。虚拟8086以类似实模式的方式工作,但是由于安全性的原因,运行在8086模式下的8086任务在I/O访问的时候会受到一些限制。
系统管理模式
供系统固件执行电源管理、安全检查或与平台相关的特殊任务。当CPU的系统管理中断管脚(SMI#)被激活时,处理器会将当前正在执行的任务上下文保存,然后切换到另一个单独的地址空间中执行专门的SMM例程,SMM例程通过RSM指令使处理器退出SMM模式并恢复到响应系统管理中断前的状态。
IA-32e模式
支持Intel 64的64位工作模式,曾被称为EM64T,是IA-32 CPU支持64位的一种拓展技术,具有对现有32程序的良好兼容性。IA-32e模式由64位模式和兼容模式构成,64位模式提供了64位的线性寻址能力,兼容模式用于执行现有32位应用程序。对于运行在IA-32e模式下的64位操作系统,系统内核和内核态驱动程序一定是64位代码,工作在64位模式下,应用程序既可以是32位也可与是64位。
模式切换
处理器在商店开始运行时或复位后处于实模式,CR0寄存器控制PE标志,用来控制处理器处于实地址模式还是保护模式。标志寄存器(EFFLAGS)的VM标志用来控制处理器是运行在虚拟8086模式还是保护模式,EFER寄存器的LME用来启用IA-32e模式。
寄存器
通用数据寄存器
通用数据寄存器又称CPR,共有8个,分别为EAX、EBX、ECX、EDX、EBP、ESP、EDI、ESI。每个寄存器的最大宽度为32位,其中EAX、EBX、ECX、EDX可以按字节(AL、AH等)或字(AX、BX等)访问。
尽管GPR寄存器大多数时候是通用的,但是在某些情况下,它们会有特定的隐含用法。常见的隐含用法如下表
寄存器名 | 隐含作用 |
---|---|
EAX | 累加器(Accumulator),用累加器进行的操作可能需要更少时间。累加器可用于乘、除、输入/输出等操作,它们的使用频率很高 |
EBX | 基地址寄存器(Base Register)。它可作为存储器指针来使用 |
ECX | 计数寄存器(Count Register)。在循环和字符串操作时,要用它来控制循环次数;在位操作中,当移多位时,要用CL来指明移位的位数; |
EDX | 数据寄存器(Data Register)。在进行乘、除运算时,它可作为默认的操作数参与运算,也可用于存放I/O的端口地址。 |
EBP | 基址指针寄存器(Base Pointer),存放堆栈基址 |
ESP | 堆栈指针寄存器(Stack Pointer),存放栈顶地址 |
ESI | 源变址寄存器 (Source Index) |
EDI | 目的变址寄存器(Destination Index |
标志寄存器
IA-32 CPU有一个32位的标志寄存器,名为EFLAGS。标志寄存器用于切换CPU的工作参数或者显示CPU的状态。EFLAGS寄存器包含3类标志:
- 报告算数指令结果的状态标志(CF、PF、AF、ZF、SF、OF)
- 控制字符串指令操作方向的控制标志(DF)
- 供操作系统软件执行管理操作的系统标志
标志 | 位序号 | 含义 |
---|---|---|
CF (Carry Flag) | 0 | 进位或错位 |
PF (Parity Flag) | 2 | 当计算结果最低字节包含偶数个1时,该标志为1 |
AF (Adjust Flag) | 4 | 辅助进位标志,但位3处有进位或借位时该标志为1 |
ZF (Zero Flag) | 6 | 当计算结果为0时,该标志为1 |
SF (Sign Flag) | 7 | 符号标志,结果为负置1 |
TF (Trap Flag) | 8 | 陷阱标志 |
IF (Interrupt enable Flag) | 9 | 中断标志,为0时禁止响应可屏蔽中断 |
DF (Direction Flag) | 10 | 方向标志,为1时使字符串指令每次操作后递减变址寄存器 |
OF (Overflow Flag) | 11 | 溢出标志 |
IOPL (I/O Privilege Level) | 12和13 | 用于表示当前任务的I/O权限级别 |
NT (Nested Task Flag) | 14 | 任务嵌套标志,为1时表示当前任务链接到前面执行的任务,通常是由于中断或者异常触发了IDT表中的任务门 |
RF (Resume Flag) | 16 | 控制处理器对调试异常(#DB)的响应,为1时展示进制由于指令断点导致的调试异常 |
VM (Virtual-8086 Mode flag) | 17 | 为1时启用虚拟8086模式 |
AC (Alignment Check flag) | 18 | 设置此标志和CR0的AM标志可以启用内存对齐检查 |
VIF (Virtual Interrupt Flag) | 19 | 与VIP标志一起用于实现虚拟中断机制 |
VIP (Virtual Interrupt Pending flag) | 20 | 与VIF标志一起用于实现虚拟中断机制 |
ID (Identification Flag) | 21 | 用于检测是否支持CPUID指令 |
MSR寄存器
MSR(Model Specific Register)的本意是指这些寄存器与CPU的信号有关,尚未正式加入IA-32架构中,也有可能不会被以后的CPU所兼容。但某些MSR寄存器因为已经被多款CPU所广泛支持,逐渐成为了IA-32架构的一部分。每个MSR寄存器除了具有一个简短的帮助记忆的代号外,还具有一个用作标识的整数ID,有时候也把MSR寄存器的ID称为该寄存器的地址,如用于控制IA-32e模式的EFER
寄存器的地址是0XC000080
- 读:
RDMSR
指令用于读取MSR寄存器,首先将要读的MSR的ID放入ECX寄存器中,然后执行RDMSR
指令,如果操作成功,返回值会放入EDX:EAX(高32位放入EDX,低32位放入EAX) - 写:
WRMSR
指令用来写MSR寄存器,先把MSR的ID放入ECX中,然后把要写入的数据放入EDX:EAX中,最后执行WRMSR
指令。
控制寄存器
IA-32 CPU设计了5个控制寄存器(CR0~CR4),用来决定CPU的操作模式以及当前任务的关键特征。CR0和CR4包含了许多与CPU工作模式密切相关的标志位,CR2和CR3都与分页机制有关,CR1保留未用。
标志 | 位序号 | 含义 |
---|---|---|
PE (Protection Enable) | CR0[0] | 为1时启用保护模式,为0时代表实地址模式 |
MP (Monitor Coprocessor) | CR0[1] | 用来控制WAIT/FWAIT指令对TS标志的检查 |
EM (Emulation) | CR0[2] | 为1时表示使用软件来模拟浮点单元(FPU)进行浮点运算,为0时表示处理器具有FPU |
TS (Task Switched) | CR0[3] | 当CPU在每次切换任务时设置该位,在执行x87 FPU和MMX/SSE/SSE2/SSE3指令时检查该位,主要用于支持在任务切换时延迟保存指令上下文 |
ET (Extension Type) | CR0[4] | 对于386和486,为1时表示支持387数学协处理器指令,486以后的CPU该位固定为1 |
NE (Numeric Error) | CR0[5] | 用来控制x87 FPU的错误报告方式,为1时启用内部的本位机制,为0时启用与DOS兼容的PC方式 |
WP (Write Protect) | CR0[16] | 为1时,禁止内核代码写用户级的只读内存页 |
AM (Alignment Mask) | CR0[18] | 为1时启用自动内存对齐检查 |
NW (Not Write-through) | CR0[29] | 与CD标志共同控制高速缓存有关选项 |
CD (Cache Disable) | CR0[30] | 与NW标志共同控制高速缓存有关选项 |
PG (Paging) | CR0[31] | 为1时启用页机制 |
PWT (Page-level Writes Transparent) | CR3[3] | 控制页目录的缓存方式,为1时启用write-through方式缓存,为0时启用write-back方式缓存 |
PCD (Page-level Cache Disable) | CR3[4] | 控制是否对当前页目录进行高速缓存,为1时禁止 |
VME (Virtual-8086 Mode Extensions) | CR4[0] | 为1时启用虚拟8086模式下的中断和异常处理扩展,将中断和异常重定向到8086程序的处理例程以减少调用虚拟8086监视程序的开销 |
PVI (Protected-Mode Virtual Interrupts) | CR4[1] | 为1时启用硬件支持的虚拟中断标志,为0时禁止VIF标志 |
TSD (Time Stamp Disable) | CR4[2] | 为1时只有在ring0才能使用RDTSC指令,为0时所有特权级都可以使用该指令读取时间戳 |
DE (Debugging Extensions) | CR4[3] | 为1时应用DR4和DR5寄存器将导致无效指令(#UD)异常,为0时引用DR4和DR5等价于引用DR6和DR7 |
PSE (Page Size Extensions) | CR4[4] | 为1时启用4MB内存页,为0时限制内存页为4MB |
PAE (Physical Address Extension) | CR4[5] | 为1时支持36位或以上的物理内存地址,为0时限制物理地址为32位 |
MCE (Machine-Check Enable) | CR4[6] | 为1时启用机器检查异常,为0时禁止 |
PGE (Page Global Enable) | CR4[7] | 为1时启用全局页功能 |
PGE (Performance-Monitoring Counter Enable) | CR4[8] | w为1时允许所有特权级代码都可使用RDPMC指令读取性能计数器,为0是只有ring0级别代码可读取 |
OSFXSR (Operating System Support for FXSAVE and FXRSTOR instructions) | CR4[9] | 操作系统使用,表示操作系统对FXSAVE、FXRSTOR及SSE/SSE2/SSE3指令的支持,以保证较老的操作系统能够运行在较新的CPU上 |
OSXMMEXCRT (Operating System Support for Unmasked SIMD Floating-Point Exceptions) | CR4[10] | c操作系统使用,表示操作系统对SIMD浮点异常(#XF)的支持。如果该位为0表示操作系统不支持#XF,CPU会通过#UD来报告#XF异常 |
其他寄存器
- 6个16位段寄存器:CS、DS、SS、ES、FS、GS,当CPU工作在实模式时,其内容代表的是段地址的高16位,在保护模式下存放的是段选择子
- 1个32位的应用程序指针寄存器EIP(Extended Instruction Pointer),指向的是CPU要执行的下一条指令,其值为该指令在当前代码段中的偏移地址。如果一条指令有多个字节,那么EIP指向的是该指令的第一个字节
- 8个128位的向量运算寄存器XMM0\~XMM7,供SSE/SSE2/SSE3指令使用以支持对单精度浮点数进行SIMD计算
- 8个80位的FPU和MMX两用寄存器ST0\~ST7,当执行MMX指令时,其中的低64位用作MMX数据寄存器MM0~MM7,当执行X87浮点指令时,用作浮点数据寄存器R0~R7
- 1个32位的中断描述表寄存器IDTR,用于记录中断描述表(IDT)的基地址和边界
- 1个32位的全局描述表寄存器GDTR,用于描述全局描述表(GDT)的基地址和边界
- 1个16位的局部描述表(LDT)寄存器LDTR,存放局部描述表的选择子
- 1个16位的任务寄存器TR,用于存放选取任务状态段(TSS)描述符的选择子。TSS用来存放一个任务的状态信息,在多任务环境下,CPU从一个任务切换到另一任务时,会将前一任务的寄存器等状态保存到TSS中。
- 1个64位的时间戳计数器TSC,每个时钟周期其值加1,重启后清零
- 内存类型范围寄存器MTRR,定义了内存空间中各个区域的内存类型
64位模式时的寄存器
当支持64位的IA-32 CPU工作在64位模式时,所有通用寄存器和大多数其他寄存器都延展为64位,但段寄存器始终为16位。可以使用RXX来引用,如RAX、RFLAGS、RIP等。此外,64位模式增加了如下寄存器:
- 8个新的通用寄存器R8\~R15,可以使用R
n
D、Rn
W、Rn
L(n = 8\~15)来访问寄存器的低32位、16位和8位 - 8个新的SIMD寄存器XMM8\~XMM15
- 控制寄存器CR8,又称任务优先寄存器(Task Priority Register)
- Extended-Feature-Enable Register(EFER) 寄存器,用来控制扩展的CPU功能,其作用与标志寄存器类似。
保护模式
大多数现代操作系统是多任务的,保护模式是为实现多任务而设计。“保护“就是保护多任务环境中各个任务的安全,不被其他任务所破坏。保护模式可分为任务内保护和任务间保护,任务内保护靠特权级别检查实现同意内务空间内不同级别的代码不会相互破坏,任务间保护依靠内存映射机制。
任务内保护
任务内保护主要用于保护操作系统。操作系统的代码和数据通常被映射到系统中每个任务空间中,并且对于所有任务,其地址是一样的。这意味着操作系统的空间对于应用程序是“可触及”的,应用程序中的指针可以指向操作系统所使用的内存。
任务内保护的核心思想是权限控制,即为代码和数据根据其重要性指定特权级别,高特权级别的代码可以执行和访问低特权级的代码和数据,而低特权级的代码无法执行和访问高特权级的代码和数据。高特权级的代码通常为操作系统或内核态驱动的代码,低特权级通常为应用程序的代码。如此一来,操作系统可以直接访问应用程序的代码和数据,而应用程序虽然可以指向操作系统的空间,但是不能访问,一旦访问就会被系统发现并禁止。如执行下列代码
int main()
{
*(int*)0xA0808080 = 0x22;//修改系统空间内的数据
return 0;
}
会发生如下错误
0x011317C8 处(位于 Project1.exe 中)引发的异常: 0xC0000005: 写入位置 0xA0808080 时发生访问冲突。
事实上,应用程序只能通过操作系统公开的接口来使用操作系统的服务,即系统调用。
特权级
IA-32处理器定义了4个特权级,又称为环(ring),分别用0,1,2,3表示。ring 0代表的特权级别最高,ring 3代表的特权级别最低。在Windows中,操作系统内核代码和数据运行在ring 0,称为内核态;应用程序运行在ring 3,称为用户态。进一步说,处理器通过以下三种方式来记录和监控特权级别以实现特权控制
- 描述符特权级别(Descriptor Privilege Level, DPL),位于段描述符或门描述符中,表示一个段或门的特权级别。
- 当前特权级别(Current Privilege Level, CPL),位于CS和SS寄存器的第0位和第1位中,用于表示当前正在执行的程序或任务的特权级别。通常CPL等于当前正被执行代码段的DPL。当处理器切换到不同的DPL段时,CPL也会随之变化。但是由于特权级别高的程序可以访问特权级别低的数据,因此当CPU访问DPL大于CPL(数值上)的一致代码段时,CPL保持不变
- 请求者特权级别(Requestor Privilege Level, RPL),用于系统调用的情况,位于保存在栈中段选择子的第0位或第1位,用来代表请求系统服务的应用程序的特权级别。在判断是否可以访问一个段时,CPU纪要检查CPL,也要检查RPL,这样做的目的是防止高特权级的代码代替应用程序访问越权区段。如果只检查CPL,应用程序在执行系统调用时,CPL为系统代码的特权级,需要RPL来反映发起访问者的特权级。
特权指令
为了防止低特权级的应用修改系统数据,某些重要的指令只可以在ring 0下执行,这些指令被称为特权指令。
指令 | 含义 |
---|---|
CLTS | 清除CR0寄存器中TS标志 |
HLT | 使CPU停止执行指令进入HALT状态,中断、调试异常或BINIT#、RESET#等硬件信号会使CPU脱离HALT状态 |
INVD | 使高数缓存无效,不回写(不必把数据写回到主机内存) |
WBINVD | 使高数缓存无效,回写 |
INVLPG | 使TLB表无效 |
LGDT | 加载GDTR寄存器 |
LIDT | 加载IDTR寄存器 |
LLDT | 加载LDTR寄存器 |
LMSW | 加载机器状态字,即CR0的0至15位 |
LTR | 加载TR寄存器 |
MOV to/from CRn | 读取或赋值控制寄存器 |
MOV to/from DRn | 读取或赋值调试寄存器 |
MOV to/from TRn | 读取或赋值测试寄存器 |
RDMSR | 读MSR寄存器 |
WRMSR | 写MSR寄存器 |
RDPMC | 读性能监控计数器 |
RDTSC | 读时间戳计数器 |
段机制
在一个多任务系统中,系统需要有一套机制来隔离不同任务所使用的内存,IA-32 CPU提供了多种内存管理机制,这些机制为操作系统实现内存管理功能提供了硬件基础。CPU的段机制将系统的内存空间划分为多个较小的受保护区域,其中每个区域称为一个段,每个段都有自己的起始地址、边界和访问权限等属性。
段描述符
实现段机制的一个重要数据结构是段描述符。在保护模式下每个内存段都有一个段描述符,这是其他代码访问该段的基本条件,段描述符是一个8字节长结构,用来描述一个段的位置、大小、访问控制和状态等信息。
各属性简介如下:
- 段基地址:以4个字节表示的内存地址,寻址大小为4GB,范围为(0x00000000\~0xFFFFFFFF)
- 段界限:用20个比特位表示的段界限,其单位由粒度位(G)决定,当G=0时边界的单位为1字节,G=1时是4KB。因此一个段的最大边界值是2^20 – 1,最大长度是2^20 * 4KB = 4GB。
- S:系统(System)位,S=0时代表该描述符描述的是一个系统段,S=1代表该描述符描述的是代码段、数据段或堆栈段
- P:存在(Present)位,该位代表被描述的段是否在内存中,P=1表示该段已在内存中,P=0表示该段不在内存中。内存管理软件可以使用该位来控制哪些段实际加载入物理内存中
- DPL:描述符特权级(Descriptor Privilege Level):这两位定义了该段的特权级别(0\~3),当且仅当访问该段程序的CPL等于或高于这个段的级别时才允许访问,否则会产生保护性异常(GPF)
- D/B:(Default/Big)
- 对于代码段,该位为D位,表示代码段的默认位数(Default Bit),D=0时表示该段为16位代码段,D=1时为32位代码段。默认代码长度属性定义的是默认的地址和操作数长度,可以用地址大小前缀和操作数大小前缀来改变默认长度
- 对于栈数据段,该位被称为B(Big)标志位,B=1时表示使用32位的堆栈指针(ESP),B=0时表示使用16位的堆栈指针(SP)。对于向下扩展的数据段,段边界指定的是该段的最小偏移,B标志用来指定上边界,当B=1时,最大偏移是
0xFFFFFFFF
,在此情况下,如果L=0,那么段的总长度便是4GB(G=0),如果B=0,那么上边界便是0xFFFF
。
- Type:表示段类型
- 0:位0为访问位(Access),简称A位,表示该段是否被访问过,A=1表示被访问过
- 1:对于数据/堆栈段,该位为读写控制位(Write),简称W位,W=0表示该段只读,W=1表示该段可以读写;对于代码段,该位表示该段是否可读(Read),简称R位,R=1表示该段即可执行又可以读,R=0表示只可以执行,不可以读。
- 2:对于数据/堆栈区段,该位为扩展方向位(Expend),简称E位,E=0表示段向高端扩展,反之向低端扩展;对于代码段,该位表示该段是否是一致代码段(Conforming),简称C位。
- 3:D/C(Data/Stack) 位,为0时表示该段是数据/堆栈段,为1时表示该段为代码段
- L:用于描述IA-32e模式下的代码段,L=1表示该代码段包含的是64位代码,为0时表示该段包含兼容模式代码
- AVL:(Available and reserved bits):供操作系统使用
段描述表
在一个多任务系统中会同时存在多个任务,每个人任务会涉及多个段,每个段都有相应的段描述符,因此系统中会有多个段描述符,为了便于管理,系统使用线性表来存放段描述符。根据用途的不同,IA-32处理器有3钟描述符表:全局描述符表(Global Descriptor Table, GDT)、局部描述表(Local Descriptor Table, LDT)和中断描述表(Interrupt Descriptor Table)。
GDT是全局的,一个系统中通常只有一个GDT,供系统中所有程序和任务使用;LDT与任务相关,每个任务都可以有一个LDT,也可以让多个任务共享一个LDT。IDT的数量与处理器的数量相关,系统通常会为每一个CPU建立一个IDT。
位于GDT中第一个表项的描述符通常保留不用,称为空描述符。
当创建LDT时,GDT已经准备好,因此LDT被创建为一种特殊的系统段,其段描述符被放在GDT表中。GDT表本身是一个数据结构,没有对应的段描述符。
GDTR和IDTR寄存器分别用来标识GDT和IDT的基地址和边界,这两个寄存器的格式是相同的。在32位下,长度是48位,高32位是基址,低16位是边界;在IA-32e模式下,长度是80位,高64位是基址,低16位是边界。使用WinDBG的r
命令可以查看GDTR和IDTR寄存器的值,由于它们是80位的,因此应该分两次读取,分别获取它们的段地址和边界。
0: kd> r gdtr
gdtr=fffff8046a889fb0; //GDT表基址为0xfffff8046a889fb0
0: kd> r gdtl
gdtl=0057; //GDT表的边界为127,总长度为128字节,共有16个表项
0: kd> r idtr
idtr=fffff8046a887000; //IDT表基址为0xfffff8046a889fb0
0: kd> r idtl
idtl=0fff; //IDT表的边界为4095,总长度为4096个字节,共有512个表项
段选择子
局部描述表寄存器LDTR表示当前任务的LDT在GDT中的索引,其格式为
- RPL:用于特权检查
- TI:代表要索引的段描述表类型,TI=0表示全局描述表,TI=1表示局部描述表
- Index:描述符索引,即要选择的段描述符在TI说表示的段描述表中的索引号。Index的长度为12位,意味着最多可索引8192个描述符,所以GDT和LDT最大的表项数都是8192.由于x86 CPU最多支持256个中断向量,因此IDT最多表项数为256。
调试观察段寄存器
可以使用调试工具来观察段寄存器的值(CS\~SS)。比如观察CS寄存器,可以使用WInDBG的r
命令
0: kd> r cs
cs=0010
将0010解码得0000 0000 0001 0000
,可以看出CS寄存器指向的是GDT(TI=0),PRL为0,即内核态。
使用WinDBG的dg
命令可以显示一个段选择子所指向的段描述符的详细信息,如
0: kd> dg [cs]
P Si Gr Pr Lo
Sel Base Limit Type l ze an es ng Flags
---- ----------------- ----------------- ---------- - -- -- -- -- --------
0010 00000000`00000000 00000000`00000000 Code RE Ac 0 Nb By P Lo 0000029b
Sel代表选择子,Base和Limit分别是基地址和边界,Type是段的类型,RE代表只读和可以执行,Ac代表该段被访问过。Pl代表特权级别,为0即ring 0,Size指代码长度,Gran表示粒度,Pres表示该段是否驻留在内存中,Long表示该段是否为64位代码。
总结
总体而言,段机制使保护模式下所有的任务都在系统分配给它的段空间中执行,每个任务的代码和数据都是相对于它所在段的一个段内便宜。处理器会根据段选择子在段描述表中找到该段的段描述符,然后再根据段描述符定位段。每个段都有自己的特权级别以实现对代码和数据的保护。