Windows XP 页保护(三)
ʕ •ᴥ•ʔ ɔ:
1 TLB
1.1 TLB结构
当程序访问一个线性地址,需要先查PDPT,然后查PDT,然后查页表PTT,最后才是访问物理页。这期间多次访问内存,效率非常低。于是TLB就被设计出来了。(64位CPU分页及TLB请点这儿)
TLB(Translation Lookaside Buffer),也叫快表。TLB 表在CPU内部,一个CPU有一张TLB表,用来缓存线性地址和物理地址的映射关系,以及属性和访问次数。(实际上缓存的线性地址、物理地址为页基地址)
- LA:存储的是线性地址所在页基地址。
- PA:线性地址所在对应的物理页基址。
- ATTR:属性,除了G、XD位属性为OR外,其余的位的属性为AND。
- LRU:统计这个线性地址的读写情况的,这是因为TLB这个表在CPU内部,那么他就不会很大,当线性地址存储满了的时候,他就会看LRU的统计情况,把读写的次数比较少的项删除,然后再把新的线性地址项添上。
关于TLB表:
- TLB是受PDE和PTE中的G位影响的,PDE中的G位又受PS位影响,且G位属性:PDE_G || PTE_G。
- 不同的CPU这个TLB表的大小不一样,能存几十项或者几百项。
- 只要CR3寄存器被重写,哪怕重新写的值和原来的相同,TLB立马刷新。一核一套TLB。
- 操作系统的高2G映射基本不变,如果CR3被重写了,TLB刷新,重建高2G以上很浪费。故高2G的线性地址对应的物理页的G位常为1(全局页)。
所以PDE和PTE中有个G标志位:- G = 1,刷新TLB时将不会刷新PDE/PTE的G位为1的页。
- 当TLB满了,根据统计信息将不常用的地址废弃,最近最常用的保留.
TLB在X86体系的CPU里的实际应用最早是从Intel的486CPU开始的,在X86体系的CPU里边,一般都设有如下4组TLB:
第一组:缓存一般页表(4K字节页面)的指令页表缓存(Instruction-TLB);
第二组:缓存一般页表(4K字节页面)的数据页表缓存(Data-TLB);
第三组:缓存大尺寸页表(2M/4M字节页面)的指令页表缓存(Instruction-TLB);
第四组:缓存大尺寸页表(2M/4M字节页面)的数据页表缓存(Data-TLB)
1.2 练习:体验TLB的存在
1 |
|
结论:修改一次NULL的PTE,PTE_G = 0后,再继续修改一次NULL的PTE,结果第一次从NULL地址读出来的值是第一次的赋值。说明TLB是存在的。
结论:PTE的G位为0的情况下,凡是涉及修改PTE,要想使修改后的值立即生效,必须使用INVLPG指令,否则由于存在TLB的缘故,修改PTE后第一次再读原地址的值会从TLB中取
1.3 练习:PTE_G、切换CR3对TLB的影响
从上一个练习可知,因为TLB的存在,修改PTE后读内存会存在“误差”。
但是TLB会受PTE_G值和切换CR3所影响,具体表现为:修改CR3的值后,PTE_G = 1的TLB缓存不会被清理,PTE_G = 0的TLB缓存会被清理。
结论:PTE的G位为0的情况下,凡是涉及修改PTE,要想使修改后的值立即生效,必须使用INVLPG指令,否则由于存在TLB的缘故,修改PTE后第一次再读原地址的值会从TLB中取
示例1:NULL的PTE_G = 0,修改CR3的值,PTE_G = 0指向的物理页记录的TLB会被刷新,读内存不存在“误差”。
mov dword ptr ds:[0xc0000000],0x01234867
1 |
|
示例2:NULL的PTE_G = 1,修改CR3的值,PTE_G = 1指向的物理页记录的TLB不会被刷新,读内存存在“误差”。
mov dword ptr ds:[0xc0000000],0x01234967
1 |
|
1.4 练习:INVLPG清空某线性地址的TLB
NULL的PTE_G = 1,使用指令INVLPG dword ptr ds:[0]
会清空NULL在TLB的纪录,下一次读内存时将会重新解析线性地址并缓存至TLB,不会存在“误差”。指令INVLPG强行删除某项全局页缓存或非全局页的缓存。
结论:PTE的G位为0的情况下,凡是涉及修改PTE,要想使修改后的值立即生效,必须使用INVLPG指令,否则由于存在TLB的缘故,修改PTE后第一次再读原地址的值会从TLB中取。
INVLPG dword ptr ds:[0]
1 |
|
2 中断和异常
2.1 中断
中断通常是由CPU外部的输入输出设备(硬件)所触发的,供外部设备通知,CPU“有事情需要处理”,因此又叫中断请求(Interrupt Request)。
中断请求的目的是希望CPU暂时停止执行当前正在执行的程序,转去执行中断请求所对应的中断处理例程(中断处理程序在哪有IDT表决定)
80×86有两条中断请求线:
- 非屏蔽中断线,称为NMI(NonMaskable Interrupt)
- 可屏蔽中断线,称为INTR(Interrupt Require)
⚠️注意:
- 中断的本质:改变CPU执行的路线。
- 操作系统并没有使用时钟进行线程切换,时钟只是线程切换的一个条件。
一、非可屏蔽中断
特别说明:
- 当非可屏蔽中断产生时,CPU在执行完当前指令后会立即进入中断处理程序。
- 非可屏蔽中断不受EFLAG寄存器中IF位的影响,一旦发生,CPU必须处理。
- 非可屏蔽中断处理程序位于IDT表中的2号(0x2)位置。
二、可屏蔽中断
在硬件级,可屏蔽中断是由一块专门的芯片来管理的,通常称为中断控制器。它负责分配中断资源和管理各个中断源发出的中断请求。为了便于标识各个中断请求,中断管理器通常用IRQ(Interrupt Request)后面加上数字来表示不同的中断。
比如:在Windows中 时钟中断的IRQ编号为0 也就是:IRQ0。
特别说明:
如果自己的程序执行时不希望CPU去处理这些中断,可以用CLI指令清空EFLAG寄存器中的IF位,IF = 0。(STI指令可使IF = 1)
硬件中断与IDT表中的对应关系并非固定不变的,参见:APIC(高级可编程中断控制器)。
大多数操作系统时钟中断在10-100ms之间,Windows系列为10-20ms。
2.2 异常
异常通常是CPU在执行指令时检测到的某些错误,比如除0、访问无效页面等。
中断与异常的区别:
1、中断来自于外部设备,是中断源(比如键盘)发起的,CPU是被动的。
2、异常来自于CPU本身,是CPU主动产生的。
3、INT N
虽然被称为“软件中断”,但其本质是异常。EFLAG的IF位对INT N无效。
⚠️注意:无论是由硬件设备触发的中断请求还是由CPU产生的异常,处理程序都在IDT表。
常见的异常处理程序:
比如缺页异常,CPU会执行IDT表中的0xE号中断处理程序,由操作系统来接管。
缺页异常的产生:
1、当PDE/PTE的P=0时。
2、当PDE/PTE的属性为只读但程序试图写入时。
3、当物理内存空间不足时,线性地址对应的物理页将被存储到文件中。
如下图左,产生缺页异常时,如果PTE下标0、10、11位为0,其余位为1,则会从文件中重新写回一个物理页。
有效位(下标9、10、11),在发生缺页时(PTE的P=0)使用。分以下四种情况(具体可见内存管理-无处不在的缺页异常)。关于这部分知识,等以后学习了内存管理的缺页异常就知道了,这里简单介绍一下。
int N中断号对应信息如下:
1.3 分析IDT表中0x2号中断的执行流程
调用门、任务门、中断门、陷进门段描述符如下(GDT中无调用门描述符,IDT中无中断门描述符,但是我们可以构造):
可以看到低4字节中的高16位,除任务门中为任务段外都为段选择子,用于查GDT表。
查IDT表。0x2号中断,查idt表下标2,即第三个。
P = 1,S = 0。
Type = 0x5 = 0101,对应任务门。(Type域查表)
段选择子:0x0058。
偏移Offset:任务门描述符不存在偏移,而非0x0000113e。
查IDT表。段选择子0x0058–0101 1000– 1011 0 00 = 11,即第12个。(段选择子结构查表)
- P = 1,S = 0。
- Type = 0x9,对应32位的任务段。(32位TSS段描述符)
从TSS段描述符中获取Base,然后获取EIP的值TSS[8]即第9个。
- TSS.Base = 0x80552768
得到EIP = 0x8054340c,则0x2号中断应该执行的代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129kd> uf 8054340c
nt!Kei386EoiHelper+0x560:
8054340c fa cli //if = 0,屏蔽中断。sti--if = 1
8054340d 64ff3540000000 push dword ptr fs:[40h]//fs.base + 0x40
80543414 64a13c000000 mov eax,dword ptr fs:[0000003Ch]
8054341a 8a685f mov ch,byte ptr [eax+5Fh]
8054341d 8a485c mov cl,byte ptr [eax+5Ch]
80543420 c1e110 shl ecx,10h
80543423 668b485a mov cx,word ptr [eax+5Ah]
80543427 64890d40000000 mov dword ptr fs:[40h],ecx
8054342e 9c pushfd
8054342f 812424ffbfffff and dword ptr [esp],0FFFFBFFFh
80543436 9d popfd
80543437 648b0d3c000000 mov ecx,dword ptr fs:[3Ch]
8054343e 8d4158 lea eax,[ecx+58h]
80543441 c6400589 mov byte ptr [eax+5],89h
80543445 8b0424 mov eax,dword ptr [esp]
80543448 6a00 push 0
8054344a 6a00 push 0
8054344c 6a00 push 0
8054344e 6a00 push 0
80543450 ff7050 push dword ptr [eax+50h]
80543453 ff7038 push dword ptr [eax+38h]
80543456 ff7024 push dword ptr [eax+24h]
80543459 ff704c push dword ptr [eax+4Ch]
8054345c ff7020 push dword ptr [eax+20h]
8054345f 6a00 push 0
80543461 ff703c push dword ptr [eax+3Ch]
80543464 ff7034 push dword ptr [eax+34h]
80543467 ff7040 push dword ptr [eax+40h]
8054346a ff7044 push dword ptr [eax+44h]
8054346d ff7058 push dword ptr [eax+58h]
80543470 64ff3500000000 push dword ptr fs:[0]
80543477 6aff push 0FFFFFFFFh
80543479 ff7028 push dword ptr [eax+28h]
8054347c ff702c push dword ptr [eax+2Ch]
8054347f ff7030 push dword ptr [eax+30h]
80543482 ff7054 push dword ptr [eax+54h]
80543485 ff7048 push dword ptr [eax+48h]
80543488 ff705c push dword ptr [eax+5Ch]
8054348b 6a00 push 0
8054348d 6a00 push 0
8054348f 6a00 push 0
80543491 6a00 push 0
80543493 6a00 push 0
80543495 6a00 push 0
80543497 6a00 push 0
80543499 6a00 push 0
8054349b 6a00 push 0
8054349d 6a00 push 0
8054349f ff7020 push dword ptr [eax+20h]
805434a2 ff703c push dword ptr [eax+3Ch]
805434a5 8bec mov ebp,esp
805434a7 33db xor ebx,ebx
805434a9 648a1d51000000 mov bl,byte ptr fs:[51h]
805434b0 391ddc275580 cmp dword ptr [nt!NtBuildNumber+0x46f4 (805527dc)],ebx
805434b6 7414 je nt!Kei386EoiHelper+0x620 (805434cc)
nt!Kei386EoiHelper+0x60c:
805434b8 8d05d8275580 lea eax,[nt!NtBuildNumber+0x46f0 (805527d8)]
805434be 50 push eax
805434bf 6a00 push 0
805434c1 8bcc mov ecx,esp
805434c3 8bd5 mov edx,ebp
805434c5 e83acbfbff call nt!KeRestoreFloatingPointState+0x21a (80500004)
805434ca eb24 jmp nt!Kei386EoiHelper+0x644 (805434f0)
nt!Kei386EoiHelper+0x620:
805434cc 833de027558008 cmp dword ptr [nt!NtBuildNumber+0x46f8 (805527e0)],8
805434d3 721b jb nt!Kei386EoiHelper+0x644 (805434f0)
nt!Kei386EoiHelper+0x629:
805434d5 7517 jne nt!Kei386EoiHelper+0x642 (805434ee)
nt!Kei386EoiHelper+0x62b:
805434d7 803d406a558000 cmp byte ptr [nt!KdDebuggerNotPresent (80556a40)],0
805434de 750e jne nt!Kei386EoiHelper+0x642 (805434ee)
nt!Kei386EoiHelper+0x634:
805434e0 803d416a558000 cmp byte ptr [nt!KdDebuggerEnabled (80556a41)],0
805434e7 7405 je nt!Kei386EoiHelper+0x642 (805434ee)
nt!Kei386EoiHelper+0x63d:
805434e9 e8566bfbff call nt!KeEnterKernelDebugger (804fa044)
nt!Kei386EoiHelper+0x642:
805434ee ebfe jmp nt!Kei386EoiHelper+0x642 (805434ee)
nt!Kei386EoiHelper+0x644:
805434f0 891ddc275580 mov dword ptr [nt!NtBuildNumber+0x46f4 (805527dc)],ebx
805434f6 ff05e0275580 inc dword ptr [nt!NtBuildNumber+0x46f8 (805527e0)]
805434fc 6a00 push 0
805434fe ff158c904d80 call dword ptr [nt+0x108c (804d908c)]
80543504 ff0de0275580 dec dword ptr [nt!NtBuildNumber+0x46f8 (805527e0)]
8054350a 754a jne nt!Kei386EoiHelper+0x6aa (80543556)
nt!Kei386EoiHelper+0x660:
8054350c c705dc275580ffffffff mov dword ptr [nt!NtBuildNumber+0x46f4 (805527dc)],0FFFFFFFFh
80543516 8bcc mov ecx,esp
80543518 e88fe5ffff call nt!KeReleaseInStackQueuedSpinLockFromDpcLevel+0x4 (80541aac)
8054351d 83c408 add esp,8
80543520 64a140000000 mov eax,dword ptr fs:[00000040h]
80543526 66833858 cmp word ptr [eax],58h
8054352a 742a je nt!Kei386EoiHelper+0x6aa (80543556)
nt!Kei386EoiHelper+0x680:
8054352c 81c48c000000 add esp,8Ch
80543532 648f0540000000 pop dword ptr fs:[40h]
80543539 648b0d3c000000 mov ecx,dword ptr fs:[3Ch]
80543540 8d4128 lea eax,[ecx+28h]
80543543 c640058b mov byte ptr [eax+5],8Bh
80543547 9c pushfd
80543548 810c2400400000 or dword ptr [esp],4000h
8054354f 9d popfd
80543550 cf iretd
nt!Kei386EoiHelper+0x6aa:
80543556 b802000000 mov eax,2
8054355b e9fc280000 jmp nt!KiCoprocessorError+0xe4 (80545e5c)
nt!KiCoprocessorError+0xe4:
80545e5c 55 push ebp
80545e5d 6a00 push 0
80545e5f 6a00 push 0
80545e61 6a00 push 0
80545e63 50 push eax
80545e64 6a7f push 7Fh
80545e66 e80546fbff call nt!KeRegisterBugCheckReasonCallback+0x208 (804fa470)
80545e6b c3 ret
Type域查表:
段选择子结构:
32位TSS段描述符:
3 控制寄存器
控制寄存器用于控制和确定CPU的操作模式。
CR0、CR1、CR2、CR3、CR4:
- CR1:保留。
- CR2:缺页线性地址。
- CR3:页目录表基址。
3.1 CR0
说明:
- PE位:为CR0的位0,是启用保护(Protection Enable)标志。这个标志仅开启段级保护,而并没有启用分页机制。
- PE = 1,保护模式。
- PE = 0。实地址模式。
若要启用分页机制,那么PE和PG标志都要置位。
- PG位:当设置该位时即开启了分页机制。在开启这个标志之前必须已经或者同时开启PE标志。
- PG = 0 且PE = 0 ,处理器工作在实地址模式下。
- PG = 0且PE = 1,处理器工作在没有开启分页机制的保护模式下(目前没有这样的操作系统)。
- PG = 1且PE = 0 ,在PE没有开启的情况下,无法开启PG。(没有这样的状态)
- PG = 1且PE = 1,处理器工作在开启了分页机制的保护模式下。
- WP位:对于Intel 80486或以上的CPU,CR0的位16是写保护(Write Proctect)标志。当设置该标志时,处理器会禁止超级用户程序(例如特权级0的程序)向用户级只读页面执行写操作。
当CPL<3的时候:- WP = 0,可以读写任意用户级物理页,只要线性地址有效。
- WP = 1,可以读取任意用户级物理页,但对于只读的物理页,则不能写。
3.2 CR2
当CPU访问某个无效页面时,会产生缺页异常,此时,CPU会将引起异常
的线性地址存放在CR2中。
3.3 CR3
CR3一包含了页目录的基地址和二个标志(PCD和PWT)。该寄存器也被称为页目录基地址寄存器(PDBR)。页目录基地址只有高20位确定,低12位是0,所以页目录地址必须是页边界对齐的(4K字节)。
对齐的(4K字节)。PCD和PWT标志控制着页目录在处理器内部数据缓冲区的缓存(它们不控制TLB页目录信息的缓存)。
3.4 CR4
CR4包含了一组标志,这些标志启用了架构方面的几个扩展,并指明了系统对某些处理器支持的能力。。在保护模式下,MOV指令允许读取或者装载控制寄存器(在0级特权下)。这个限制意味着应用程序或者操作系统过程(运行在1、2、3级特权下)不能读取或者装载控制寄存器。装载控制寄存器时,保留位应该保持以前读取的值。
PAE/PSE说明:
- PAE = 1,是2-9-9-12分页。
- PAE = 0,是10-10-12分页。
应特别注意,PDE的PS位受CR4的PSE位影响,具体如下:
4 PCD和PWT标志位
PCD和PWT标志存在于PDBR(CR3)、PDPE、PDE、PTE中,分别如下:
10-10-12分页PDE、PTE:
2-9-9-12分页PDPE:
PDE:
PTE:
1、CPU缓存
1)CPU缓存是位于CPU与物理内存之间的临时存储器,它的容量比内存小的多但是交换速度却比内存要快得多。
2)CPU缓存可以做的很大,有几K、几十K、几百K 甚至上M的也有。
CPU缓存与TLB的区别:
TLB:
线性地址 <—–> 物理地址
CPU缓存:
物理地址 <—–> 物理地址中的内容
2、关于PWT/PCD属性
PWT:Page Write Through
- PWT = 1,写Cache的时候也要将数据写入内存中。
- PWT = 0,写Cache时由CPU控制寄存器决定是否写入内存。
PCD:Page Cache Disable
- PCD = 1,禁止某个页写入缓存,直接写内存。
- PCD = 0,可以写入Cache。
比如,做页表用的页,已经存储在TLB中了,可以不需要再缓存了。
5 保护模式:测试1
以下代码对每一个线性地址都做了检查,起始这已经包括跨页检查了。
跨页检查的目的:如线性地址0x0040ffd,如果要取4字节内容,则最后一个字节在下一个页,这个时候还需要对下一个页的PDE、PTE做检查。而我的代码是对每一个线性地址做检查,粒度是每一个地址,而非页,所以自然包含了跨页内存检查。
1 |
|
6 保护模式:测试2
题目:
申请长度为100的DWORD的数组,且每项的值用该项的地址初始化;
把这个数组所在的物理页挂到0x1000的地址上;
定义一个指针,指向0x1000这个页里的数组所在的地址,用0x1000这个页的线性地址打印出这数组的值。
要求:
数组所在的物理页,是同一个页。
本题在:2-9-9-12分页模式下,页大小:4KB,PTE_PAT = 0条件下解题。
- 先获取数组pArr的PDE、PTE。
- 检查线性地址0x1000的PDE_P:(若PDE存在且PS = 1,仅需要替换PTE即可)
- PDE_P = 1,判断PDE_PS:
- PDE_PS = 0,则直接用pArr的PTE替换即可。
- PDE_PS = 1,替换PDE和PTE。
- PDE_P = 1,判断PDE_PS:
- 修改PTE就要清空TLB。
1 |
|