随笔 - 74, 文章 - 0, 评论 - 26, 引用 - 0
数据加载中……

(转)Windows应用程序捆绑核心编程

1.3  虚拟内存访问

每个进程都拥有自己的虚拟地址空间,那么怎样才能访问这个空间呢?这就需要用到Windows API函数。这些函数直接与编写程序相关,因而更受软件工程师的关注。有关这方面的函数较多,这里介绍几个重要的函数。

1.3.1  获取系统信息

在一个程序中不能直接应用某个系统的设备参数,否则将不利于程序的移植。因此,如果确实需要用到这样的设备参数,则需要一个系统信息函数来获得。VC++ 编译器所提供这样的函数为GetSystemInfo()。该函数需要一个指向SYSTEM_INFO结构的指针作为参数。其原型表示为:

l           

void GetSystemInfo(LPSYSTEM_INFO lpSystemInfo);

l           

其中lpSystemInfo返回LPSYSTEM_INFO结构的地址,用于装载适当的系统信息,这个结构体定义为:

l           

typedef struct _SYSTEM_INFO { 

    union {   

        DWORD dwOemId;   

        struct {     

            WORD wProcessorArchitecture;      

            WORD wReserved;   

        };

    }; 

   

    DWORD  dwPageSize; 

    LPVOID  lpMinimumApplicationAddress; 

    LPVOID  lpMaximumApplicationAddress; 

    DWORD_PTR  dwActiveProcessorMask; 

    DWORD  dwNumberOfProcessors; 

    DWORD  dwProcessorType; 

    DWORD  dwAllocationGranularity; 

    WORD   wProcessorLevel; 

    WORD   wProcessorRevision;

} SYSTEM_INFO;

l           

其中参数含义如下所述。

dwOemId:是一个过时选项,用于与Windows NT 3.5以及以前的版本兼容。

wProcessorArchitecture:指明处理的结构,如Intel、Alpha、Intel 64位或Alpha       64位。

dwPageSize:用于显示CPU的页面大小。在x86 CPU上,这个值是4096字节。在Alpha CPU上,这个值是8192字节。在IA-64上,这个值是8192字节。

lpMinimumApplicationAddress:用于给出每个进程可用地址空间的最小内存地址。在Windows 98上,这个值是0x400000,因为每个进程的地址空间中下面的4MB是不能使用的。在Windows 2K/XP上,这个值是0x10000,因为每个进程的地址空间中开头的64KB总是空闲的。

lpMaximumApplicationAddress:用于给出每个进程可用地址空间的最大内存地址。在Windows 98上,这个地址是0x7FFFFFFF,因为共享内存映射文件区域和共享操作系统代码包含在上面的2GB分区中。在Windows XP上,这个地址是0x7FFEFFFF。

dwActiveProcessorMask:位屏蔽,指明哪个CPU是活动的。

dwNumberOfProcessors:计算机中CPU的数目。

dwProcessorType:处理器类型。

dwAllocationGranularity:保留的地址空间区域的分配粒度。

wProcessorLevel:进一步细分处理器的结构。

wProcessorRevision:用于进一步细分处理器的级别。

wReserved:保留供将来使用。

在以上参数中只有lpMinimumApplicationAddress、lpMaximumApplicationAddress、dwPageSize和dwAllocationGranularity与内存有关。

1.3.2  在应用程序中使用虚拟内存

对内存分配可以采用不同的方法,常用的方法有:用C/C++语言的内存分配函数,例如,用malloc() 和 free()、new 和 delete 函数分配和释放堆内存;用Windows传统的全局或者局部内存分配函数,如GlobalAlloc()和GlobalFree();用Win32的堆分配函数,如HeapAlloc()和HeapFree();用Win32的虚拟内存分配函数,如VirtualAlloc()和VirtualFree()。注意,用不同的方法分配内存后,要用相对应的函数来释放所占用的内存。这里只介绍Win32的虚拟内存分配函数。

在进程创建之初并被赋予地址空间时,其虚拟地址空间尚未分配,处于空闲状态。这时地址空间内的内存是不能使用的,必须通过VirtualAlloc()函数来分配其中的各个区域,对其进行保留。VirtualAlloc()函数原型为:

l           

LPVOID VirtualAlloc(

    LPVOID lpAddress,

    DWORD dwSize,

    DWORD flAllocationType,

    DWORD flProtect

    );

l           

该函数用来分配一定范围的虚拟页。参数1指定起始地址;参数2指定分配内存的长度;参数3指定分配方式,取值MEM_COMMINT或者MEM_RESERVE;参数4指定控制访问本次分配的内存的标识,取值为PAGE_READONLY、PAGE_READWRITE或者PAGE_NOACCESS。

