探索虚拟内存
1.0 系统信息
GetSystemInfo, GetNativeSystemInfo.想了解64位windows提供的32位模拟层,参阅:http://www.microsoft.com/whdc/system/platform/64bit/wow64_bestprac.mspx.
1.1 虚拟内存状态
GlobalMemoryStatus可以获得当前内存状态的动态信息。
1.2 Microsoft Windows 提供了以下三种机制来对内存进行操控。
虚拟内存,内存映射文件,堆。
1.3 VirtualAlloc来预订进程中的地址空间区域。对于大多数程序员来说,能够让系统在指定的内存地址预订区域是个不同寻常的概念。
如果应用程序在非统一内存访问(NUMA)机器上运行,那么我们可以调用VirtualAllocExNuma来强制系统在某个节点的物理内存中分配一部分虚拟内存,以此提升性能。系统会从页交换文件中来调拨物理存储器给区域。
1.4 大页面分配必须满足三个条件:
a. 要分配的内存块大小(即dwSize参数的值),必须是GetLargePageMinimum函数返回值的整数倍。
b. 在调用VirtualAlloc时,必须把MEM_RESERVE | MEM_COMMIT与fdwAllocationType参数按位或起来。
c. 在用VirtualAlloc分配内存时必须传PAGE_READWRITE保护属性给fdwProtect参数。
1.5 重置物理存储器的内容:
MEM_RESET只能单独使用。
1.6 地址窗口扩展:
在创建AWE时,Microsoft有以下两个目标:
a.允许应用程序以一种特殊的方式分配内存,操作系统保证不会将以这种方式分配的内存换出到磁盘上。
b.允许应用程序访问比进程地址空间还要多的内存。
基本上AWE提供了一种方式,可以让应用程序分配一块或多块内存。
1.7 当系统在创建线程的时候默认会为其预订1MB的地址空间并给区域顶部(即地址最高)的两个页面调拨物理存储器。我们可以通过Microsoft编译器的/F选项,或/STACK选项来改变这个默认值(/Freserve ,/STACK:reserve[,commit])。在构建程序的时候,系统会将其写入到.EXE,.DLL文件的PE文件头中,当系统创建线程的时候会根据这大小来预订地址空间,在调用CreateThead,_beginthreadex函数时可以指定一开始就要调拨的存储器的数量,所有调拨的物理储存器都有PAGE_READWRITE保护属性。系统永远不会给区域底部的那个页面调拨储存器(这样做的目的是为了保护进程使用其他的数据,使它们不会因为意外的内存写越界而遭到破坏,如果这个页面也调拨了那么系统就无法捕捉到线程对栈外区域的访问)
当线程访问最后一个防护页面时,系统会抛出EXECEPTION_STACK_OVERFLOW异常,如果线程捕获了该异常并继续执行,那么系统将不会在同一个线程中再次抛出此异常,这时因为后面再也没有防护页面了。如果希望在同一线程中继续收到此异常,那么应用程序必须重置防护页面。我们可以调用c运行库中的_resetstkoflw(在malloc.h中)这个函数来实现。
如果栈的增长越过了所预订的区域,那么线程就会覆盖进程地址空间中的其他数据——像这种类型的缺陷是很难找到的。另一种很难找的缺陷是栈下溢(stack underflow)如下:
int WINAPI WinMain(HINSTANCE hInstExe, HINSTANCE , PTSTR pszCmdLine, int nCmdShow)
{
BYTE aBytes[100];
aBytes[10000] = 0; //stack underflow
这条语句有可能会引发访问违规,也可能不会(因为紧接着线程栈的后面可能有另一块 已调拨的区域),如果这样系统将无法检测到。
return(0);
}
下面的栈下溢总是会引起内存破坏:
DWORD WINAPI ThreadFunc(PVOID pvParam) {
BYTE aBytes[0x10];
// Figure out where the stack is in the virtual address space
// See Chapter 14 for more details about VirtualQuery
MEMORY_BASIC_INFORMATION mbi;
SIZE_T size = VirtualQuery(aBytes, &mbi, sizeof(mbi));
// Allocate a block of memory just after the 1 MB stack
SIZE_T s = (SIZE_T)mbi.AllocationBase + 1024*1024;
PBYTE pAddress = (PBYTE)s;
BYTE* pBytes = (BYTE*)VirtualAlloc(pAddress, 0x10000,
MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
// Trigger an unnoticeable stack underflow
aBytes[0x10000] = 1; // Write in the allocated block, past the stack
...
return(0);
}