《windows核心编程》–Windows内存体结构(二)

13.6页面保护属性

    内存页面保护属性有 PAGE_NOACCESS、PAGE_READONLY、PAGE_READWRITE、PAGE_EXECUTE、PAGE_EXECUTE_READ、PAGE_EXECUTE_READWRITE、PAGE_WRITECOPY、PAGE_EXECUTE_WRITECOPY。这些保护分别表示什么http://127.0.0.1:47873/help/1-3788/ms.help?method=page&id=09839DB7-2118-4A7D-A707-A08C92BD600C&product=VS&productversion=100&locale=zh-CN&topiclocale=EN-US&topicversion=85&SQM=2

一些恶意软件将代码写入到用于数据的内存区域(比如线程栈上),通过这种方式让应用程序执行恶意代码。windows数据执行保护特性提供了对此类恶意攻击的防护。如果启用了DEP,那么只有对那些真正需要执行的代码的内存区域,操作系统才page_execute_*保护属性。其它保护属性(最常 见的就是PAGE_READWRITE)用于只应该存放数据的内存区域。

 

13.6.1 写时复制(PAGE_WRITECOPY PAGE_EXECUTE_WRITECOPY)

Windows支持一种机制,允许两个以上的质监共享同一块存储器。因此,如果有10个记事本程序正在运行,所有进程会共享应用程序的代码页和数据页。让所有的应用程序实例共享相同的存储页极大的提升了系统的性能。但另一方面,这要求应用程序只能读取其中的数据和代码。如果一个程序修改并写入一个存储页,那么这等于修改了其它实例正在使用的存储页,最终将导致混乱。

为了避免此类错误发生,操作系统会给共享存储页指定写时复制属性。当系统把一个exe或dll遇到 一个地址空间时,系统会计算有多少页是可以写的。

当线程试图写入一个共享页面时,系统会介入并执行以下操作

1. 系统在内存中找一个闲置页面。注意,该闲置页面的后备页面来自页交换文件,它是系统最初将模块映射到进程地址空间时分配的。由于 系统第一次进行映射的时候分配了所有可能需要的页交换文件空间,这一步不可能失败。

2.系统把线程想要修改的页面内容复制到第1步中找到的闲置页面。系统会给该闲置页面指定PAGE_READWRITE或PAGE_EXECUTE_READWRITE保护属性,系统不会对原始页面的保护属性和数据做任何修改。

3. 然后系统更新进程页面表,这样一来,原来的虚拟地址现在就对应到内存中的一个新的页面了

13.6.2  一些特殊的访问保护属性标志

使用这些标志时,只需将它们与除PAGE_NOACCESS之外的任何其它保护属性进行按位 或 操作就可以 了

PAGE_NOCACHE:禁止对已调拨页面进行缓存。不建议将该标志用于除驱动程序以外的程序

PAGE_WRITECOMBINE:也是给驱动开发人员用的。它允许把单个设备的多次写操作组合在一起。

PAGE_GUARD:使应用程序能够在页面中任何一个字节被写入时得到通知。

13.7 实例分析

以应用程序VMPMap.exe来测试

《windows核心编程》–Windows内存体结构(二)