分配完成后,即在进程的虚拟地址空间中保留了一个区域,可以对此区域中的内存进行保护权限许可范围内的访问。当不再需要访问此地址空间区域时,应释放此区域,由VirtualFree()负责完成。其函数原型为:

l           

BOOL VirtualFree(

    LPVOID lpAddress,

    DWORD dwSize,

    DWORD dwFreeType

    );

l           

其中参数含义如下所述。

lpAddress:指向待释放页面区域的指针。如果参数dwFreeType指定了MEM_RELEASE,则lpAddress必须为页面区域保留由VirtualAlloc()所返回的基地址。

dwSize:指定了要释放的地址空间区域的大小,如果参数dwFreeType指定了MEM_RELEASE标志,则将dwSize设置为0,由系统计算在特定内存地址上的待释放区域的大小。

dwFreeType:为所执行的释放操作的类型,其可能的取值为MEM_RELEASE和MEM_DECOMMIT,其中MEM_RELEASE标志指明要释放指定的保留页面区域,MEM_DECOMMIT标志则对指定的占用页面区域进行占用的解除。

如果VirtualFree()执行完成,将回收全部范围的已分配页面,此后如再对这些已释  放页面区域内存进行访问将引发内存访问异常。释放后的页面区域可供系统继续分配   使用。

1.3.3  获取虚存状态

Windows API函数GlobalMemoryStatus()可用于检索关于当前内存状态的动态信息。在软件的About对话框中,通常用这个函数来获取系统内存的使用情况。其函数原型为:

l           

void GlobalMemoryStatus(LPMEMORYSTATUS lpmstMemStat);

l           

其中lpmstMemStat返回MEMORYSTATUS结构的地址,这个结构体的定义为:

l           

typedef struct MEMORYSTATUS{

    DWORD dwLength;

    DWORD dwMemoryLoad;        

    DWORD dwTotalPhys;

    DWORD dwAvailPhys;

    DWORD dwTotalPageFile;

    DWORD dwAvailPageFile;

    DWORD dwTotalVirtual;

    DWORD dwAvailVirtual; 

} MEMORYSTATUS ,* LPMEMORYSTATUS;

l           

其中参数含义如下所述。

dwLength:MEMORYSTATUS结构大小。

dwMemoryLoad:已使用内存所占的百分比。

dwTotalPhys:物理存储器的总字节数。

dwAvailPhys:空闲物理存储器的字节数。

dwTotalPageFile:页文件包含的最大字节数。

dwAvailPageFile:用户模式分区中空闲内存大小。

dwTotalVirtual:用户模式分区大小。

dwAvailVirtual:表示当前进程中还剩下的自由区域的总和。

在调用GlobalMemoryStatus()之前,必须将dwLength成员初始化为用字节表示的结构的大小,即一个MEMORYSTATUS结构的大小。这个初始化操作使得Microsoft能够在新版本Windows系统中将新成员添加到这个结构中,而不会破坏现有的应用程序。当调用GlobalMemoryStatus()时,它将对该结构的其余成员进行初始化并返回。

如果某个应用程序在内存大于4GB的计算机上运行,或者合计交换文件的大小大于4GB,那么可以使用新的GlobalMemoryStatusEx()函数。其函数的原型为:

l           

BOOL GlobalMemoryStatusEx(MEMORYSTATUSEX  &mst);

l           

其中mst返回MEMORYSTATUSEX结构的填充信息,该结构体与原先的MEMORYSTATUS结构基本相同,差别在于新结构的所有成员的大小都是64位宽,因此它的值可以大于4 GB。

1.3.4  确定虚拟地址空间的状态

对内存的管理除了对当前内存的使用状态信息进行获取外,还经常需要获取有关进程的虚拟地址空间的状态信息。例如,如何得到一个进程已提交的页面范围?这就要用到两个 API函数VirtualQuery()或VirtualQueryEx()来进行查询。这两个函数的功能相似,不同就是VirtualQuery()只是查询本进程内存空间信息,而VirtualQueryEx()可以查询指定进程的内存空间信息。VirtualQuery()函数原型如下:

l           

DWORD VirtualQuery(

    LPVOID lpAddress,     

    PMEMORY_BASIC_INFORMATION lpBuffer,

    DWORD dwLength

    );

l           

VirtualQueryEx()函数原型如下:

l           

DWORD VirtualQueryEx(

    HANDLE hProcess ,

    LPCVOID lpAddress ,

    PMEMORY_BASIC_INFORMATION lpBuffer ,

    DWORD dwLength

    );

l           

其中参数含义如下所述。

hProcess:进程的句柄。

lpAddress:想要了解其信息的虚存地址。

lpBuffer:返回MEMORY_ BASIC_INFORMATION结构的地址。

dwLength:返回的字节数。

PWEMORY_BASIC_INFORMATION的定义如下:

l           

