本文主要是介绍什么是可读,可写,可执行。 线性地址和TLB的关系,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
C/C++的编程过程中应该都遇到过 0xC0000005,访问权限异常,当访问没有权限访问的页时候就会出现这个问题
经过这一段时间的学习,我发现我对可读可写可执行有了不一样的理解,从汇编层面
mov ds:[0x12345678],eax 是把eax的值存放到 0x12345678线性地址对应的物理地址 这个线性地址对应的物理页既是可写
mov eax,ds:[0x22222222] 这个线性地址0x22222222对应的物理页既是可读
想要理解什么是可读可写可执行首先需要理解页机制
而可执行的本质是所有对EIP寄存器修改,(既然是读EIP的修改肯定会给EIP一个线性地址)CPU必须对这个线性地址进行一些列的检查,比如如果这个线性地址
连物理页都没有肯定不能让你执行,返回一个0xC0000005异常,如果这个线性地址挂上了物理页,但是这个物理页没有执行权限属性,那也不能让你执行(xp中的vc++6.0的
数组可执行)是因为vc++6.0没有DEP(数据执行保护),而win10中的vs2017的不管是全局还是局部对应的物理页都没有执行权限
看似一条简单的指令在执行过程中需要检查的地方非常多
可写权限:上面的mov ds:[0x12345678],eax 在执行的时候需要 检查0x12345678线性地址是有有物理页MmIsAddressValid, 如果没有物理页执行int e中断,判断是不是缺页,不是缺页返回一个0xC0000005(前提是Ring3,ring0就蓝了)缺页就把交换到文件中的页交换回来,如果没有缺页,这个线性地址有物理页的话,需要判断这个物理页是否有写的属性,
如果有写的属性才能把eax的值写入到 这个线性地址对应的物理页中,这条指令才算执行成功,而你在执行这条指令前,需要把EIP指向这条指令所在的地址,这时候需要判断这指令所在的地址所在的物理页有没有物理页,且有没有可执行权限只要有一个没有权限就挂了
可读权限:只要你的线性地址挂了物理就一定有可读权限
可读可写可执行:首先都是检查一个线性地址是否合法,如果是2-9-9-12分页需要通过这个线性地址对应的PDE PTE 在访问对应的物理页,需要访问三次内存,这还是没有任何缺页的情况,没有访问的地址在不同PDE和PTE上的情况,否则需要访问更多次内存。
这样的话问题就来了,如果CPU没执行一条指令的话都必须检查这么多,如很多时候执行一段段程序(很多时候指令是连续的)每个执行一条指令都对线性地址都检查是否可执行,
这样效率低了因为很多时候指令地址都是在同一个物理页上的,于是CPU内部设置了一个TLB结构,TLB项纪录了当前进程的线性地址与之对应的物理页
TLB是CPU中的一张表
一般都有如下4组TLB
第一组,缓存一般页表(4KB字节页面)的指令页表缓存(Instruction-TLB)
第二组,缓存一般也表(4KB字节页面)的数据页表缓存 (Data _TLB)
第三组,缓存大尺寸页表(2M/4M字节页面)的指令页表缓存
第四组,缓存大尺寸页表(2M/4M字节页面)的数据页表缓存
TLB一项如下 纪录了线性地址,这个线性地址与之对应的物理页地址, 和这个物理页的属性,并且纪录这这个物理页访问了多少次
LA(线性地址) PA(物理地址) ATTR(属性) LRU(统计)
通过4组TLB可以发现,4KB页面有指令页缓存和数据页缓存,非常巧妙
有了TLB后如果我们所执行一段指令,首先取第一条指令的线性地址,在(4kb字节页面)的指令TLB 指令TLB中的项比较 比较LA(线性地址)如果找到了一个项与当前需要指令的线性
地址高20位相同的项(就说明这两个线性地址在同一个物理页(一个页4KB 线性地址的低12为页内偏移)),当找到高20线性地址相同的项,就不用在于拆分这个线性地址,判断有没有物理页啊,有没有权限,找到了高20位相同的项,此时比较ATTR是否有可执行属性没有就挂。如果没有指令缓存TLB中找到就在查找内存,看没有有挂上物理页,通过PTE对比有没有访问权限,如果有了就把这个线性地址和物理地址,以及页的属性做成一个记录写入指令缓存TLB中,执行下一面很大一段指令的时候很大概率就不用查找内存了,线性地址高20相同的直接查记录就ok了。
注意把记录写入的是指令缓存TLB之前是会改变EIP(这个也是可执行的本质)每一个指令周期都会改变EIP,为下一个条指令取指做准备 要改变EIP的值,得要给EIP一个地址(
但是这个地址不一定有效的必须要检查 ,就算有效还要检查有没有执行权限(这个是因为安全))jcc指令,ret call jmp(jcc)等等指令执行也都会改变eip,那你给改成0那就挂了啊
所以修改eip值的时候如果没找到记录,且检查了是有效的,就把这个值和物理页,属性全都写入指令缓存TLB(为后续可能要执行同一个页的数据)
数据缓存TLB也是相同的原理不过是通过mov访问内存,push pop (访问的就是esp中存储的线性地址)等等 把线性地址如果没有到数据缓存TLB中找到高20位相同的线性地址就,拆分这个地址是否有物理页,和对应物理页的权限,访问成功了就把这个线性地址和物理地址等信息加载到数据缓存TLB中
画了很大的力气想讲清楚可执行,相比于可读可写,可执行好像模糊一些,更难以让人理解一些
数据TLB和指令TLB都是这样的,如果满了会根据LRU(同级记录把访问最少的物理页删了(这个物理页的G位不为1)) ,G位为1的物理页不删
当进程切换的(就是CR3寄存器的值发送改动的时候)会把所有组TLB中物理页G位不为1的全都删了
验证TLB存在
首先开一个进程TBL Test开辟了两个页的内存,分别存储了06 和09并通过windug获得了
0x10000000 线性地址的PTE 1ee39067 存储的都是6
0x00A50000 线性地址的PTE 1f17a067 存储的都是9
#include "stdafx.h"
#include <Windows.h>
int Temp=0;
void _declspec(naked) taolaoda(){
__asm{
mov dword ptr ds:[0xC0000000],0x1f17a067 //0x390000 09
mov dword ptr ds:[0x0],0x11111111
#if 0
mov eax,cr3
mov cr3,eax //改变CR3的值
#endif
mov dword ptr ds:[0xC0000000],0x1ee39067 //0x10000000 06
mov eax,dword ptr ds:[0]
mov Temp,eax
retf
}
}
int main(int argc, char* argv[])
{
char buf[6];
*((WORD*)&buf[4])=0x48;
printf("%p\n",taolaoda);
__asm{
call fword ptr buf; //调用们我构建好了没写出来
}
printf("Temp=%p\n",Temp);
getchar();
return 0;
}
下面可以看到没有改变CR3寄存器,第一次访问0x0线性地址的时候会拆分这个地址并判断有没有写入权限,(067这个页是可读可写的),此时会往数据缓存TLB
加入一条记录有线性地址0与对应的物理页 0x1f17a000 的物理页的属性可读可写,访问次数
下面在访问这个0线性地址的时候,不会在拆分这个地址了能够在数据缓存TLB中找到记录,且是可读的有权限,会直接从0x1f17a000这个页开始读取4个字节 读取到的0x11111111前面写入的,因为第一次写入数据缓存TLB加入了记录,所以那个第二次给0线性地址挂上了另外的一个物理页,但是实际上操作的还是上一个物理页
修改CR3刷新TLB
mov cr3,eax刷新了cr3,此时相当于切换了进程(当然还是同一个进程,只要往cr3寄存器写入数据cpu就以为是切换进程就会清理TLB所有中G=0的项)
这里可以看到,第一次往0写入0x11111111后刷新了cr3,所以记录清除了在此挂上另外一个物理页(这个物理页每个字节存储的都是0x06)读取的就是0x06060606
把G位修改成1,可以看到当把G位修改成1后,发现修改cr3刷新TLB由于0x1f17a000物理页的G=1所以没有刷新,然后再次访问0线性地址,在数组缓存TLB中查找到了记录
记录的物理页是0x1f17a000实际上已经是0x1e39000,读取的还是0x1f17a000,所以读取的是0x11111111
上面这三个实验可以证明TLB存在了
在另外开的那个进0x390000线性地址对应的物理页前4个字节已经修改成了0x11111111,我在那个进程反复刷新还是现实0x09090909就是因为有记录,
通过INVLPG指令可以在TLB中清除一条记录,哪怕G=1的全局页都可以清理, 可以看到 两个页的PTE重新挂了不影响
第一个给0线性地址挂上的一个全局页,cr3刷新刷不掉,然后通过INVLPG删了这0线性地址的记录,然后在给0地址挂上了一个0x06687000物理页,
然后读取0地址的内存,(拆分的时候没有记录)会添加一个记录
通过上述实验就已经可以证明TLB存在了
希望大家能够结合TLB把可读可写可执行,以及比较过程理解清除
这篇关于什么是可读,可写,可执行。 线性地址和TLB的关系的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!