Delphi研究之驱动开发篇(四)

来源:互联网 发布:sqlserver数据仓库 编辑:程序博客网 时间:2024/06/05 12:43

======================================================
注:本文源代码点此下载
======================================================

通过对前面几篇教程的学习,相信大家已经掌握了一些用delphi开发windows驱动程序的基础知识,从现在开始我们来了解一些必要的底层技术,首先我们要了解的就是内存管理方面的知识。

内存管理器给用户进程提供了大量的api。这些api可以分为三类:虚拟内存函数、内存映射文件函数和堆函数。内核的成员(包括驱动程序)有很多高级的工具。例如:驱动程序能够在物理地址空间里分配一个连续的内存。这类函数呢,前缀是"mm"。另外呢,还有一种以"ex"为前缀的函数,用于从系统内存池里(分页和不分页的)分配和释放内存,还可以操作后备列表(lookaside lists)。

后备列表是啥东东?我们后面再讲,它可以提供更快的内存分配,但要使用预定义的固定的块大小。

系统内存堆可跟用户堆不一样啊,它表现为系统地址空间的两个所谓的内存池。

◎ 不分页池--不分页池不会分页到交换文件(swap file),自然也不需要分页回来。它们总是老老实实在物理内存里活动,在你想访问它们的时候总能找到它们(任何irql等级),并且不会出现分页错误。这也正是它的优点,任何访问都不会出现页面错误!页面错误往往会导致系统瘫(当irql >= dispatch_level)!