以上列出了一个地址空间映射的实例

  • 左边第一列:基地址 
    从0x00000000区开始,到可用地址空间最后一个区域为止,最后一个区域的起始地址为0x7FFFe0000。所有区域都是连续的。读者可能还会注意到所有非闲置区域 的基地址都是64KB的整数倍。这是由系统地址空间分配粒度来决定的。如果一个区域的基地址不是64KB的整数倍,这意味着该区域是由操作系统以进程的名义分配的。
  • 第二列 区域类型: 
    闲置:区域的虚拟地址没有任何后备存储器。该地址空间尚未预订,应用程序即可以从基地址开始预订区域,也可以从闲置区域的任何地方开始预订区域 
    私有:区域虚拟地址以系统的页交换文件为后备存储器 
    映象:区域的虚拟地址一开始以映象文件(比如.exe和.dll)为后备存储器,但此后不一定以心映象文件为后备存储器。例如:如果程序写入一个映象文件中的一个全局变量,那么写时复制机制会改用页交换文件来作为后备存储器。 
    已映射:区域虚拟地址一开始以内存映射文件为后备存储器,但此后不一定以内存映射文件为后备存储器。例如:内存映射文件可能会使用写时复制保护属性。任何编写操作会使对应的页面改用页交换文件为后备存储器。
  • 第三列 预订字节数 
    此列始终是CPU页面大小的整数倍。为了节省磁盘空间,链接器会尽可能的对所生成的PE文件进行压缩。但是当windows将PE文件映射到进程虚拟地址空间时,每 一段必须另起一页,而且起始地址必须是系统页面大小的整数倍。这意味着PE文件所需的虚拟地址空间的大小一般比PE 文件本身的大小
  • 第四列 所预订区域内块的数量 
    块是一些连续的页面,这些页面具有相同的保护属性,并且以相同的物理存储器为后备存储器
  • 第五列 区域保护属性 
    E = execute R = read W = write
  • 第六列 描述

13.8 数据对齐的重要性

当访问已对齐的数据时,CPU的执行效率才最高。把数据的地址模除数据的大小,如果结果为0,那么数据就是对齐的。例如:一个WORD值的起始地址应该能被2整除,一个DWORD的起始地址应该能被4整除,以此类推。

下面的代码访问了一个错位的数据

VOID SomeFunc(PVOID pvDataBuffer) 

    //the first byte in the buffer is some byte of information 
    char c = * (PBYTE) pvDataBuffer; 
    
    //increase past the first byte in the buffer 
    pvDataBuffer = (PVOID) ((PBYTE)pvDataBuffer + 1); 
    
    //bytes 2 - 5 contain a double-word value 
    DWORD dw =  * (DWORD * ) pvDataBuffer; 
    
    //the line above raise a data misalignment exception on some CPUS 
}

 

CPU处理数据对齐的方式 :

  • x86: 
    x86 CPU的EFLAGE寄存器内有一个AC标志。默认为0。如果为0,那么CPU会自动执行必要的操作来访问必要的数据,否则CPU就会触发INT 17H中断 。由于  x86  CPU从来不改变这个标志,因此应用程序在X86 运行时从来不会发生数据错位异常。当应用程序在AMD x86-64处理器上运行时,会有相同的结果。这是因为在默认情况下CPU处理了数据错位的错误。
  • IA-64: 
    IA-64 CPU不能自动处理数据错位的错误。当任何代码要访问错位数据时,CPU会通知操作系统。Windows然后决定到底是应该抛出数据错位异常,还是应该没有任何提示的执行额外指令来修正错误并让代码继续执行。在IA-64操作系统里,window会自动将将数据错位错误转化成一个EXCEPTION_DATATYPE_MISALIGNMENT异常。但是我们可以通过SetErrorMode来改变这种行为。注意改变这个标志会影响到进程所有的线程,另外值得注意的一点是这个标志会被子进程继承。IA-64版本的vc++ 编译器支持一个__unaligned关键字。下面的代码是前面的代码经过修改代码的版本。新版使用了__unaligned关键字 

    VOID SomeFunc(PVOID pvDataBuffer) 

        //the first byte in the buffer is some byte of information 
        char c = * (PBYTE) pvDataBuffer; 
        
        //increase past the first byte in the buffer 
        pvDataBuffer = (PVOID) ((PBYTE)pvDataBuffer + 1); 
        
        //bytes 2 - 5 contain a double-word value 
        DWORD dw =  * (__unaligned DWORD * ) pvDataBuffer; 
        
        //the line above raise a data misalignment exception on some CPUS 
    }

上一篇:恢复快速启动中的“显示桌面”按钮


下一篇:7 Papers | 谷歌开源V-MoE代码;FAIR等纯卷积ConvNet,反超Transformer