欢迎光临 C++Builder 研究! 本站主要面向软件开发者(Developer/Programmer), 提供 C++Builder, Delphi, C/C++, VC++ 等相关的资料。发布信息请致信给
编程文档
本站首页 www.ccrun.com | 编程文档 |   关键字:

文件系统驱动编程基础篇之2——标准模型

关键字:文件系统驱动编程,标准模型,基本例程,结构与函数

作者:wskjuf    更新:2008-10-06 22:08:47    浏览:18442

文件系统驱动编程基础篇之二——标准模型、基本例程、结构与函数

文件系统驱动编程基础篇之二——标准模型、基本例程、结构与函数

一、前略

    本系列文章为业余编程爱好者而写,仅仅作为初学者的一个借鉴,真正的精华存在于参考资料*中。知识的积累将经历从薄到厚,再从厚到薄的反复过程,为了打下牢固的基础,请读者务必在阅读本文的基础上花费必要的时间完成参考资料。 笔者的实践环境为:
硬件:P35 Motherboard & ICH9 chip,Pentium Dual Cpu E2160 1.8g, DDR2 1g
软件:Windows XP2、VS 2005、Visual AssistX、DriverStudio 3.2、MICROSOFT.WINDOWS.SERVER.V2003.IFS.DDK、Windbg 6.8.0004.0,请安装好用于调试的xp虚拟机并配置好调试环境。

参考资料*:
1.《Programming the Microsoft Windows driver model》第一版(当前阶段主要阅读资料,阅读第二至第五章)
2.《Kernel Debugging with WinDbg》

阅读基础:掌握c语言,会使用Msdn和WinDbg文档。

本章目的:了解驱动程序的标准模型,认识基本例程和初步了解常用结构与函数。


二、标准模型

    WDM采用了结构化的编程方式,执行效率很高,但编写效率较低,这也是DriverStudio得以发展的重要原因。正如掌握了COM原理,使用ATL才能掌握精髓的道理一样,读者需要忍受记忆大量基础知识的“痛苦”,暂时放弃编写驱动程序的捷径。
    下面的某些图示稍显陈旧,不过已经足以说明问题了。
?

    尽管驱动程序分为多个种类(图1-4),但它们包含的基本内容(图1-5)是一致的。每个驱动程序都从初始化程序DriverEntry进入,通过某个派遣例程DispatchXXX派发特定命令(我们不妨称之为IRP),这些IRP有可能在派遣例程里就得到了解决,也有可能交给驱动程序的其他部分解决。如果驱动程序A本身不能处理这个IRP命令,它就需要将IRP传递到更下层的驱动B,由它们来处理,此时驱动程序A可能因为等待IRP完成而处于睡眠状态,或继续处理新的IRP,直到下层驱动B通知(或通过某种机制唤醒并通知)A该IRP已经处理完毕了,此时A就将处理的结果(我们称之为NTSTATUS)返回原来派发这个IRP的发起人。这就是驱动程序处理IRP的一个简化过程。
    我们提到了驱动程序是分层的这个概念,那么如何理解分层的概念呢?请看图示:


    引用资料1的原话:WDM模型使用了如图2-1的层次结构。图中左边是一个设备对象堆栈。设备对象是系统为帮助软件管理硬件而创建的数据结构。一个物理硬件可以有多个这样的数据结构。处于堆栈最底层的设备对象称为物理设备对象(physical device object),或简称为PDO。在设备对象堆栈的中间某处有一个对象称为功能设备对象(functional device object),或简称FDO。在FDO的上面和下面还会有一些过滤器设备对象(filter device object)。位于FDO上面的过滤器设备对象称为上层过滤器,位于FDO下面(但仍在PDO之上)的过滤器设备对象称为下层过滤器。
    由某个家伙(可能是用户模式下的应用程序,也可能是系统内核组件)发起的IRP从上层过滤器驱动程序一直顺流而下,传递到总线驱动程序处理后,再逐级返回上层,最终发起人得到处理的结果。
    一般情况下,IRP也许不需要传递到总线驱动程序就被处理掉了,但如果大家都不认识这个IRP,他们就只好逐级下传了,如果此时有个搞破坏的驱动程序混了进来,拦截了这个IRP,轻则丢失用户信息、重启、死机,重则造成系统区的数据混乱,你除了重新安装操作系统再无任何事情可做。由此可见,我们不要求驱动程序“有理想”,但必须“有纪律”,每个驱动程序都必须严格按照规范书写代码,这要求编程人员具备较高的素质。
    下面我们来了解驱动编程里最基本的标准模型,这个模型不能解决所有的编程需求,根据需要,它将存在各种变化。我们来看看这个驱动编程里的“基本定式”:

    IO管理器,大家应该理解为该IRP的发起人,可能是张三,也可能是李四,而不是某个固定的组件。这个模型表明了单个驱动程序里各部件的合作与分工,注意它是个循环不断的过程,它的发起人与最终接受人是相同的,所谓“从哪里来,就回哪里去”。如果我们的编程不涉及真正的硬件,StartIo例程、中断服务例程ISR、DPC例程均可能不存在。各部件的具体功能请参看资料1的第五章。

