如何让 32 位程序突破 2G 内存限制
一般情况下,32 位程序的内存大小被限制在了 2G,不过可以通过以下的操作来突破这个限制。
修改操作系统参数
这一步骤只针对 32 位操作系统,64 位操作系统可以跳过
用管理员权限打开一个命令行窗口
执行 bcdedit.exe /set pae forceenable
使系统可以识别大于 4G 的内存
执行 bcdedit.exe /set increaseuserva 3072
使用户态内存大小从原来的 2G 增加到 3G
重启电脑
修改 32 位程序属性(headers)
用管理员权限打开一个命令行窗口
执行 editbin.exe /largeaddressaware <your_application>.exe
这样,这个修改过的 32 位程序就可以使用超过 2G 的系统内存了。
注意:
editbin.exe 一般位于 Visual Studio 的安装目录中,比如在 Visual Studio 2010 的目录下: C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\bin\amd64\editbin.exe
执行完 editbin.exe 之后,最好再使用 dumpbin.exe 来验证修改是否生效
a. 命令行执行: dumpbin.exe /headers <your_application>.exe | more
b. 在程序的输出中,如果可以看到 Application can handle large (>2GB) addresses, 则说明修改已经生效
虽然程序修改之后可以使用的内存总数超过了 2G,但是每次申请内存的大小限制还是 2G,所以如果程序中尝试一次性申请大于 2G 的内存,还是会失败,不过一般也不会有一个 32 位的程序会一次性申请这么大的内存。
/************************************************************
如何修改EXE程序的栈大小?
在实际使用中,我们常常碰到程序停止工作的情况。有时候是因为数据规模较大,栈溢出造成的。本经验介绍直接修改EXE文件的栈大小的办法。
工具
-
editbin.exe(如果安装过Visual Studio,这个是自带的,否则自行下载)
方法
-
首先我们以一个如图所示程序为例。这个小程序解析地址并列出一个很长的信息列表。但是有时候信息条数过多,就出现了如图所示情况。
-
我们可以使用x64dbg/x32dbg等调试器调试该程序,发现运行时报错为EXCEPTION_STACK_OVERFLOW,就是栈溢出了。原因是该程序的栈较小。
-
在文件夹中按下Shift点击右键,在右键菜单中点击在此处打开PowerShell窗口/在此处打开命令行窗口
-
在打开的命令窗口中输入editbin,回车可以看到使用方法。期中/STACK可以修改程序栈大小。(如果没有该程序,请自行搜索下载)
-
通过命令
editbin /STACK 10进制栈字节数 被编辑文件的路径/文件名.exe
修改栈大小,如图表明修改成功。
/************************************************************
以前的文章中分析过软件的内存模型(链接点击这里),从中我们可以知道,程序运行起来以后,它将有自己独立的虚拟地址空间。这个空间的大小与操作系统的位数有关。目前使用C++开发的PC端软件一般都是开发32位的。那么32位的软件,最大寻址空间也就是4GB。其中很重要的一部分是给系统内核使用的内核空间。在32位的windows操作系统中,高地址的2GB空间就是给内核使用的内核空间,用户程序空间只有2GB;而32位的linux内核空间只有1GB大小,也就是用户空间有3GB大小。我们也引用以前的图:
其中,我们用户自己的空间其实只有2G左右。但是在实际过程中,我们很可能遇到这样的问题,申请内存不够,导致程序运行失败,更严重的直接崩溃了。
在我们实际应用中,我们还真遇到了这样的问题。我们有这样一个需求,就是对图片进行内存级别的翻转,这时候如果图片较大,内存反转也就需要很多的连续内存空间。很多时候,程序就这样崩溃了。
一直以为解决方案就是压缩图片,或者升级为64位的程序。偶然的时机,看到同事的文章,就是讨论如何让32位程序使用4G内存。觉得特别有意义,这里也算是记录一下。
我们在使用VS开发的过程中,一直忽略了项目配置的一个属性,就是 LARGEADDRESSWARE。这个就是解决问题的关键点。
1.对于 VS项目:
右键->属性->配置属性->链接器->系统->启用大地址->是(/LARGEADDRESSWARE) 进行开启大地址:
2.对于 QT 项目:
在配置文件.pro中添加下面的编译选项,进行开启大地址:
QMAKE_LFLAGS_WINDOWS += /LARGEADDRESSAWARE
1
3.其实不管使用什么IDE编译的程序,都可以使用VS的editbin工具打开大地址选项。
1.打开VS命令行工具,cd到exe的目录下:
1
editbin /largeaddressaware xxx.exe
1
4.接下来我们怎么验证一下当前程序是否启用了大地址呢?
这时候,我们可以借用VS命令行工具,editbin命令来进行检测。
1.首先打开VS命令行工具:
2.进入生成的可执行文件的目录
3.使用dumpbin命令进行检测:
dumpbin /headers xxx.exe
1
我们先看一下没有启动大地址的程序是什么样子的:
主要是红框里面的内容,用来标识。上面的是没有启用大地址的内容。
我们按照上面提到的方式,启用大地址之后看一下结果:
我们可以看到信息增加了一行:“Application can handle large (>2GB) address”。也就是程序可以操控大于2GB的地址。这也就达到了我们开启大地址的结果。
32位的程序开启了大地址,可以降低由于程序申请大内存失败导致的崩溃。有效提高了软件的稳定性。
/***************************************
PC端C++软件, 开启大地址,让32位程序使用4G内存
我在以前的文章中分析过软件的内存模型(链接点击这里),从中我们可以知道,程序运行起来以后,它将有自己独立的虚拟地址空间。这个空间的大小与操作系统的位数有关。目前使用C++开发的PC端软件一般都是开发32位的。那么32位的软件,最大寻址空间也就是4GB。其中很重要的一部分是给系统内核使用的内核空间。在32位的windows操作系统中,高地址的2GB空间就是给内核使用的内核空间,用户程序空间只有2GB;而32位的linux内核空间只有1GB大小,也就是用户空间有3GB大小。我们也引用以前的图:
其中,我们用户自己的空间其实只有2G左右。但是在实际过程中,我们很可能遇到这样的问题,申请内存不够,导致程序运行失败,更严重的直接崩溃了。
在我们实际应用中,我们还真遇到了这样的问题。我们有这样一个需求,就是对图片进行内存级别的翻转,这时候如果图片较大,内存反转也就需要很多的连续内存空间。很多时候,程序就这样崩溃了。
一直以为解决方案就是压缩图片,或者升级为64位的程序。偶然的时机,看到同事的文章,就是讨论如何让32位程序使用4G内存。觉得特别有意义,这里也算是记录一下。
我们在使用VS开发的过程中,一直忽略了项目配置的一个属性,就是 LARGEADDRESSWARE。这个就是解决问题的关键点。
1.对于 VS项目:
右键->属性->配置属性->链接器->系统->启用大地址->是(/LARGEADDRESSWARE) 进行开启大地址:
2.对于 QT 项目:
在配置文件.pro中添加下面的编译选项,进行开启大地址:
QMAKE_LFLAGS_WINDOWS += /LARGEADDRESSAWARE
1
3.其实不管使用什么IDE编译的程序,都可以使用VS的editbin工具打开大地址选项。
1.打开VS命令行工具,cd到exe的目录下:
1
editbin /largeaddressaware xxx.exe
1
4.接下来我们怎么验证一下当前程序是否启用了大地址呢?
这时候,我们可以借用VS命令行工具,editbin命令来进行检测。
1.首先打开VS命令行工具:
2.进入生成的可执行文件的目录
3.使用dumpbin命令进行检测:
dumpbin /headers xxx.exe
1
我们先看一下没有启动大地址的程序是什么样子的:
主要是红框里面的内容,用来标识。上面的是没有启用大地址的内容。
我们按照上面提到的方式,启用大地址之后看一下结果:
我们可以看到信息增加了一行:“Application can handle large (>2GB) address”。也就是程序可以操控大于2GB的地址。这也就达到了我们开启大地址的结果。
32位的程序开启了大地址,可以降低由于程序申请大内存失败导致的崩溃。有效提高了软件的稳定性。
/************************************************************
32位程序超过2G内存空间寻址问题
由于在32位处理器架构下,对内存的访问限制在4GB以下的空间。为了突破 4GB的限制,现在的32位至强处理器采用一种叫PAE(物理地址扩展)的技术,来实现对超出4GB空间的物理地址的访问。PAE实际上采用了36位的地址总线,这样理论上可以支持64GB内存空间的寻址。
PAE使得处理器可以支持访问更多的内存空间,但是这还需要操作系统的支持。由于操作系统本身也是32位的,所以需要在操作系统上做相应的处理来支持处理器。Windows系统采用AWE(Address Windowing Extension)来进行处理。具体结构如图2所示。此外,Linux系统在2.4内核以后也支持PAE和超过4GB的内存空间处理。
32位系统,地址空间是4G。这个没错。用户空间指的是应用程序可以直接访问的地址,系统空间指的是应用程序不能直接访问,必须转到内核模式,由操作系统访问。任何操作系统都分用户空间和系统空间,Unix, Linux都是。
Windows95, Windows 98, WindowsME系列2G用户空间 + 2G系统空间, 2G的系统空间中含有多个进程共享内存的空间和Win16的程序的代码,这就是Win9x系列容易死机的原因之一。
WinNT, Win2000, WinXP系列2G用户空间 + 2G系统空间.(没有Win9x系列的进程共享内存等等)
那么是不是我启动10个应用,那操作系统占有了10*2G这么多空间?
首先,要搞清楚内存和地址空间的区别。内存是内存,地址空间是地址空间(太累了,我用memory代替内存,address space代替地址空间吧). 不管你有多少memory,256,还是512M,
32位系统的每个应用都有4G的地址空间(只不过它不能访问上2G罢了).win2000下,你的程序最多可以使用2G空间,并不是说你还就可以分配2G的内存了,一般情况下,内存分配到
一个值是会失败的。比如,你的物理memory = 256M,你的页交换文件大小=256M,那么你分配个516M内存看看,失败!物理内存和页交换文件加起来才512M呢.
其次,操作系统真的占有了2G的内存吗?也不是。你的程序启动后,操作系统的代码和数据映射到程序的上面2G的空间,只是映射,不是说再启动一个实例放那儿。操作系统要映射一片内存的内容到一个区域是多简单的事情。操作系统一般把系统代码和数据映射到应用程序的上面2G的同一个地方。2个应用程序,它的上面2G空间的代码数据大致上一样(不完全一样,因为还是有些和应用程序相关信息,不过应用程序访问不到,得由系统通过内核态访问).严格说来,下面2G也不是应用程序都能用,0到64K这个空间就不能用,至于为什么,回到DOS时代吧!如果没搞多DOS那就不必要深究了。总之这是故意的。你的指针指到这儿肯定会出错,读都不能读。故意不让你读!
哈哈。
其实大多数应用程序比如你用VC写个程序,它的入口地址是从4M开始,也就是0x00400000,你看看 yourApp.hInstance的值吧!它就等于0x00400000.越讲越远了,
为什么hInstance是程序的入口地址,跑题了!别扔砖头啊!
那么应用程序怎么申请大于2GB的空间呢?
办法1:就是上面的朋友提过的 通过 /3GB或者/USERVA开关。
boot.ini 里头加上这个开关,系统会让出1G给应用程序访问。系统自己只用1G。这样子不是什么好办法。微软临时的方案而已。应用程序link时加上 LARGEADDRESSAWARE,在生成EXE的时候设置一个标志位,这个位系统看到了就让你访问 3G的空间(但是内存+页交换太小,你分配一样会失败).
/3GB指定3G,/USERVA指定一个值,不是死的3G.
办法2:通过AWE (Address Window Extension),地址窗扩展
窗?是的,窗。你可以人认为是一个buffer,用来作为中间物,给2边的东西倒腾。
socket接受发送数据也有窗的概念,一样的。
要细看AWE,你到MSDN上查关键是AWE或者Address Window Extension。Google上Goole一把。你有了2G甚至更多的内存(啥机器都有,你不能怀疑一个32位系统装个20G内存会有问题吧?硬件可以做的)可以这么干。没有的话就别这么做了,没什么用.AWE的原理很简单,你在比如4G的物理内存上,分配的一片物理内存X, 你再分配的一片虚拟内存(地址空间)Y,把 X 和 Y 通过address window映射。你可以在这片虚拟内存里头访问4G的物理内存。最多访问多少要看你的参数设置了。
AllocateUserPhysicalPages分配物理内存, VirtualAlloc申请虚拟内存,MapUserPhysicalPages进行映射。没什么难的.MSDN有个sample做这个事情,看看吧! 这么多关键字了应该好查到了。代码我不贴了 ,占篇幅。
顺便说一下,内存文件映射并不把什么映射到(2~4G)这范围。它是进程共享数据的方式,并不是扩大内存的方式。不过如果你有个大硬盘,创建这么个映射来保存/修改数据也是可以的,这不要求大的内存。和内存没关系。
/***************************************************************
C++使用LargeAddressAware扩展程序内存地址空间
一般情况下,无论在32位系统还是64位系统下,一个Win32用户进程可用的内存空间只有2GB少一点。那么,假设一个Win32程序内存不够用了,又不想(或者没办法)编译成X64的,怎么办呢,MS提供了一种方法,Linker中的LargeAddressAware参数。
启用它,可以获得额外的用户态内存。我在自己的X64系统下测试,如果没有启用,可以申请2GB不到;但是启动了这个参数,则可以申请将近4GB(因为还要受32位程序32位指针的限制):
程序很简单,如下:
[cpp] view plain copy
- int _tmain(int argc, _TCHAR* argv[])
- {
- int c = 0;
- while (true) {
- void * p = malloc(1024 * 1024 * 32);
- if (p != 0) {
- c++;
- printf("alloc %d MB OK\n", c * 32);
- } else {
- break;
- }
- }
- printf("alloc %d MB Totally\n", c * 32);
- getchar();
- return 0;
- }
运行结果是
该程序如果不设置LargeAddressAware参数的话是1986MB。
我自己没有测试32位的系统,但是查到的资料说需要设置/3GB的启动参数。
对于已经存在的程序(没办法编译),可以使用VC中带的editbin.exe工具把该标志位打开:
这样这个程序也可以使用超过2GB的内存了。
同样对于一个exe,可以使用dumpbin.exe来验证。
如果dumpbin.exe /headers 文件名.exe
显示 Application can handle large (>2GB) addresses
那么说明该应用程序可以使用超过2GB的内存
当然这种方法的缺点:
1. 在64位系统上没办法使用超过4GB的内存;不过以前只能用2G,聊胜于无了;
2. 在32位系统上必须打开/3GB启动参数,为用户态程序预留3GB的内存
3. 对于带有自校验的程序,不适用,因为dumpbin相当于修改了这个exe,那么文件校验肯定是失败了;
总之最终的解决方法还是使用(或者编译出)X64的程序。
/*************************************************************************************
x64机器上的x86 LARGEADDRESSAWARE程序的内核模式内存大小?(Kernel Mode memory size for an x86 LARGEADDRESSAWARE program on an x64 machine?)
首先,我们来看看32位Windows。 每个进程的虚拟地址空间都有一定的部分分配给进程本身,而某个部分则分配给内核需要的部分。 但是,所有进程共享相同的内核内存 - 您甚至在自己的虚拟地址空间中拥有内核内存这一事实基本上是一种性能优化,以避免在处理应用程序中的内核对象和数据时切换地址空间。
默认情况下,这是1:1分割,因此您将获得2 GiB的用户地址空间和2 GiB的内核地址空间。 早期的32位Windows软件使用(ab)(当你的计算机可能只有4 MiB的内存,486 CPU或类似内存时),因为内存布局的方式,你的用户地址空间从未在2 GiB障碍之上有任何指针 - 有效地为您自己的数据提供任何指针的最高位。 通常这用于允许混合“如果它适合,这是一个值,否则它是指向结构的指针”方法,节省内存和一点间接性。 由于这种情况如此广泛,因此默认情况与早期版本相同,以防止出现兼容性问题。 但是,您还可以选择使用不同的拆分 - 3 GiB的用户空间和1 GiB的内核空间。 这是/3GB
选项的作用。 但这还不够 - 您的应用程序还必须选择使用/LARGEADDRESSAWARE
。 这基本上说“我不会用我的指针做奇怪的东西”。
应该注意的是,32位操作系统或进程并不一定意味着你只能处理4 GiB的内存 - 它只是限制了CPU在任何时候都可以直接访问的内存。 对于内存密集型服务器软件,即使是“32位”版本也可能支持寻址更多内存 - 例如,32位MS SQL Server通过AWE支持最多64 GiB。 这基本上是另一层虚拟化,它允许重新映射虚拟地址的物理地址。 从理论上讲,无论是否使用AWE,您都可以解决的内存量没有限制 - 毕竟,没有什么能阻止您使用自己的硬件充当内存映射文件,从而有效地为您提供无限的地址空间。 当然,就像分段内存的日子一样,它不是很容易使用或实用:)
在64位Windows上, /3GB
不再有任何意义而被忽略。 默认地址空间拆分取决于Windows的确切版本,但是在“TB级以上”范围内,超出了32位限制。 对于现代Windows,这通常是128 TiB用户+ 128 TiB内核。 32位应用程序仍然必须像以前一样使用/LARGEADDRESSAWARE
。 但是,由于内核现在是64位,因此它无法与用户进程处于相同的地址空间,因此64位操作系统上的32位应用程序可以完全访问4 GiB的地址空间。
当然,这些限制仍远低于理论上能够解决的64位。 但是,大多数64位CPU实际上无法处理整个64位地址空间 - 最后一次检查时最常见的只是48位。 令人惊讶的是,它给你带来了256 TiB的地址空间,这是Windows的极限。 毕竟不是微软的阴谋! :)实际上,这不是什么新东西。 英特尔x86的32位ALU与32位地址空间相关联这一事实在CPU历史中非常突出 - CPU通常具有比ALU宽度更高和更低的地址空间(用于虚拟寻址或物理寻址)宽度。 MS DOS典型限制为1 MiB的可寻址存储器(剩余640 kiB留给用户应用程序)也来自于此 - 当时的“32位”CPU只能使用20位地址。
/******************************************************************************************
windows虚拟内存机制
在windows系统中个,每个进程拥有自己独立的虚拟地址空间(Virtual Address Space)。这一地址空间的大小与计算机硬件、操作系统以及应用程序都有关系。
对于32位程序来说,最多能使用2GB空间(0x00010000-0x7FFEFFFF)。为了获得3GB的地址空间,在不同的windows系统中可以按照如下方法来进行扩充。
1.操作系统方面
① 32位windowsXP
② 32位win7 -- 管理员权限执行命令:bcdedit /set increaseuserva 3072来开启
③ 64位win7 -- 对32位程序默认开启3GB,无需额外设置
2.应用程序方面
无论是32位还是64位windows若要让32位程序能使用3GB内存,必须在链接时加上参数: /LARGEADDRESSAWARE
进程地址空间区段
注:进程地址空间在低地址,操作系统内核在高地址
进程地址空间分布(以2GB为例)
Windows系统在进程空间中专门划出一块0x70000000 - 0x80000000(共256MB)区域,用于映射这些常用的系统DLL(如kernel32.dll、ntdll.dll等)
对系统DLL的默认基地址进行调整,防止加载时冲突,触发ReBasing(重定基地址)
注:基地址必须对齐到分配粒度(64KB)
Win7下,exe在PE文件中基地址为0x400000,DllPrj.dll的基地址为0x10000000且该地址未被其他dll占用;但实际exe被映射到0xEC0000,DllPrj.dll被映射到0x535A0000
生成exe和dll模块时,链接时使用了参数/DYNAMICBASE(启用动态基地址)
注:地址空间布局随机化, Address space layout randomization (ASLR):防范恶意程序对已知地址进行攻击
windows内存分配过程可细化为以下3个要点:
① 保留一段虚拟内存地址空间:从进程的4GB中保留一段地址空间。// 带MEM_RESERVE参数的VirtualAlloc函数
起始地址必须是系统分配粒度的整数倍(64KB),大小必须是系统页面大小的整数倍(4KB)。
② 提交一段虚拟内存地址空间:将进程已保留的一段地址空间映射机器的虚拟内存上。// 带MEM_COMMIT参数的VirtualAlloc函数
起始地址和大小都必须是页面大小的整数倍(4KB)。
③ 将虚拟内存地址空间映射到物理内存页(RAM):在访问进程提交的页面被访问时,通过缺页中断(又名页缺失、页面错误, PageFault)机制来真正分配物理内存页,同时修改对应页面的地址空间映射关系。
注1:在程序中所访问的地址都必须是保留并提交的虚拟内存地址
注2:可以使用VirtualFree来释放保留或提交的虚拟内存地址空间
内存指标概念
Total = Image + Mapped File + Shareable + Heap + Managed Heap + Stack + Private Data + Unusable
Image:exe、dll等可执行模块的代码段、数据段等
Mapped File:作为数据载入的内存映射文件
Shareable:进程间共享内存、消息等
Heap:malloc()、new、HeapAlloc()、LocalAlloc()创建出来的私有内存,由用户态堆管理器统一管理
Managed Heap:由GC管理的私有内存
Stack:线程栈
Private Data:由VirtualAlloc()创建出来的私有内存
Page Table:分配在内核态的页表
Unusable:起始地址以64KB对齐,导致一些无用的空闲内存地址空间
Free:空闲内存地址空间
-------------------------
Blocks:拥有内存块的个数
Largest:所有内存块中最大一个的size
虚拟内存:
Private Bytes // 进程Committed的虚拟内存字节数 对应VMMap的Private、win7任务管理器中的【提交大小】,资源管理器中的【提交】
Peak Private Bytes // 进程Committed的虚拟内存的最高峰字节数
Virtual Size // 进程Reserved的虚拟地址空间字节数
Page Faults // 发生过的缺页中断次数 对应win7任务管理器中的【页面错误】
物理内存:
Working Set = WS Private + WS Shareable // 进程占用物理内存总字节数 对应win7任务管理器中的【工作设置(内存)】,资源管理器中的【工作集】
WS Private // 进程独享的物理内存字节数(如:堆内存+栈内存+cow机制创建的内存) 对应win7任务管理器中的【内存(专用工作集)】,资源管理器中的【专用】
WS Shareable // 进程可与其他进程共享的物理内存字节数(如:exe及dll代码段、数据段等) 对应win7资源管理器中的【可共享】
WS Shared // 进程已与其他进程共享的物理内存字节数,WS Shared<=WS Shareable
// 若只启动一个exe实例,那么exe的代码段、数据段等不会被共享,因而就不统计在WS Shared中
Peak Working Set // 物理内存的最高峰字节数 对应win7任务管理器中的【峰值工作设置(内存)】
注:无论是虚拟内存还是物理内存下的各个指标,都是通过统计用户态的那部分占用
整个系统的物理内存和虚拟内存占用:
内存计数条(Counter)的整个高度代表了当前电脑整个物理内存RAM的大小(16GB),绿条的高度代表了所有进程所消耗的物理内存总和(7.36GB)
-- 物理内存(MB)的区域 --
总数:当前电脑整个物理内存RAM的大小(16312MB)
已缓存:Windows系统将没有占用的物理内存来做缓存的部分(8786MB),这块绝大部分可被释放出来以供使用
可用:可被使用的物理内存(8771MB),当再起一个进程,可以占用的物理内存
空闲:完全空闲的物理内存(45MB)
进程消耗的物理内存(7.36GB) + 已缓存(8786MB) + 空闲(45MB) = 总数(16312MB)
-- 系统的区域 --
提交(GB):10 / 31
31GB为物理内存和分页文件的总大小
10GB为所有进程占用的Committed虚拟内存大小
页交换文件
页交换文件(Page File):一般被用作可写物理内存页的后备存储器。Windows下该文件名为pagefile.sys,位于各盘的根目录中。
可以根据机器的软硬件状况来设置页交换文件的大小,甚至关闭页交换文件的使用。
页出(Page Out):当物理内存不够时,系统会将一些不经常使用且有后备的物理内存页释放,并将虚拟地址映射关系指向后备。
①以页交换文件(如:堆、栈等)为后备:在页交换文件中分配空间,并拷贝内容到其中后再释放
②以内存映射文件(如:exe、dll等)为后备:直接释放
页入(Page In):当系统读取某个虚拟内存地址,而该地址所在的页不在物理内存页中时,将产生一个缺页中断,
告诉系统从页交换文件或者内存映射文件中取回包含该地址的虚拟内存页(即:将内容拷回到物理内存页,并建立新的虚拟地址映射到物理内存页上,然后释放页交换文件中对应部分的空间) 。
写时复制机制
写时复制机制(copy on write, COW):当WRITECOPY属性内存页面被修改时,会触发内存页拷贝,以此来节省物理内存和页交换文件的占用。
注:系统在映射exe或dll文件时会把数据页指定为PAGE_WRITECOPY属性,代码页指定为PAGE_EXECUTE_WRITECOPY属性
具体过程:
① 当进程对内存页执行修改操作时,系统会找一个闲置的物理内存页,并拷贝所有内容到新页上,然后标记新页的后备存储器为页交换文件,最后将进程的虚拟内存页指向新的物理内存页。
② 经过上述步骤,进程就可以使用自己副本了,修改在新的物理页上进行,而不对原来的内存页产生任何影响。
重定基地址
重定基地址(Rebasing):模块装载时,如果目标地址被占用或基于安全考虑,系统会根据模块的所需地址空间的大小为其分配一个新的基地址,并将模块装载到该基地址处。
问题:
① 一旦发生了Rebasing,当模块映射时,要对重定位表中所有页进行地址修正。
② 系统修正这些地址的页面时,会触发写时复制机制。
地址空间布局随机化(Address space layout randomization,ASLR)
微软在Vista系统中引入了名为ASLR的技术,模块每次会被加载到随机位置(伪随机),防范恶意程序对已知地址进行攻击。
ASLR不仅对模块地址做了随机处理,还对堆、栈、进程环境块(Process Environment Block, PEB)、线程环境块(Thread Environment Block, TEB)的地址也进行了随机化。
ASLR技术将Rebasing放到内核中进行处理,意味着可以在系统范围上(原来只能在进程范围内),最大程度上减少Rebasing的发生,从而节省物理内存和页交换文件的使用。
PE文件装载
注:映射必须以页面(4KB)为单位,并按照页边界进行对齐
执行完映射后,绝大部分指令和数据都还没有被装入物理内存中。装载过程是随着程序的执行动态进行的。
具体过程:cpu在访问指令和数据时,发现该地址所在的页不在物理内存页中时,会触发缺页中断,此时系统会找一个闲置的物理内存页,并将内容从后备中(映像文件或页交换文件中)载入到该物理内存页中。