什么是可读,可写,可执行。 线性地址和TLB的关系

2024-03-17 09:58

本文主要是介绍什么是可读,可写,可执行。 线性地址和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的关系的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/818611

相关文章

常用的jdk下载地址

jdk下载地址 安装方式可以看之前的博客: mac安装jdk oracle 版本:https://www.oracle.com/java/technologies/downloads/ Eclipse Temurin版本:https://adoptium.net/zh-CN/temurin/releases/ 阿里版本: github:https://github.com/

POJ1269 判断2条直线的位置关系

题目大意:给两个点能够确定一条直线,题目给出两条直线(由4个点确定),要求判断出这两条直线的关系:平行,同线,相交。如果相交还要求出交点坐标。 解题思路: 先判断两条直线p1p2, q1q2是否共线, 如果不是,再判断 直线 是否平行, 如果还不是, 则两直线相交。  判断共线:  p1p2q1 共线 且 p1p2q2 共线 ,共线用叉乘为 0  来判断,  判断 平行:  p1p

maven 编译构建可以执行的jar包

💝💝💝欢迎莅临我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」👈,「stormsha的知识库」👈持续学习,不断总结,共同进步,为了踏实,做好当下事儿~ 专栏导航 Python系列: Python面试题合集,剑指大厂Git系列: Git操作技巧GO

pip-tools:打造可重复、可控的 Python 开发环境,解决依赖关系,让代码更稳定

在 Python 开发中,管理依赖关系是一项繁琐且容易出错的任务。手动更新依赖版本、处理冲突、确保一致性等等,都可能让开发者感到头疼。而 pip-tools 为开发者提供了一套稳定可靠的解决方案。 什么是 pip-tools? pip-tools 是一组命令行工具,旨在简化 Python 依赖关系的管理,确保项目环境的稳定性和可重复性。它主要包含两个核心工具:pip-compile 和 pip

线性因子模型 - 独立分量分析(ICA)篇

序言 线性因子模型是数据分析与机器学习中的一类重要模型,它们通过引入潜变量( latent variables \text{latent variables} latent variables)来更好地表征数据。其中,独立分量分析( ICA \text{ICA} ICA)作为线性因子模型的一种,以其独特的视角和广泛的应用领域而备受关注。 ICA \text{ICA} ICA旨在将观察到的复杂信号

webapp地址

F:\LSP\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps

jenkins 插件执行shell命令时,提示“Command not found”处理方法

首先提示找不到“Command not found,可能我们第一反应是查看目标机器是否已支持该命令,不过如果相信能找到这里来的朋友估计遇到的跟我一样,其实目标机器是没有问题的通过一些远程工具执行shell命令是可以执行。奇怪的就是通过jenkinsSSH插件无法执行,经一番折腾各种搜索发现是jenkins没有加载/etc/profile导致。 【解决办法】: 需要在jenkins调用shell脚

Jenkins 插件 地址证书报错问题解决思路

问题提示摘要: SunCertPathBuilderException: unable to find valid certification path to requested target...... 网上很多的解决方式是更新站点的地址,我这里修改了一个日本的地址(清华镜像也好),其实发现是解决不了上述的报错问题的,其实,最终拉去插件的时候,会提示证书的问题,几经周折找到了其中一遍博文

Lua 脚本在 Redis 中执行时的原子性以及与redis的事务的区别

在 Redis 中,Lua 脚本具有原子性是因为 Redis 保证在执行脚本时,脚本中的所有操作都会被当作一个不可分割的整体。具体来说,Redis 使用单线程的执行模型来处理命令,因此当 Lua 脚本在 Redis 中执行时,不会有其他命令打断脚本的执行过程。脚本中的所有操作都将连续执行,直到脚本执行完成后,Redis 才会继续处理其他客户端的请求。 Lua 脚本在 Redis 中原子性的原因

✨机器学习笔记(二)—— 线性回归、代价函数、梯度下降

1️⃣线性回归(linear regression) f w , b ( x ) = w x + b f_{w,b}(x) = wx + b fw,b​(x)=wx+b 🎈A linear regression model predicting house prices: 如图是机器学习通过监督学习运用线性回归模型来预测房价的例子,当房屋大小为1250 f e e t 2 feet^