typedef struct _MEMORY_BASIC_INFORMATION{

    PVOID BaseAddress;  

    PVOID AllocationBase;

    DWORD AllocationProtect;

    DWORD RegionSize;

    DWORD State;

    DWORD Protect;

    DWORD Type;

} MEMORY_BASIC_INFORMATION, * PMEMORY_BASIC_INFORMATION;

l           

其中参数含义如下所述。

BaseAddress:被查询内存块的基地址。

AllocationBase:用VirtualAlloc()分配该内存时实际分配的基地址。

AllocationProtect:分配该页面时,页面的一些属性,如PAGE_READWRITE、PAGE_EXECUTE等(其他属性可参考 Platform SDK)。

RegionSize:从BaseAddress开始,具有相同属性的页面的大小。

State:页面的状态,有3种可能值:MEM_COMMIT、MEM_FREE和MEM_ RESERVE,这个参数是最重要的,从中可知指定内存页面的状态。

Protect:页面的属性,它可能的取值与 AllocationProtect 相同。

Type:指明了该内存块的类型,有3种可能值:MEM_IMAGE、MEM_MAPPED和MEM_PRIVATE。

1.3.5  改变内存页面保护属性

在进行进程挂钩时,经常要向内存页中写入部分代码,这就需要改变内存页的保护属性。有幸的是Win32提供了两个API函数VirtualProtect()和VirtualProtectEx(),它们可以对改变内存页保护。例如,在使用这两个函数时,可以先按PAGE_READWRITE属性来提交一个页的地址,并且立即将数据填写到该页中,然后再把该页的属性改变为PAGE_READONLY,这样可以有效地保护数据不被该进程中的任何其他线程重写。在调用这两个函数之前最好先了解有关页面的信息,可以通过VirtualQuery()来实现。

VirtualProtect()与VirtualProtectEx()函数的区别在于VirtualProtect()只适用于本进程,而VirtualProtectEx()可以适用于其他进程。VirtualProtect()函数原型如下:

BOOL VirtualProtect(

    PVOID pvAddress,

    DWORD dwSize,

    DWORD flNewProtect,

    PDWORD pflOldProtect

    );

l           

VirtualProtectEx()函数原型如下:

l           

BOOL VirtualProtectEx(

    HANDLE hProcess,

    PVOID pvAddress,

    DWORD dwSize,

    DWORD flNewProtect,

    PDWORD pflOldProtect

    );

l           

其中参数的含义如下所述。

hProcess:要修改内存的进程句柄。

pvAddress:指向内存的基地址(它必须位于进程的用户方式分区中)。

dwSize:用于指明想要改变保护属性的字节数。

flNewProtect:代表PAGE_*保护属性标志中的任何一个标志,但PAGE_ WRITECOPY和PAGE_EXECUTE_WRITECOPY这两个标志除外。

pflOldProtect:是DWORD大小的地址,VirtualProtect()和VirtualProtectEx()将用原先与pvAddress位置上的字节相关的保护属性填入该地址。尽管许多应用程序并不需要该信息,但是必须为该参数传递一个有效地址,否则该函数的运行将会失败。

1.3.6  进行一个进程的内存读写

前面已经说明了如何获得一个进程的内存属性、如何分配内存和如何改变内存页的保护属性,其最终的目的是要对一个进程中内存内容进行读写。要完成此工作,需要用到两个函数:ReadProcessMemory() 和WriteProcessMemory(),这两个函数非常有用。如果知道了一个进程的句柄和内存地址,就可以用ReadProcessMemory()函数来得到该进程和该地址中的内容,此函数的原型为:

l           

BOOL ReadProcessMemory(

    HANDLE hProcess,

    LPCVOID lpBaseAddress,

    LPVOID lpBuffer,

    DWORD nSize, 

    LPDWORD lpNumberOfBytesRead

    );

l           

其中hProcess为要读入的进程句柄,lpBaseAddress为读内存的起始地址,lpBuffer为读入数据的地址,nSize为要读入的字节数,lpNumberOfBytesRead为实际读入的字   节数。

同样,如果知道了一个进程的句柄和内存地址,可以用WriteProcessMemory()函数向该进程和该地址中写入新的内容,这个函数的原型为:

l           

BOOL WriteProcessMemory(

    HANDLE hProcess,

    LPVOID lpBaseAddress,

    LPVOID lpBuffer,

    DWORD nSize,

    LPDWORD lpNumberOfBytesWritten

    );

l           

其中参数hProcess为要写入的进程句柄,lpBaseAddress为写内存的起始地址,lpBuffer为写入数据的地址,nSize为要写入的字节数,lpNumberOfBytesWritten为实际写入的字节数。

posted on 2007-11-15 12:40 井泉 阅读(855) 评论(0)  编辑 收藏 引用 所属分类: c code


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理