◎ 分页池--顾名思义,就是可以分页(分入和分出)的了。你只能使用(irql = dispatch_level会导致系统瘫痪!但是事情并不绝对,也许它当时不死机,过一会才死呢!啥时候死?就是当你的系统将内存交换了出去,并且你访问它的时候!

千万不要太钟爱不分页内存,太浪费资源了!它总要占用物理内存!分配的堆使用完后记得一定要释放,你在系统池里分配了内存,无论你的驱动程序发生了什么事情,这些内存不会被回收,除非exfreepool 被调用。如果你不用exfreepool显式地释放内存,即使你的驱动程序卸载了,这些内存还驻留在那里。所以呢,你就乖乖地显式地释放内存吧!

系统内存池分配的内存不会被自动清零,最后的使用者可能会留下垃圾。所以呢,使用之前,最好统统置零。

你可以很容易地定义你需要的内存类型了,就两种:分页,不分页。如果你的代码要访问irql >= dispatch_level,不用说你也知道,你必须使用不分页类型。代码本身,和涉及的数据都要在不分页内存里。在缺省情况下,驱动程序以不分页内存加载,除非是init节区或者名称以"page"开始的节区。假如你不做任何动作去改变驱动程序的内存属性(例如:别去调用mmpageentiredriver使驱动程序的映像分页),你就不用关心驱动程序了,它总是在内存里。

先前的文章中我们讨论了常用的驱动函数(driverentry, driverunload, dispatchxxx)被调用时所处的irql等级。

ddk给了我们关于每一个函数被调用时的irql等级的相关信息。例如:在后面的文章中我们会使用ioinitializetimer函数,该函数的描述这样说的:该函数执行时,时钟事件发生时的等级irql = dispatch_level 。这就意味着:这个函数访问的所有内存都必须是不分页的。

如果你不能确定到底是哪个irql,你写程序时候可以这样调用kegetcurrentirql:

if kegetcurrentirqldispatch_level then

begin

{使用任意内存}

end else

begin

{只能使用不分页内存}

end;

下面让我们来看一个简单驱动程序例子systemmodules,该例子的主要动作集中在driverentry函数里。我们会分配分页内存(你应该记得driverentry运行在irql =passive_level等级,所以使用分页内存自然是没问题了),然后写进一些信息,再释放,并让系统卸载驱动程序。

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 cb0 then

begin

p := exallocatepool(pagedpool, cb);

if pnil 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 pmessagenil 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.

为了写点有用的信息,我们加载了一些模块到系统地址空间(包括以下系统模块:ntoskrnl.exe, hal.dll等,还有设备驱动程序),然后去找ntoskrnl.exe和ntice.sys。我们用systemmoduleinformation信息类作为参数调用zwquerysysteminformation来得到系统模块列表。读者可以到garry nebbett的书《winodwsnt/2000 内部api参考》去找这些函数的描述。顺便说一下,zwquerysysteminformation 是一个独特的函数,它返回大量的系统信息。

这个例子中没有提供驱动控制程序。你可以使用kmdkit4d工具包中的kmdmanager或者类似的工具,还可以使用debugview 或 softice 控制台来查看调试信息。

现在我们来分析分析这段程序吧……

cb := 0;

zwquerysysteminformation(systemmoduleinformation, @p, 0, cb);

首先我们要决定我们要使用多少空间。上面调用对zwquerysysteminformation的调用使我们获得status_info_length_mismatch(这是正常,因为buffer的尺寸为零。),但是cb变量接收到了buffer尺寸(为零或非零)。于是我们就得到了需要的buffer尺寸。这里需要地址p是为了zwquerysysteminformation函数的正常执行。

if cb0 then

begin

p := exallocatepool(pagedpool, cb);

exallocatepool从分页内存池分配需要数量的内存。如果是不分页内存呢,就把第一个参数pagedpool相应地改成nonpagedpool。exallocatepool比用户模式的heapalloc 简单一些,只有两个参数:第一个参数是内存池类型(分页、不分页),第二个参数是需要的内存尺寸。简单吧!

if pnil then

如果exallocatepool 返回非零值,那么它就是一个指向分配buffer的指针。

检查调试信息会发现exallocatepool 分配的buffer地址是页尺寸大小的倍数。假如请求的内存的大小大于或等于(>=)页尺寸(我们这个例子中,是明显地大了),分配的内存会从页边界开始分配。

if zwquerysysteminformation(systemmoduleinformation, p, cb, cb) = status_success then

begin

ptemp := p;

我们再次调用zwquerysysteminformation,这次使用buffer的指针和尺寸作为参数。

如果返回的是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;

cb变量接受实际返回的字节的数量,但是我们目前用不到它。

我假设在两次调用zwquerysysteminformation 之间没有其它新模块出现。这种可能性当然是很小的。我们这里只是为了学习目的嘛!你最好使用更安全的办法:在循环中反复调用zwquerysysteminformation来逐次增加buffer大小,直到该大小满足需求!

dwnummodules := dword(p^);

buffer中的第一个双字(double word)包含模块的数量,这些模块紧跟在system_module_information队列的后面。然后,模块的数量会保存在dwnummodules中。

cb := (sizeof(system_module_information) + 100) * 2;

pmessage := exallocatepool(pagedpool, cb);

我们需要另一个buffer来保存我们寻找的两个模块的名字和其它信息。我们假定这个尺寸(((sizeof(system_module_information) + 100) * 2)是足够的。

注意:这次的buffer地址不是页尺寸的倍数,是因为buffer尺寸小于一个页尺寸。

memset(pmessage, 0, cb);

用memset填充buffer为零,这是为了安全考虑,使字符串肯定以零结束。学习过c语言的朋友对此函数应该很熟悉。(事实上你也可以使用delphi的fillchar函数)

inc(pchar(ptemp), sizeof(dword));

跳过保存了模块数量的大小的第一个双字,现在ptemp就指向了第一个system_module_information结构。

for icnt := 1 to dwnummodules do

begin

我们对结构队列循环dwnummodules次数,来寻找ntoskrnl.exe 和 ntice.sys。

在多处理器的系统ntoskrnl.exe模块的名字应该是ntkrnlmp.exe,如果你使用的是带pae(物理地址扩展)的系统,那么系统会分别地支持ntkrnlpa.exe 和 ntkrpamp.exe。我这里当然假定你不会拥有那么牛的机器了^_^。

ipos := (psystem_module_information(ptemp))^.modulenameoffset;

pmodulename := @((psystem_module_information(ptemp))^.imagename[ipos]);

imagename和modulenameoffset 域分别包含了模块的全路径和路径内模块名的相对偏移。

if (_strnicmp(pmodulename, 'ntoskrnl.exe', length('ntoskrnl.exe')) = 0) or

(_strnicmp(pmodulename, 'ntice.sys', length('ntice.sys')) = 0) then

begin

strnicmp 做不区分大小写的两ansi标准字符串比较。第三个参数是比较的字符的数量。这里也许不必要使用__strnicmp,因为system_module_information里模块名是零结束的,使用_stricmp就可以了。这里使用__strnicmp是为了更加安全。

顺便提一句,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;

假如上文提及的模块被找到,我们就用_snprintf(内核提供)函数格式化字符串,字符串中包含了模块名、基地址和尺寸,然后将字符串加到buffer去。

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);

释放在系统内存池里分配的内存。

result := status_device_configuration_error;

返回失败代码,这样强制系统将驱动程序从内存中卸载。

现在你清楚了吧,使用系统内存池比使用用户模式的堆简单多了。唯一需要注意的问题就是正确地定义内存池类型。

用户模式下的ntdll.dll提供了许多zwxxx系列函数,他们是进入内核模式的大门。注意:他们的参数的数量和含义都是一样的。你可以省不少事儿了吧!

由于内核的错误会导致系统瘫痪,所以你可以在用户模式下调试,然后小小地改动(如果需要)后拷贝到你的驱动程序。例如:ntdll.dll的zwquerysysteminformation 调用返回同样的信息。使用这个技巧你就不用总是重新启动你的机器了。

文件下载:kmdkit4d_patch0119


======================================================
在最后,我邀请大家参加新浪APP,就是新浪免费送大家的一个空间,支持PHP+MySql,免费二级域名,免费域名绑定 这个是我邀请的地址,您通过这个链接注册即为我的好友,并获赠云豆500个,价值5元哦!短网址是http://t.cn/SXOiLh我创建的小站每天访客已经达到2000+了,每天挂广告赚50+元哦,呵呵,饭钱不愁了,\(^o^)/
原创粉丝点击