三、基本例程、常用数据结构与函数

    为正确理解各类例程的具体功能,需要弄清涉及的众多内核函数、数据结构,读者应以本文和资料1为索引,认真的阅读Msdn上的相关内容。
    我们的代码将从入口函数DriverEntry处开始执行,一般情况下,不要将它改名,否则需要修改DDK里的Build脚本。
    驱动函数定义一般采用__stdcall约定,这个约定,在vs和bcb里的实际行为是不同的。如DriverEntry,两种编译器编译后的库中名字(即外部名字)分别是_DriverEntry@8和DriverEntry。我们还习惯以IN,OUT宏显式说明函数的参数是输入或输出参数。
    DriverEntry里常见的几个例程由红字标出,包括添加(硬件、虚拟)设备函数AddDevice、驱动卸载函数DriverUnload、StartIo函数以及放置于MajorFunction数组里的派遣函数。DriverEntry还申请了分页池以保存注册表中的服务键,但作为文件系统驱动的DriverEntry,一般还会声明快速IO派遣函数,这组派遣函数没有出现在示例中。

extern "C"
NTSTATUS
DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
    DriverObject->DriverUnload = DriverUnload;         <--1
    DriverObject->DriverExtension->AddDevice = AddDevice;
    DriverObject->DriverStartIo = StartIo;
    DriverObject->MajorFunction[IRP_MJ_PNP] = DispatchPnp;         <--2
    DriverObject->MajorFunction[IRP_MJ_POWER] = DispatchPower;
    DriverObject->MajorFunction[IRP_MJ_SYSTEM_CONTROL] = DispatchWmi;
...         <--3
    servkey.Buffer = (PWSTR) ExAllocatePool(PagedPool, RegistryPath->Length + sizeof(WCHAR)); <--4
    if (!servkey.Buffer)
    {
        return STATUS_INSUFFICIENT_RESOURCES;
    }
    servkey.MaximumLength = RegistryPath->Length + sizeof(WCHAR);
// 本文转自 C++Builder研究 - http://www.ccrun.com/article.asp?i=1055&d=1m6c34
    RtlCopyUnicodeString(&servkey, RegistryPath);
    return STATUS_SUCCESS;         <--5
}

    示例同时引用了多种数据结构,它们的详细注释可参看资料1第二章的第一小节的后半部分。我们首先掌握这些数据结构的可见域,即可由编程人员存取的域,在理解一些重要的函数时也会涉及部分非透明域,一般情况下可使用微软推荐的函数来间接访问它们。
    不要混淆驱动对象和设备对象。驱动对象代表了内核加载的驱动镜像,DriverEntry或AddDevice例程调用IoCreateDevice函数来创建设备对象时,驱动对象将作为该函数的一个输入参数。设备对象作为硬件或虚拟硬件的抽象,是一个极其重要的数据结构,用于处理设备的I/O请求。
    我们将在WinDbg里实际查看这些数据结构,下面列出它们的定义图:

    内核函数根据执行的功能,大致分为如下几类,读者可通过例子接触到这些函数:

函数前缀

类别

Ex…

执行支持

Hal…

硬件抽象层(仅NT/Windows 2000/XP)

Io…

I/O管理器(包括即插即用函数)

Ke…

内核

Ks…

内核流IRP管理函数

Mm…

内存管理器

Ob…

对象管理器

Po…

电源管理(Vista下存在新的限制)

Ps…

进程结构

Rtl…

运行期库

Se…

安全

Zw…

其他函数

Cc…

Cache函数

FsRtl…

文件系统运行期库

