|
珞珈山水BBS →
电脑网络 →
程序人生 →
单文区文章阅读
|
| 单文区文章阅读 [返回] |
|---|
|
发信人: Stravadivaly (老子就是机器人), 信区: Programm 标 题: 伪驱动初级教程2 发信站: BBS 珞珈山水站 (Fri Sep 1 13:33:44 2006) 伪驱动初级教程2 By Stravadivaly 前面是一些准备工作,现在,我们开始了解驱动程序的编写。 1.一个驱动Hello World!(不要怕,后面有讲解) #include <ntddk.h> #include <stdarg.h> #include <stdio.h> //这个宏是我们用来构造控制代码的,至于为什么会是这样,DDk的帮助文档中有 #define CTL_CODE( DeviceType, Function, Method, Access ) ( \ ((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method) \ ) //下面定义我们的控制代码 注(1) #define IOCTL_MYDDK_HELLOWORLD CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,\ METHOD_BUFFERED,FILE_READ_DATA | FILE_WRITE_DA TA) ////////////////DriveDisPatch//////////// 注(2) NTSTATUS DriverDispatch(IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp){ PIO_STACK_LOCATION irpStack; NTSTATUS ntStatus; irpStack = IoGetCurrentIrpStackLocation (Irp); switch (irpStack->MajorFunction){ //注(3) //当我们调用DeviceIoControl的时候,就会产生IRP_MJ_DEVICE_CONTROL //在调用CreateFile的时候,会产生IRP_MJ_CREATE case IRP_MJ_DEVICE_CONTROL: switch (irpStack->Parameters.DeviceIoControl.IoControlCode){ case IOCTL_MYDDK_HELLOWORLD: KdPrint(("Hello World!\n")); //注(4) break; } break; } Irp->IoStatus.Status= ntStatus ; Irp->IoStatus.Information = 0; IoCompleteRequest (Irp, IO_NO_INCREMENT); return ntStatus; } ///////// 释放DriverEntry例程在全局初始化过程中申请的任何资源 //////注(5) VOID DriverUnload(IN PDRIVER_OBJECT DriverObject) { WCHAR deviceLinkBuffer[] = L"\DosDevices\MyDdk0"; UNICODE_STRING deviceLinkUnicodeString; RtlInitUnicodeString (&deviceLinkUnicodeString,deviceLinkBuffer); IoDeleteSymbolicLink (&deviceLinkUnicodeString); IoDeleteDevice (DriverObject->DeviceObject); } ///////////////////// 驱动入口 //////////////////////// //要注意的是, 驱动程序的某些全局初始化操作只能在第一次被装入时执行一次。 //而 DriverEntry 就是用于这个目的。对于我们这样的伪驱动程序,当然不会被多次加载 , //舍我其谁? NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject,IN PUNICODE_STRING Regist ryPath) {//驱动对象由参数传入,很象 WIn32 中 WinMain 的 INSTANCE参数 PDEVICE_OBJECT deviceObject = NULL;//注意,这就是新建的设备对象 NTSTATUS ntStatus; //为设备和连接命名, 通常都在Device下 WCHAR deviceNameBuffer[] = L"\Device\MyDdk0"; //注(6a) UNICODE_STRING deviceNameUnicodeString; WCHAR deviceLinkBuffer[] = L"\DosDevices\MyDdk0";//注(6) UNICODE_STRING deviceLinkUnicodeString; //下面的函数用deviceNameBuffer初始化deviceNameUnicodeString。 //注意,deviceNameUnicodeString是一个UNICODED_STRING 类型。 RtlInitUnicodeString (&deviceNameUnicodeString, deviceNameBuffer); //创建设备对象 ntStatus = IoCreateDevice (DriverObject,0,&deviceNameUnicodeString,FIL E_DEVICE_UNKNOWN,0,FALSE,&deviceObject); if (NT_SUCCESS(ntStatus)) { RtlInitUnicodeString (&deviceLinkUnicodeString,deviceLinkBuffer); //创建对象连接,目的就是把名字DeviceName和设备联系起来.如果命名了设 备对象, //那么任何内核模式程序都可以打开该设备的句柄。另外, //任何内核模式或用户模式程序都能创建连接到该设备的符号连接, //并可以使用这个符号连接打开设备的句柄。也就是说在用户模式下面也可 以访问了。 ntStatus = IoCreateSymbolicLink (&deviceLinkUnicodeString,&deviceN ameUnicodeString); if (!NT_SUCCESS(ntStatus)) { IoDeleteDevice (deviceObject); return ntStatus; } //所有的驱动程序函数指针都指向 DriverDispatch 函数。 //这样可以省很多事。当然,做真正的驱动就不能这样偷懒了。 DriverObject->MajorFunction[IRP_MJ_CREATE] = DriverDispatch; DriverObject->MajorFunction[IRP_MJ_CLOSE] = DriverDispatch; DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DriverDispatc h; //注(7) DriverObject->DriverUnload = DriverUnload; return STATUS_SUCCESS; } else { return ntStatus; } } 2.一些注解: 好了,保存这个文件,就可以编译这个文件了。用教程1中的insdrv来安装驱动。什么 都没有出现,是么?你可能会问,为什么注(4)那里的KdPrint没有把Hello World!输出到 命令行下? 原因就是,kdPrint只是输出一些调试信息,我们要看 Hello World!要到调 试工具(softice)中看,另一种方法就是用 DriverStdio 中的DbgView。我通常不用它,因 为既然是调试,我就不能保证不会蓝屏(BSOD,Blue Screen of Death), 一旦蓝了,还 看个什么? 不如就用调试器一步步走。 还要解释的东西很多,我们一个个来。先看看整个结构: NTSTATUS DriverDispatch(IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp) VOID DriverUnload(IN PDRIVER_OBJECT DriverObject) NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject,IN PUNICODE_STRING Re gistryPath) 最后一个函数类似我们Windows程序中的“winmain”,C 中的“main”,更像Dll中的“D llMain”。它是个入口函数,参数由系统填充,我们关注的是第一个驱动对象。每个驱动 只有一个驱动对象,但可以有多个设备对象。如果你想硬件驱动发展的话,最好弄清楚两 者得的区别。 中间那个函数一看名字就知道,是在驱动卸载的时候做清道夫的。对于伪驱动,我们只用 释放几个资源(我们申请的设备,和字串)。 第一个函数类似Windows中的消息回调函数。当你在DriverEntry中注(7) 处指定了IRP(稍 候解释)的处理程序后,我们的驱动程序执行到return 就“销声匿迹”了。当我们的用户 程序调用DeviceIoControl的时候,系统根据IRP调用不同的例程,我们这里都是调用Driv erDispatch,参数都是由系统搞定的。我们常常在DriverEntrty这里做一些初始化的工作 ,比如创建设备,连接设备与设备名,设置IRP处理例程等等。要注意的是,当我们安装了 驱动之后,DriverEntrty在驱动卸载之前只执行一次,也就是驱动安装时的那一次,如果 我们调用DeviceIoControl,DriverEntry市不会执行的。 IRP, 它很像是Windows编程里面的消息。有各种各样的IRP, 我们也可以定义自己的 IRP,就像注(1)处的定义一样。要根据不同的IRP处理不同的事情,我们首先要得到IRP 。 当然,IRP也是由系统给我们的。IRP是个很胖的结构,细节大家自己打网上找找。同时任 何内核模式程序在创建一个IRP时,同时还创建了一个与之关联的IO_STACK_LOCATION结构 数组:数组中的每个堆栈单元都对应一个将处理该IRP的驱动程序,另外还有一个堆栈单元 供IRP的创建者使用。堆栈单元中包含该IRP的类型代码和参数信息以及完成函数的地址。 要获得这个结构数组,可以调用IoGetCurrentIrpStackLocation函数。具体使用就参见上 面的DriverDispatch 代码了。 好了,还记得上次我们的Source文件么,我们说他的第一行是可以修改的,改成什么?就改 成注(7)处的"MyDdk0". 当然你也可以把这里改成你想要的名字. 注意:我们这里有两个名字 ,注(6a) 和 注(6)两个地方.如果你把两者改成不同的名字 的话(不全是MyDdk0),用上次提到的instdrv是不能安装的.因为instdrv默认这两个名字是 相同的. 3.整个驱动程序最简单的框架,大概就是上面这样,你可以把它存成模版,每次就不用重 复劳动了。但建议还是您亲手手敲一次, 你可能会遇到编译不过或者BSOD,虽然有点惨, 但是你会学到更多。 -- 是吗? 不是吗? 对吗? 不对吗? 傻吗? ...... 的确很傻. 就像" 树动风欲静, 日涌大山流". ※ 来源:·珞珈山水BBS站 http://bbs.whu.edu.cn·[FROM: 218.247.215.*] |
| [返回单文区目录] |
|
|