珞珈山水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.*]
[返回单文区目录]

武汉大学BBS 珞珈山水站 All rights reserved.
wForum , 页面执行时间:22.624毫秒