四、WinDbg上的实践

    我们已在上一篇介绍了如何用WinDbg查看KdPrint等内核函数输出的调试信息。事实上象vs或迅雷之类的软件也会产生调试信息,但它们由用户模式下的调试输出函数发出。
    本次实践的对象是下篇将要用到的的示例代码,我们将演示如何在WinDbg里的常用操作,如设置断点,查看变量的值、数据结构等,代码位于WINDDK\3790\src\general\ioctl。如果你还不会编译驱动程序,请赶快完成上一篇拉下的作业。
    用WinDbg连接远程机,按g返还远程机的控制权,将编译好的驱动程序sioctl.sys和测试程序ioctlapp.exe复制到远程机上的任意目录里,如我们新建了一个目录c:\ioctl。
    Ctrl+break返回WinDbg后,用.cls命令清屏,延时加载bu sioctl!DriverEntry,此时输入bl查看已经设置的断点列表,WinDbg显示:

kd> bl
0 eu 0001 (0001) (sioctl!DriverEntry)

    0表示断点的id号,e表示断点的状态为允许,u表示断点未被解析,即当前加载的模块里未找到符合断点的符号。
    输入g返回控制权,在远程机里打开cmd命令提示符窗口,输入iocatlapp.exe运行程序,程序立即在DriverEntry处断下(粉红括号):


    接下来的操作其实和用户模式下的调试无大的区别,你既可以单步跟踪(F10或F11),在源代码上设置断点(F9),也可以查看变量的赋值和结构(命令dv、dt…)等,请读者随意发挥了。如用dt查看某个结构,如:

先用!pcr查看进程或线程内核对象地址,接着查看特定地址的eprocess结构内容:dt -r1 _eprocess 81bef448,尾随_eprocess的这个当前进程的地址可以用!process取得。特别关注基础篇七提及的iopm:
+0x030 IopmOffset : 0x20ac
+0x032 Iopl : 0 ''
dt -r1 _ethread 8055be40

    bl显示的内容变更为:

kd>bl
0 e f8d6a5b0 [d:\0vcprojects\ioctl\sys\sioctl.c @ 123] 0001 (0001) SIoctl!DriverEntry

    请读者据资料2与WinDbg帮助文档认真实践常用的命令,同时在网上阅读一些调试高手发表的文章。只要多实践,可以很轻松的掌握这项基本功,毕竟我们已经拥有源代码,这和通过反编译来破解信息的难度是不可相提并论的。

五、结语

    本篇是驱动编程学习过程中必须跨过的生死关,如果时间充裕,建议将资料1的第二至第五章先通读一遍,再精读两至三遍。一些不影响大局的细节(如第三章)粗通即可,无须死记;一些新的概念,如第四章的同步技术,可多花费时间尽可能努力的理解这项技术。
    驱动编程与汇编语言的学习有相似之处,入门总是先难后易。如果代码在读者的眼中和天书一般艰难,请不要怀疑自己的能力。不能理解的概念无非是因为在它之前还存在其他未知的知识,把一个庞大的论题分解为若干小块,逐步解决它们,总有豁然开朗的时候。
    本篇不设置参考完成时间,根据个人的实际情况,尽快完成入门阶段的学习。

上篇文章:文件系统驱动编程基础篇之1——我们的准备
下篇文章:浅析C++中内存分配的方式
相关搜索:
  中搜索“文件系统驱动编程基础篇之2——标准模型 ”相关内容
  中搜索“文件系统驱动编程基础篇之2——标准模型 ”相关内容
  中搜索“文件系统驱动编程基础篇之2——标准模型 ”相关内容
  中搜索“文件系统驱动编程基础篇之2——标准模型 ”相关内容
  中搜索“文件系统驱动编程基础篇之2——标准模型 ”相关内容
  中搜索“文件系统驱动编程基础篇之2——标准模型 ”相关内容
C++Builder 研究 - http://www.ccrun.com © 2001,2011  总访问量: 43667557  来访IP: 23.20.54.196  晋ICP备05000574号
Tags: Borland CodeGear Embarcadero C++Builder Delphi VC++ C/C++ RAD Studio BCB BDS Source Code VCL MFC COM SDK Components Controls Developer Programmer 编程学习资料 源代码 源程序 源码 编程文档 经验技巧 组件 控件 元件 开源 函数 软件开发 一切尽在C++Builder研究!