什么是可读,可写,可执行。 线性地址和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

相关文章

python 线程池顺序执行的方法实现

《python线程池顺序执行的方法实现》在Python中,线程池默认是并发执行任务的,但若需要实现任务的顺序执行,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋... 目录方案一:强制单线程(伪顺序执行)方案二:按提交顺序获取结果方案三:任务间依赖控制方案四:队列顺序消

Go语言连接MySQL数据库执行基本的增删改查

《Go语言连接MySQL数据库执行基本的增删改查》在后端开发中,MySQL是最常用的关系型数据库之一,本文主要为大家详细介绍了如何使用Go连接MySQL数据库并执行基本的增删改查吧... 目录Go语言连接mysql数据库准备工作安装 MySQL 驱动代码实现运行结果注意事项Go语言执行基本的增删改查准备工作

Springboot项目构建时各种依赖详细介绍与依赖关系说明详解

《Springboot项目构建时各种依赖详细介绍与依赖关系说明详解》SpringBoot通过spring-boot-dependencies统一依赖版本管理,spring-boot-starter-w... 目录一、spring-boot-dependencies1.简介2. 内容概览3.核心内容结构4.

Linux查询服务器 IP 地址的命令详解

《Linux查询服务器IP地址的命令详解》在服务器管理和网络运维中,快速准确地获取服务器的IP地址是一项基本但至关重要的技能,下面我们来看看Linux中查询服务器IP的相关命令使用吧... 目录一、hostname 命令:简单高效的 IP 查询工具命令详解实际应用技巧注意事项二、ip 命令:新一代网络配置全

Java中数组与栈和堆之间的关系说明

《Java中数组与栈和堆之间的关系说明》文章讲解了Java数组的初始化方式、内存存储机制、引用传递特性及遍历、排序、拷贝技巧,强调引用数据类型方法调用时形参可能修改实参,但需注意引用指向单一对象的特性... 目录Java中数组与栈和堆的关系遍历数组接下来是一些编程小技巧总结Java中数组与栈和堆的关系关于

解密SQL查询语句执行的过程

《解密SQL查询语句执行的过程》文章讲解了SQL语句的执行流程,涵盖解析、优化、执行三个核心阶段,并介绍执行计划查看方法EXPLAIN,同时提出性能优化技巧如合理使用索引、避免SELECT*、JOIN... 目录1. SQL语句的基本结构2. SQL语句的执行过程3. SQL语句的执行计划4. 常见的性能优

Spring Bean初始化及@PostConstruc执行顺序示例详解

《SpringBean初始化及@PostConstruc执行顺序示例详解》本文给大家介绍SpringBean初始化及@PostConstruc执行顺序,本文通过实例代码给大家介绍的非常详细,对大家的... 目录1. Bean初始化执行顺序2. 成员变量初始化顺序2.1 普通Java类(非Spring环境)(

Spring Boot 中的默认异常处理机制及执行流程

《SpringBoot中的默认异常处理机制及执行流程》SpringBoot内置BasicErrorController,自动处理异常并生成HTML/JSON响应,支持自定义错误路径、配置及扩展,如... 目录Spring Boot 异常处理机制详解默认错误页面功能自动异常转换机制错误属性配置选项默认错误处理

如何在Java Spring实现异步执行(详细篇)

《如何在JavaSpring实现异步执行(详细篇)》Spring框架通过@Async、Executor等实现异步执行,提升系统性能与响应速度,支持自定义线程池管理并发,本文给大家介绍如何在Sprin... 目录前言1. 使用 @Async 实现异步执行1.1 启用异步执行支持1.2 创建异步方法1.3 调用

Spring Boot Maven 插件如何构建可执行 JAR 的核心配置

《SpringBootMaven插件如何构建可执行JAR的核心配置》SpringBoot核心Maven插件,用于生成可执行JAR/WAR,内置服务器简化部署,支持热部署、多环境配置及依赖管理... 目录前言一、插件的核心功能与目标1.1 插件的定位1.2 插件的 Goals(目标)1.3 插件定位1.4 核