内存管理器给用户进程提供了大量的API。这些API可以分为三类:虚拟内存函数、内存映射文件函数和堆函数。内核的成员(包括驱动程序)有很多高级的工具。例如:驱动程序能够在物理地址空间里分配一个连续的内存。这类函数呢,前缀是"Mm"。另外呢,还有一种以"Ex"为前缀的函数,用于从系统内存池里(分页和不分页的)分配和释放内存,还可以操作后备列表(lookaside lists)。
后备列表是啥东东?我们后面再讲,它可以提供更快的内存分配,但要使用预定义的固定的块大小。
系统内存堆可跟用户堆不一样啊,它表现为系统地址空间的两个所谓的内存池。
◎ 不分页池--不分页池不会分页到交换文件(swap file),自然也不需要分页回来。它们总是老老实实在物理内存里活动,在你想访问它们的时候总能找到它们(任何IRQL等级),并且不会出现分页错误。这也正是它的优点,任何访问都不会出现页面错误!页面错误往往会导致系统瘫(当IRQL >= DISPATCH_LEVEL)!
◎ 分页池--顾名思义,就是可以分页(分入和分出)的了。你只能使用(IRQL < DISPATCH_LEVEL)的内存。
以上两种池都位于系统地址空间,在进程上下文中可以使用它们。有一个函数集合叫ExAllocatePoolXXX,用于从系统内存池分配内存。函数ExFreePool用来释放。
在我们开始使用它们的时候,来看看基本要点:
前面提到,如果你访问已经被交换出去的内存时IRQL >= DISPATCH_LEVEL会导致系统瘫痪!但是事情并不绝对,也许它当时不死机,过一会才死呢!啥时候死?就是当你的系统将内存交换了出去,并且你访问它的时候!
千万不要太钟爱不分页内存,太浪费资源了!它总要占用物理内存!分配的堆使用完后记得一定要释放,你在系统池里分配了内存,无论你的驱动程序发生了什么事情,这些内存不会被回收,除非ExFreePool 被调用。如果你不用ExFreePool显式地释放内存,即使你的驱动程序卸载了,这些内存还驻留在那里。所以呢,你就乖乖地显式地释放内存吧!
系统内存池分配的内存不会被自动清零,最后的使用者可能会留下垃圾。所以呢,使用之前,最好统统置零。
你可以很容易地定义你需要的内存类型了,就两种:分页,不分页。如果你的代码要访问IRQL >= DISPATCH_LEVEL,不用说你也知道,你必须使用不分页类型。代码本身,和涉及的数据都要在不分页内存里。在缺省情况下,驱动程序以不分页内存加载,除非是INIT节区或者名称以"PAGE"开始的节区。假如你不做任何动作去改变驱动程序的内存属性(例如:别去调用MmPageEntireDriver使驱动程序的映像分页),你就不用关心驱动程序了,它总是在内存里。
先前的文章中我们讨论了常用的驱动函数(DriverEntry, DriverUnload, DispatchXxx)被调用时所处的IRQL等级。
DDK给了我们关于每一个函数被调用时的IRQL等级的相关信息。例如:在后面的文章中我们会使用IoInitializeTimer函数,该函数的描述这样说的:该函数执行时,时钟事件发生时的等级IRQL = DISPATCH_LEVEL 。这就意味着:这个函数访问的所有内存都必须是不分页的。
如果你不能确定到底是哪个IRQL,你写程序时候可以这样调用KeGetCurrentIrql:
If KeGetCurrentIrql
<
DISPATCH_LEVEL
then
begin
{ 使用任意内存 }
End else
begin
{ 只能使用不分页内存 }
End;
begin
{ 使用任意内存 }
End else
begin
{ 只能使用不分页内存 }
End;
unit SystemModules;
interface
uses
nt_status, ntoskrnl, native;
function _DriverEntry(pDriverObject:PDRIVER_OBJECT; pusRegistryPath:PUNICODE_STRING): NTSTATUS; stdcall;
implementation
function _DriverEntry(pDriverObject:PDRIVER_OBJECT; pusRegistryPath:PUNICODE_STRING): NTSTATUS;
var
cb:DWORD;
p, pTemp:PVOID;
dwNumModules:DWORD;
pMessage, pModuleName: PCHAR;
buffer: array [ 0 .. 295 ] of char;
szModuleName: array [ 0 .. 100 ] of char;
iCnt, iPos: integer;
begin
DbgPrint( ' SystemModules: Entering DriverEntry ' );
cb : = 0 ;
ZwQuerySystemInformation(SystemModuleInformation, @p, 0 , cb);
if cb <> 0 then
begin
p : = ExAllocatePool(PagedPool, cb);
if p <> nil then
begin
DbgPrint( ' SystemModules: %u bytes of paged memory allocted at address %08X ' , cb, p);
if ZwQuerySystemInformation(SystemModuleInformation,
p, cb, cb) = STATUS_SUCCESS then
begin
pTemp : = p;
dwNumModules : = DWORD(p^);
cb : = (sizeof(SYSTEM_MODULE_INFORMATION) + 100 ) * 2 ;
pMessage : = ExAllocatePool(PagedPool, cb);
if pMessage <> nil then
begin
DbgPrint( ' SystemModules: %u bytes of paged memory allocted at address %08X ' , cb, pMessage);
memset(pMessage, 0 , cb);
inc(PCHAR(pTemp), sizeof(DWORD));
for iCnt : = 1 to dwNumModules do
begin
iPos : = (PSYSTEM_MODULE_INFORMATION(pTemp))^.ModuleNameOffset;
pModuleName : = @((PSYSTEM_MODULE_INFORMATION(pTemp))^.ImageName[iPos]);
if (_strnicmp(pModuleName, ' ntoskrnl.exe ' , length( ' ntoskrnl.exe ' )) = 0 ) or
(_strnicmp(pModuleName, ' ntice.sys ' , length( ' ntice.sys ' )) = 0 ) then
begin
memset(@szModuleName, 0 , sizeof(szModuleName));
strcpy(@szModuleName, pModuleName);
_snprintf(@buffer, sizeof(buffer),
' SystemModules: Found %s base: %08X size: %08X ' ,
@szModuleName,
(PSYSTEM_MODULE_INFORMATION(pTemp))^.Base,
(PSYSTEM_MODULE_INFORMATION(pTemp))^._Size);
strcat(pMessage, @buffer);
end ;
inc(PCHAR(pTemp), sizeof(SYSTEM_MODULE_INFORMATION));
end ;
if pMessage[ 0 ] <> # 0 then
begin
DbgPrint(pMessage);
end else
begin
DbgPrint( ' SystemModules: Found neither ntoskrnl nor ntice ' );
end ;
ExFreePool(pMessage);
DbgPrint( ' SystemModules: Memory at address %08X released ' , pMessage);
end ;
end ;
ExFreePool(p);
DbgPrint( ' SystemModules: Memory at address %08X released ' , p);
end ;
end ;
DbgPrint( ' SystemModules: Leaving DriverEntry ' );
result : = STATUS_DEVICE_CONFIGURATION_ERROR;
end ;
end .
interface
uses
nt_status, ntoskrnl, native;
function _DriverEntry(pDriverObject:PDRIVER_OBJECT; pusRegistryPath:PUNICODE_STRING): NTSTATUS; stdcall;
implementation
function _DriverEntry(pDriverObject:PDRIVER_OBJECT; pusRegistryPath:PUNICODE_STRING): NTSTATUS;
var
cb:DWORD;
p, pTemp:PVOID;
dwNumModules:DWORD;
pMessage, pModuleName: PCHAR;
buffer: array [ 0 .. 295 ] of char;
szModuleName: array [ 0 .. 100 ] of char;
iCnt, iPos: integer;
begin
DbgPrint( ' SystemModules: Entering DriverEntry ' );
cb : = 0 ;
ZwQuerySystemInformation(SystemModuleInformation, @p, 0 , cb);
if cb <> 0 then
begin
p : = ExAllocatePool(PagedPool, cb);
if p <> nil then
begin
DbgPrint( ' SystemModules: %u bytes of paged memory allocted at address %08X ' , cb, p);
if ZwQuerySystemInformation(SystemModuleInformation,
p, cb, cb) = STATUS_SUCCESS then
begin
pTemp : = p;
dwNumModules : = DWORD(p^);
cb : = (sizeof(SYSTEM_MODULE_INFORMATION) + 100 ) * 2 ;
pMessage : = ExAllocatePool(PagedPool, cb);
if pMessage <> nil then
begin
DbgPrint( ' SystemModules: %u bytes of paged memory allocted at address %08X ' , cb, pMessage);
memset(pMessage, 0 , cb);
inc(PCHAR(pTemp), sizeof(DWORD));
for iCnt : = 1 to dwNumModules do
begin
iPos : = (PSYSTEM_MODULE_INFORMATION(pTemp))^.ModuleNameOffset;
pModuleName : = @((PSYSTEM_MODULE_INFORMATION(pTemp))^.ImageName[iPos]);
if (_strnicmp(pModuleName, ' ntoskrnl.exe ' , length( ' ntoskrnl.exe ' )) = 0 ) or
(_strnicmp(pModuleName, ' ntice.sys ' , length( ' ntice.sys ' )) = 0 ) then
begin
memset(@szModuleName, 0 , sizeof(szModuleName));
strcpy(@szModuleName, pModuleName);
_snprintf(@buffer, sizeof(buffer),
' SystemModules: Found %s base: %08X size: %08X ' ,
@szModuleName,
(PSYSTEM_MODULE_INFORMATION(pTemp))^.Base,
(PSYSTEM_MODULE_INFORMATION(pTemp))^._Size);
strcat(pMessage, @buffer);
end ;
inc(PCHAR(pTemp), sizeof(SYSTEM_MODULE_INFORMATION));
end ;
if pMessage[ 0 ] <> # 0 then
begin
DbgPrint(pMessage);
end else
begin
DbgPrint( ' SystemModules: Found neither ntoskrnl nor ntice ' );
end ;
ExFreePool(pMessage);
DbgPrint( ' SystemModules: Memory at address %08X released ' , pMessage);
end ;
end ;
ExFreePool(p);
DbgPrint( ' SystemModules: Memory at address %08X released ' , p);
end ;
end ;
DbgPrint( ' SystemModules: Leaving DriverEntry ' );
result : = STATUS_DEVICE_CONFIGURATION_ERROR;
end ;
end .
这个例子中没有提供驱动控制程序。你可以使用KmdKit4D工具包中的KmdManager或者类似的工具,还可以使用DebugView 或 SoftICE 控制台来查看调试信息。
现在我们来分析分析这段程序吧……
cb :
=
0
;
ZwQuerySystemInformation(SystemModuleInformation, @p, 0 , cb);
ZwQuerySystemInformation(SystemModuleInformation, @p, 0 , cb);
if
cb
<>
0
then
begin
p : = ExAllocatePool(PagedPool, cb);
begin
p : = ExAllocatePool(PagedPool, cb);
if
p
<>
nil
then
检查调试信息会发现ExAllocatePool 分配的buffer地址是页尺寸大小的倍数。假如请求的内存的大小大于或等于(>=)页尺寸(我们这个例子中,是明显地大了),分配的内存会从页边界开始分配。
if
ZwQuerySystemInformation(SystemModuleInformation, p, cb, cb)
=
STATUS_SUCCESS
then
begin
pTemp : = p;
begin
pTemp : = p;
如果返回的是STATUS_SUCCESS,那么buffer之中就包含了系统模块列表,数据以SYSTEM_MODULE_INFORMATION(在native.dcu中定义)结构队列的形式存在。
SYSTEM_MODULE_INFORMATION
=
packed
record
Reserved: array [ 0 .. 1 ] of DWORD;
Base: PVOID;
_Size: DWORD;
Flags: DWORD;
Index: WORD;
Unknown: WORD;
LoadCount: WORD;
ModuleNameOffset: WORD;
ImageName: array [ 0 .. 255 ] of char;
end ;
Reserved: array [ 0 .. 1 ] of DWORD;
Base: PVOID;
_Size: DWORD;
Flags: DWORD;
Index: WORD;
Unknown: WORD;
LoadCount: WORD;
ModuleNameOffset: WORD;
ImageName: array [ 0 .. 255 ] of char;
end ;
cb变量接受实际返回的字节的数量,但是我们目前用不到它。
我假设在两次调用ZwQuerySystemInformation 之间没有其它新模块出现。这种可能性当然是很小的。我们这里只是为了学习目的嘛!你最好使用更安全的办法:在循环中反复调用ZwQuerySystemInformation来逐次增加buffer大小,直到该大小满足需求!
dwNumModules :
=
DWORD(p^);
cb :
=
(sizeof(SYSTEM_MODULE_INFORMATION)
+
100
)
*
2
;
pMessage : = ExAllocatePool(PagedPool, cb);
pMessage : = ExAllocatePool(PagedPool, cb);
我们需要另一个buffer来保存我们寻找的两个模块的名字和其它信息。我们假定这个尺寸(((sizeof(SYSTEM_MODULE_INFORMATION) + 100) * 2)是足够的。
注意:这次的buffer地址不是页尺寸的倍数,是因为buffer尺寸小于一个页尺寸。
memset(pMessage,
0
, cb);
inc(PCHAR(pTemp), sizeof(DWORD));
for
iCnt :
=
1
to
dwNumModules
do
begin
begin
在多处理器的系统ntoskrnl.exe模块的名字应该是ntkrnlmp.exe,如果你使用的是带PAE(物理地址扩展)的系统,那么系统会分别地支持ntkrnlpa.exe 和 ntkrpamp.exe。我这里当然假定你不会拥有那么牛的机器了^_^。
iPos :
=
(PSYSTEM_MODULE_INFORMATION(pTemp))^.ModuleNameOffset;
pModuleName : = @((PSYSTEM_MODULE_INFORMATION(pTemp))^.ImageName[iPos]);
pModuleName : = @((PSYSTEM_MODULE_INFORMATION(pTemp))^.ImageName[iPos]);
if
(_strnicmp(pModuleName,
'
ntoskrnl.exe
'
, length(
'
ntoskrnl.exe
'
))
=
0
)
or
(_strnicmp(pModuleName, ' ntice.sys ' , length( ' ntice.sys ' )) = 0 ) then
Begin
(_strnicmp(pModuleName, ' ntice.sys ' , length( ' ntice.sys ' )) = 0 ) then
Begin
顺便提一句,ntoskrnl.exe提供了许多基本的字符串函数,如strcmp、strcpy和strlen等。
memset(@szModuleName,
0
, sizeof(szModuleName));
strcpy(@szModuleName, pModuleName);
_snprintf(@buffer, sizeof(buffer),
' SystemModules: Found %s base: %08X size: %08X ' ,
@szModuleName,
(PSYSTEM_MODULE_INFORMATION(pTemp))^.Base,
(PSYSTEM_MODULE_INFORMATION(pTemp))^._Size);
strcat(pMessage, @buffer);
end ;
strcpy(@szModuleName, pModuleName);
_snprintf(@buffer, sizeof(buffer),
' SystemModules: Found %s base: %08X size: %08X ' ,
@szModuleName,
(PSYSTEM_MODULE_INFORMATION(pTemp))^.Base,
(PSYSTEM_MODULE_INFORMATION(pTemp))^._Size);
strcat(pMessage, @buffer);
end ;
假如上文提及的模块被找到,我们就用_snprintf(内核提供)函数格式化字符串,字符串中包含了模块名、基地址和尺寸,然后将字符串加到buffer去。
if
pMessage[
0
]
<>
#
0
then
begin
DbgPrint(pMessage);
end else
begin
DbgPrint( ' SystemModules: Found neither ntoskrnl nor ntice ' );
end ;
begin
DbgPrint(pMessage);
end else
begin
DbgPrint( ' SystemModules: Found neither ntoskrnl nor ntice ' );
end ;
ExFreePool(pMessage);
DbgPrint( ' SystemModules: Memory at address %08X released ' , pMessage);
end ;
end ;
ExFreePool(p);
DbgPrint( ' SystemModules: Memory at address %08X released ' , pMessage);
end ;
end ;
ExFreePool(p);
释放在系统内存池里分配的内存。
result :
=
STATUS_DEVICE_CONFIGURATION_ERROR;
现在你清楚了吧,使用系统内存池比使用用户模式的堆简单多了。唯一需要注意的问题就是正确地定义内存池类型。
用户模式下的ntdll.dll提供了许多ZwXxx系列函数,他们是进入内核模式的大门。注意:他们的参数的数量和含义都是一样的。你可以省不少事儿了吧!
由于内核的错误会导致系统瘫痪,所以你可以在用户模式下调试,然后小小地改动(如果需要)后拷贝到你的驱动程序。例如:ntdll.dll的ZwQuerySystemInformation 调用返回同样的信息。使用这个技巧你就不用总是重新启动你的机器了。
文件下载: KmdKit4D_patch0119