标签: 杂谈 |
下面是个例子,代码作者是张帆,我来详细解释,从中遇到知识点,我延伸出来分析。
忘记说了,前些日子,公司搬家,好了之后有累出病了,之后休息年假回老家,就这样折腾了好久好久,博客也忘记写了。实在不好意思~~~OK,继续来吧
**************************
Driver.h
*****************************
#pragma once
#ifdef __cplusplus
extern "C"
{
#endif
#include
#ifdef __cplusplus
}
#endif
#define PAGEDCODE code_seg("PAGE")
#define LOCKEDCODE code_seg()
#define INITCODE code_seg("INIT")
#define PAGEDDATA data_seg("PAGE")
#define LOCKEDDATA data_seg()
#define INITDATA data_seg("INIT")
#define arraysize(p) (sizeof(p)/sizeof((p)[0]))
typedef struct _DEVICE_EXTENSION {
PDEVICE_OBJECT pDevice;
UNICODE_STRING ustrDeviceName; //设备名称
UNICODE_STRING ustrSymLinkName; //符号链接名
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;
// 函数声明
NTSTATUS CreateDevice (IN PDRIVER_OBJECT pDriverObject);
VOID HelloDDKUnload (IN PDRIVER_OBJECT pDriverObject);
NTSTATUS HelloDDKDispatchRoutine(IN PDEVICE_OBJECT pDevObj,
IN PIRP pIrp);
******************************
Driver.cpp
****************************
#include "Driver.h"
#pragma INITCODE
extern "C" NTSTATUS DriverEntry (
IN PDRIVER_OBJECT pDriverObject,
IN PUNICODE_STRING pRegistryPath )
{
NTSTATUS status;
KdPrint(("Enter DriverEntry\n"));
//注册其他驱动调用函数入口
pDriverObject->DriverUnload = HelloDDKUnload;
pDriverObject->MajorFunction[IRP_MJ_CREATE] = HelloDDKDispatchRoutine;
pDriverObject->MajorFunction[IRP_MJ_CLOSE] = HelloDDKDispatchRoutine;
pDriverObject->MajorFunction[IRP_MJ_WRITE] = HelloDDKDispatchRoutine;
pDriverObject->MajorFunction[IRP_MJ_READ] = HelloDDKDispatchRoutine;
//创建驱动设备对象
status = CreateDevice(pDriverObject);
KdPrint(("DriverEntry end\n"));
return status;
}
#pragma INITCODE
NTSTATUS CreateDevice (
IN PDRIVER_OBJECT pDriverObject)
{
NTSTATUS status;
PDEVICE_OBJECT pDevObj;
PDEVICE_EXTENSION pDevExt;
//创建设备名称
UNICODE_STRING devName;
RtlInitUnicodeString(&devName,L"\\Device\\MyDDKDevice");
//创建设备
status = IoCreateDevice( pDriverObject,
sizeof(DEVICE_EXTENSION),
&(UNICODE_STRING)devName,
FILE_DEVICE_UNKNOWN,
0, TRUE,
&pDevObj );
if (!NT_SUCCESS(status))
return status;
pDevObj->Flags |= DO_BUFFERED_IO;
pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;
pDevExt->pDevice = pDevObj;
pDevExt->ustrDeviceName = devName;
//创建符号链接
UNICODE_STRING symLinkName;
RtlInitUnicodeString(&symLinkName,L"\\??\\HelloDDK");
pDevExt->ustrSymLinkName = symLinkName;
status = IoCreateSymbolicLink( &symLinkName,&devName );
if (!NT_SUCCESS(status))
{
IoDeleteDevice( pDevObj );
return status;
}
return STATUS_SUCCESS;
}
#pragma PAGEDCODE
VOID HelloDDKUnload (IN PDRIVER_OBJECT pDriverObject)
{
PDEVICE_OBJECT pNextObj;
KdPrint(("Enter DriverUnload\n"));
pNextObj = pDriverObject->DeviceObject;
while (pNextObj != NULL)
{
PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)
pNextObj->DeviceExtension;
//删除符号链接
UNICODE_STRING pLinkName = pDevExt->ustrSymLinkName;
IoDeleteSymbolicLink(&pLinkName);
pNextObj = pNextObj->NextDevice;
IoDeleteDevice( pDevExt->pDevice );
}
}
#pragma PAGEDCODE
NTSTATUS HelloDDKDispatchRoutine(IN PDEVICE_OBJECT pDevObj,
IN PIRP pIrp)
{
KdPrint(("Enter HelloDDKDispatchRoutine\n"));
NTSTATUS status = STATUS_SUCCESS;
// 完成IRP
pIrp->IoStatus.Status = status;
pIrp->IoStatus.Information = 0; // bytes xfered
IoCompleteRequest( pIrp, IO_NO_INCREMENT );
KdPrint(("Leave HelloDDKDispatchRoutine\n"));
return status;
}
开始分段来解释:**************************此段是头文件*******************************
#pragma once //这个比较常用,其意思就是此头文件只能被编译一次
#ifdef __cplusplus //如果将来包含此头文件的代码是C++代码,那么
extern "C" //用C语言方式来编译
{
#endif
#include
#ifdef __cplusplus
} // 结束这个如果(下面详细解释为什么要用C语言编译方式)
#endif
#define PAGEDCODE code_seg("PAGE") //code_seg("PAGE")说明其后的函数作为可以放入分页内存
#define LOCKEDCODE code_seg() //code_seg()说明其后的函数必须常驻内存
#define INITCODE code_seg("INIT") //code_seg("INIT")说明其后的函数执行好了之后回收其占用的内存空间
#define PAGEDDATA data_seg("PAGE") //和上面差不多,现在不是针对代码,而是针对数据
#define LOCKEDDATA data_seg()
#define INITDATA data_seg("INIT")
#define arraysize(p) (sizeof(p)/sizeof((p)[0])) //这里也是个宏,仔细看下里面的p应该是一维数组的指针,arraysize的作用就是算出数组里有多少个元素
typedef struct _DEVICE_EXTENSION { //定义一个设备扩展结构,这个很重要,下面我详细解释
PDEVICE_OBJECT pDevice;
UNICODE_STRING ustrDeviceName; //设备名称
UNICODE_STRING ustrSymLinkName; //符号链接名
} DEVICE_EXTENSION, *PDEVICE_EXTENSION; //设备扩展结构定义结束
// 函数声明
NTSTATUS CreateDevice (IN PDRIVER_OBJECT pDriverObject); //定义一个私有函数CreateDevice
VOID HelloDDKUnload (IN PDRIVER_OBJECT pDriverObject); //定义一个驱动程序卸载函数
NTSTATUS HelloDDKDispatchRoutine(IN PDEVICE_OBJECT pDevObj, //定义一个分发函数
IN PIRP pIrp);
****************************************头文件分析结束*******************************
看到这肯定大家一头雾水,不要紧,继续分析CPP文件,最后会让大家理解清楚。继续
******************************
Driver.cpp
****************************
#include "Driver.h" //包含头文件
#pragma INITCODE //刚才头文件里已经分析过了,这个的意思是让DriverEntry函数执行后之后就离开内存,别让它占用宝贵的内存资源
extern "C" NTSTATUS DriverEntry ( //DriverEntry函数是驱动程序的入口函数,和WinMain一样,微软就这样规定的。不需要解释
IN PDRIVER_OBJECT pDriverObject, //系统会传入一个Driver结构的指针,相信解释下面会讲到
IN PUNICODE_STRING pRegistryPath )
{
NTSTATUS status; //申明一个状态变量,NTSTATUS就是状态型数据
KdPrint(("Enter DriverEntry\n")); //输出一段字符,用WINDBG工具可以看到,具体说明下面详解
//注册其他驱动调用函数入口
pDriverObject->DriverUnload = HelloDDKUnload; //刚才系统传入一个Driver结构的指针,那么下面这些语句就是给此结构里面的成员进行“赋值”。让这个本来空荡荡的结构“饱满”起来
pDriverObject->MajorFunction[IRP_MJ_CREATE] = HelloDDKDispatchRoutine;
pDriverObject->MajorFunction[IRP_MJ_CLOSE] = HelloDDKDispatchRoutine;
pDriverObject->MajorFunction[IRP_MJ_WRITE] = HelloDDKDispatchRoutine;
pDriverObject->MajorFunction[IRP_MJ_READ] = HelloDDKDispatchRoutine;
//创建驱动设备对象
status = CreateDevice(pDriverObject); //调用私有函数,请大家转到下面这个函数部分
KdPrint(("DriverEntry end\n"));
return status;
}
#pragma INITCODE //说明下面CreateDevice函数和DriverEntry函数一样,执行好了立刻“消失”
NTSTATUS CreateDevice (
IN PDRIVER_OBJECT pDriverObject) //把Driver结构的指针传入,为什么要传入进来呢?因为要用到。这样的解释很爽快吧 哈哈
{
NTSTATUS status; //上面解释过了
PDEVICE_OBJECT pDevObj; //定义一个设备对象结构的指针,很重要,下面详解
PDEVICE_EXTENSION pDevExt; //定义一个设备扩展对象 设备扩展结构在头文件里已经定义
//创建设备名称
UNICODE_STRING devName; //这个大家记住,驱动程序使用的是DDK。MFC编程中我们用CString类来定义一个字符串,而在驱动程序中,字符串是UNICODE_STRING结构,下面详细解释,目前你只要知道以后定义字符串就用它,还是它是个结构就OK
RtlInitUnicodeString(&devName,L"\\Device\\MyDDKDevice"); //初始化这个字符串,注意这个函数以及这个函数的用法。第一个参数是UNICODE_STRING结构的指针,第2个参数就是具体的字符串了。
//创建设备
status = IoCreateDevice( pDriverObject, //此函数是DDK函数,它的作用是创建一个设备,第一个参数说明此设备属于哪个驱动程序。这句话千万记得,下面详解
sizeof(DEVICE_EXTENSION), //设备扩展的大小
&(UNICODE_STRING)devName, //设备名字
FILE_DEVICE_UNKNOWN, //是个什么设备呢,USB,还是串口?不知道,那么就写“不知道”
0, TRUE, //暂时不解释
&pDevObj ); //刚才定义了一个设备的指针,再次取地址作为参数。哈哈,很多人理解出现僵局了,为什么已经是指针了,还再取地址呢?OK,下面详细解释。指针这关必须吃透
if (!NT_SUCCESS(status)) //如果刚才创建设备不成功
return status; //那么返回状态,一般都是成功的
pDevObj->Flags |= DO_BUFFERED_IO; //此设备的输入输出方式为 DO_BUFFERED_IO,下面详解
pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension; //设备对象结构里有个成员叫做设备扩展,此成员也是个结构,用pDevExt指向这个设备扩展结构
pDevExt->pDevice = pDevObj; //你到头文件里看看设备扩展结构的定义,这个地方很容易理解
pDevExt->ustrDeviceName = devName;
//创建符号链接
UNICODE_STRING symLinkName; //定义一个字符串变量
RtlInitUnicodeString(&symLinkName,L"\\??\\HelloDDK"); //初始化
pDevExt->ustrSymLinkName = symLinkName; //继续“填补”设备扩展结构,也就是为其成员变量赋值
status = IoCreateSymbolicLink( &symLinkName,&devName ); //DDK函数,创建一个符号链接,下面详解
if (!NT_SUCCESS(status)) //如果不成功
{
IoDeleteDevice( pDevObj ); //把刚才建立的设备删除
return status;
}
return STATUS_SUCCESS; //返回成功
}
#pragma PAGEDCODE //HelloDDKUnload函数可以在分页到硬盘,此函数不必常驻内存
VOID HelloDDKUnload (IN PDRIVER_OBJECT pDriverObject)
{
PDEVICE_OBJECT pNextObj; //定义一个设备
KdPrint(("Enter DriverUnload\n")); //输出一段字符串
pNextObj = pDriverObject->DeviceObject; //Driver结构中有个Device设备结构,用pNextObj指向它
while (pNextObj != NULL)
{
PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)
pNextObj->DeviceExtension; //得到设备扩展,为什么?因为只有得到设备扩展,才能得到设备扩展里的符号链接,因为符号链接一会要删除。
//删除符号链接
UNICODE_STRING pLinkName = pDevExt->ustrSymLinkName; //得到符号链接
IoDeleteSymbolicLink(&pLinkName); //删除符号链接
pNextObj = pNextObj->NextDevice; //得到设备指针
IoDeleteDevice( pDevExt->pDevice ); //删除设备
}
}
#pragma PAGEDCODE
NTSTATUS HelloDDKDispatchRoutine(IN PDEVICE_OBJECT pDevObj,
IN PIRP pIrp) //此函数为驱动的分发函数
{
KdPrint(("Enter HelloDDKDispatchRoutine\n"));
NTSTATUS status = STATUS_SUCCESS;
// 完成IRP
pIrp->IoStatus.Status = status; //IRP很重要,一会详解
pIrp->IoStatus.Information = 0; // bytes xfered
IoCompleteRequest( pIrp, IO_NO_INCREMENT );
KdPrint(("Leave HelloDDKDispatchRoutine\n"));
return status;
}
*****************************************
结束。哈哈哈 ,迷茫了吧,肯定N多问题要问吧。别急,下面章节开始细嚼慢咽,不过现在屡下流程还是必要的。
1,我们知道驱动程序的入口函数为DriverEntry()
2,驱动程序是由服务控制管理器加载的,关于服务控制管理器,我前面的章节已经介绍过。
3,之后系统在内存中创建一个Drvier结构,此时此刻内核中就多了一个Driver结构体,但是这个结构体只是一个空架子,里面的成员并没有赋值
4,I/O管理器调用DriverEntry()这个入口函数,并且把Drvier结构结构传进来
5,你看看上面的代码,你会发现,入口函数里很多代码就是在那为Drvier结构里的成员“赋值”
6,入口函数里还创建了一个设备对象,并且这个设备对象还带有设备扩展,并且还创建这个设备的符号链接
7,前面章节我已经提到过IRP,并且还说过IRP就像个短信,要注意的是只有设备才能接收IRP。你编写驱动程序的目的是什么?不管是硬盘驱动还是显卡驱动,还是键盘驱动,他们有一点是一样的,就是为应用程序服务,因此这些驱动也是会创建设备的。你的驱动程序如果也服务于应用程序,那么就必须创建设备。服务于应用程序的驱动很多很多,比如说杀毒软件,你所能看到的是它的应用程序部分,它的驱动程序部分运行在内核,应用程序可以发送控制信息来控制驱动程序。当然啦,如果你的驱动程序只是挂钩系统函数或者干些其他事情,那么可以不创建设备。设备扩展的用途比较广泛,设备扩展结构可以自己来定义,为什么要有这个东西呢?在内核中最好不要用全局变量,那么如果你实在打算要全局变量,那么就定义到设备扩展结构里,你仔细观察上面的代码,你会发现,驱动对象结构里面有个成员指向此驱动程序创建的设备对象结构,并且设备对象结构里有个成员指向设备扩展结构。你还会发现,上面所有的函数的参数要么是驱动对象指针,要么是设备对象指针。因此不管是哪个,我们都可以直接或间接的得到设备扩展结构,从而可以得到里面的成员变量。如果我们把一个变量放入设备扩展中,那么这个变量和全局变量没有任何区别,任何例程都可以直接或间接的得到它,修改它。这就是设备扩展的用途,说白了就这么简单。
8,设备链接的作用就是提供一个接口给应用程序,应用程序可以通过这个链接名来控制此设备。下面详解
此节到此结束,下面开始详细介绍各个细节。谢谢
驱动程序4---逐步了解驱动程序(2009-12-20 03:32:27)
上一节笔者提供了一段源代码,目的就是让大伙稍微熟悉下。因此笔者并没有详细解释其中各各语句的作用和意思。细心的读者其实也发现了,上一节我并没有打算把它作为一个教程来看待,因此我没有注明节号。OK,今天是第4节。我先送上楚狂人的一个关于键盘过滤的源代码,让大家熟悉熟悉一个真正有实际作用的驱动程序的框架。之后笔者将会暂时放开驱动程序代码的分析,转而继续探讨更进一步的“基础”知识。其中包括内存,指针,IRP,结构,等等。
想必大家都听说过ROOTKIT技术,难吗? 难!******** 真的难道无法入手并且到了无法理解的地步吗?不见得!********张无忌为什么学乾坤大挪移只用了区区几小时?乾坤大挪移难吗?难!那么为什么张无忌很快就练成了?因为他任督二脉已经打通,他有了一定的基础了! OK,下面笔者的任务就是尽力协助大家打通任督二脉,助大家跨进驱动大门。享受不受约束的快感。
下面代码来源于寒江独钓,再次感谢楚大哥~~~~
///
/// @file ctrl2cap.c
/// @author wowocock
/// @date 2009-1-27
///
#include
// Kbdclass驱动的名字
#define KBD_DRIVER_NAME L"\\Driver\\Kbdclass"
typedef struct _C2P_DEV_EXT
{
// 这个结构的大小
ULONG NodeSize;
// 过滤设备对象
PDEVICE_OBJECT pFilterDeviceObject;
// 同时调用时的保护锁
KSPIN_LOCK IoRequestsSpinLock;
// 进程间同步处理
KEVENT IoInProgressEvent;
// 绑定的设备对象
PDEVICE_OBJECT TargetDeviceObject;
// 绑定前底层设备对象
PDEVICE_OBJECT LowerDeviceObject;
} C2P_DEV_EXT, *PC2P_DEV_EXT;
NTSTATUS
c2pDevExtInit(
IN PC2P_DEV_EXT devExt,
IN PDEVICE_OBJECT pFilterDeviceObject,
IN PDEVICE_OBJECT pTargetDeviceObject,
IN PDEVICE_OBJECT pLowerDeviceObject )
{
memset(devExt, 0, sizeof(C2P_DEV_EXT));
devExt->NodeSize = sizeof(C2P_DEV_EXT);
devExt->pFilterDeviceObject = pFilterDeviceObject;
KeInitializeSpinLock(&(devExt->IoRequestsSpinLock));
KeInitializeEvent(&(devExt->IoInProgressEvent), NotificationEvent, FALSE);
devExt->TargetDeviceObject = pTargetDeviceObject;
devExt->LowerDeviceObject = pLowerDeviceObject;
return( STATUS_SUCCESS );
}
// 这个函数是事实存在的,只是文档中没有公开。声明一下
// 就可以直接使用了。
NTSTATUS
ObReferenceObjectByName(
PUNICODE_STRING ObjectName,
ULONG Attributes,
PACCESS_STATE AccessState,
ACCESS_MASK DesiredAccess,
POBJECT_TYPE ObjectType,
KPROCESSOR_MODE AccessMode,
PVOID ParseContext,
PVOID *Object
);
extern POBJECT_TYPE IoDriverObjectType;
ULONG gC2pKeyCount = 0;
PDRIVER_OBJECT gDriverObject = NULL;
// 这个函数经过改造。能打开驱动对象Kbdclass,然后绑定
// 它下面的所有的设备:
NTSTATUS
c2pAttachDevices(
IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath
)
{
NTSTATUS status = 0;
UNICODE_STRING uniNtNameString;
PC2P_DEV_EXT devExt;
PDEVICE_OBJECT pFilterDeviceObject = NULL;
PDEVICE_OBJECT pTargetDeviceObject = NULL;
PDEVICE_OBJECT pLowerDeviceObject = NULL;
PDRIVER_OBJECT KbdDriverObject = NULL;
KdPrint(("MyAttach\n"));
// 初始化一个字符串,就是Kdbclass驱动的名字。
RtlInitUnicodeString(&uniNtNameString, KBD_DRIVER_NAME);
// 请参照前面打开设备对象的例子。只是这里打开的是驱动对象。
status = ObReferenceObjectByName (
&uniNtNameString,
OBJ_CASE_INSENSITIVE,
NULL,
0,
IoDriverObjectType,
KernelMode,
NULL,
&KbdDriverObject
);
// 如果失败了就直接返回
if(!NT_SUCCESS(status))
{
KdPrint(("MyAttach: Couldn't get the MyTest Device Object\n"));
return( status );
}
else
{
// 这个打开需要解应用。早点解除了免得之后忘记。
ObDereferenceObject(DriverObject);
}
// 这是设备链中的第一个设备
pTargetDeviceObject = KbdDriverObject->DeviceObject;
// 现在开始遍历这个设备链
while (pTargetDeviceObject)
{
// 生成一个过滤设备,这是前面读者学习过的。这里的IN宏和OUT宏都是
// 空宏,只有标志性意义,表明这个参数是一个输入或者输出参数。
status = IoCreateDevice(
IN DriverObject,
IN sizeof(C2P_DEV_EXT),
IN NULL,
IN pTargetDeviceObject->DeviceType,
IN pTargetDeviceObject->Characteristics,
IN FALSE,
OUT &pFilterDeviceObject
);
// 如果失败了就直接退出。
if (!NT_SUCCESS(status))
{
KdPrint(("MyAttach: Couldn't create the MyFilter Filter Device Object\n"));
return (status);
}
// 绑定。pLowerDeviceObject是绑定之后得到的下一个设备。也就是
// 前面常常说的所谓真实设备。
pLowerDeviceObject =
IoAttachDeviceToDeviceStack(pFilterDeviceObject, pTargetDeviceObject);
// 如果绑定失败了,放弃之前的操作,退出。
if(!pLowerDeviceObject)
{
KdPrint(("MyAttach: Couldn't attach to MyTest Device Object\n"));
IoDeleteDevice(pFilterDeviceObject);
pFilterDeviceObject = NULL;
return( status );
}
// 设备扩展!下面要详细讲述设备扩展的应用。
devExt = (PC2P_DEV_EXT)(pFilterDeviceObject->DeviceExtension);
c2pDevExtInit(
devExt,
pFilterDeviceObject,
pTargetDeviceObject,
pLowerDeviceObject );
// 下面的操作和前面过滤串口的操作基本一致。这里不再解释了。
pFilterDeviceObject->DeviceType=pLowerDeviceObject->DeviceType;
pFilterDeviceObject->Characteristics=pLowerDeviceObject->Characteristics;
pFilterDeviceObject->StackSize=pLowerDeviceObject->StackSize+1;
pFilterDeviceObject->Flags |= pLowerDeviceObject->Flags & (DO_BUFFERED_IO | DO_DIRECT_IO | DO_POWER_PAGABLE) ;
//next device
pTargetDeviceObject = pTargetDeviceObject->NextDevice;
}
return status;
}
VOID
c2pDetach(IN PDEVICE_OBJECT pDeviceObject)
{
PC2P_DEV_EXT devExt;
BOOLEAN NoRequestsOutstanding = FALSE;
devExt = (PC2P_DEV_EXT)pDeviceObject->DeviceExtension;
__try
{
__try
{
IoDetachDevice(devExt->TargetDeviceObject);
devExt->TargetDeviceObject = NULL;
IoDeleteDevice(pDeviceObject);
devExt->pFilterDeviceObject = NULL;
DbgPrint(("Detach Finished\n"));
}
__except (EXCEPTION_EXECUTE_HANDLER){}
}
__finally{}
return;
}
#define DELAY_ONE_MICROSECOND (-10)
#define DELAY_ONE_MILLISECOND (DELAY_ONE_MICROSECOND*1000)
#define DELAY_ONE_SECOND (DELAY_ONE_MILLISECOND*1000)
VOID
c2pUnload(IN PDRIVER_OBJECT DriverObject)
{
PDEVICE_OBJECT DeviceObject;
PDEVICE_OBJECT OldDeviceObject;
PC2P_DEV_EXT devExt;
LARGE_INTEGER lDelay;
PRKTHREAD CurrentThread;
//delay some time
lDelay = RtlConvertLongToLargeInteger(100 * DELAY_ONE_MILLISECOND);
CurrentThread = KeGetCurrentThread();
// 把当前线程设置为低实时模式,以便让它的运行尽量少影响其他程序。
KeSetPriorityThread(CurrentThread, LOW_REALTIME_PRIORITY);
UNREFERENCED_PARAMETER(DriverObject);
KdPrint(("DriverEntry unLoading...\n"));
// 遍历所有设备并一律解除绑定
DeviceObject = DriverObject->DeviceObject;
while (DeviceObject)
{
// 解除绑定并删除所有的设备
c2pDetach(DeviceObject);
DeviceObject = DeviceObject->NextDevice;
}
ASSERT(NULL == DriverObject->DeviceObject);
while (gC2pKeyCount)
{
KeDelayExecutionThread(KernelMode, FALSE, &lDelay);
}
KdPrint(("DriverEntry unLoad OK!\n"));
return;
}
NTSTATUS c2pDispatchGeneral(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
{
// 其他的分发函数,直接skip然后用IoCallDriver把IRP发送到真实设备
// 的设备对象。
KdPrint(("Other Diapatch!"));
IoSkipCurrentIrpStackLocation(Irp);
return IoCallDriver(((PC2P_DEV_EXT)
DeviceObject->DeviceExtension)->LowerDeviceObject, Irp);
}
NTSTATUS c2pPower(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
{
PC2P_DEV_EXT devExt;
devExt =
(PC2P_DEV_EXT)DeviceObject->DeviceExtension;
PoStartNextPowerIrp( Irp );
IoSkipCurrentIrpStackLocation( Irp );
return PoCallDriver(devExt->LowerDeviceObject, Irp );
}
NTSTATUS c2pPnP(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
{
PC2P_DEV_EXT devExt;
PIO_STACK_LOCATION irpStack;
NTSTATUS status = STATUS_SUCCESS;
KIRQL oldIrql;
KEVENT event;
// 获得真实设备。
devExt = (PC2P_DEV_EXT)(DeviceObject->DeviceExtension);
irpStack = IoGetCurrentIrpStackLocation(Irp);
switch (irpStack->MinorFunction)
{
case IRP_MN_REMOVE_DEVICE:
KdPrint(("IRP_MN_REMOVE_DEVICE\n"));
// 首先把请求发下去
IoSkipCurrentIrpStackLocation(Irp);
IoCallDriver(devExt->LowerDeviceObject, Irp);
// 然后解除绑定。
IoDetachDevice(devExt->LowerDeviceObject);
// 删除我们自己生成的虚拟设备。
IoDeleteDevice(DeviceObject);
status = STATUS_SUCCESS;
break;
default:
// 对于其他类型的IRP,全部都直接下发即可。
IoSkipCurrentIrpStackLocation(Irp);
status = IoCallDriver(devExt->LowerDeviceObject, Irp);
}
return status;
}
// 这是一个IRP完成回调函数的原型
NTSTATUS c2pReadComplete(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp,
IN PVOID Context
)
{
PIO_STACK_LOCATION IrpSp;
ULONG buf_len = 0;
PUCHAR buf = NULL;
size_t i;
IrpSp = IoGetCurrentIrpStackLocation( Irp );
// 如果这个请求是成功的。很显然,如果请求失败了,这么获取
// 进一步的信息是没意义的。
if( NT_SUCCESS( Irp->IoStatus.Status ) )
{
// 获得读请求完成后输出的缓冲区
buf = Irp->AssociatedIrp.SystemBuffer;
// 获得这个缓冲区的长度。一般的说返回值有多长都保存在
// Information中。
buf_len = Irp->IoStatus.Information;
//… 这里可以做进一步的处理。我这里很简单的打印出所有的扫
// 描码。
for(i=0;i
DbgPrint("ctrl2cap: %2x\r\n", buf[i]);
}
}
gC2pKeyCount--;
if( Irp->PendingReturned )
{
IoMarkIrpPending( Irp );
}
return Irp->IoStatus.Status;
}
NTSTATUS c2pDispatchRead(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp )
{
NTSTATUS status = STATUS_SUCCESS;
PC2P_DEV_EXT devExt;
PIO_STACK_LOCATION currentIrpStack;
KEVENT waitEvent;
KeInitializeEvent( &waitEvent, NotificationEvent, FALSE );
if (Irp->CurrentLocation == 1)
{
ULONG ReturnedInformation = 0;
KdPrint(("Dispatch encountered bogus current location\n"));
status = STATUS_INVALID_DEVICE_REQUEST;
Irp->IoStatus.Status = status;
Irp->IoStatus.Information = ReturnedInformation;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return(status);
}
// 全局变量键计数器加1
gC2pKeyCount++;
// 得到设备扩展。目的是之后为了获得下一个设备的指针。
devExt =
(PC2P_DEV_EXT)DeviceObject->DeviceExtension;
// 设置回调函数并把IRP传递下去。 之后读的处理也就结束了。
// 剩下的任务是要等待读请求完成。
currentIrpStack = IoGetCurrentIrpStackLocation(Irp);
IoCopyCurrentIrpStackLocationToNext(Irp);
IoSetCompletionRoutine( Irp, c2pReadComplete,
DeviceObject, TRUE, TRUE, TRUE );
return IoCallDriver( devExt->LowerDeviceObject, Irp );
}
NTSTATUS DriverEntry(
IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath
)
{
ULONG i;
NTSTATUS status;
KdPrint (("c2p.SYS: entering DriverEntry\n"));
// 填写所有的分发函数的指针
for (i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)
{
DriverObject->MajorFunction[i] = c2pDispatchGeneral;
}
// 单独的填写一个Read分发函数。因为要的过滤就是读取来的按键信息
// 其他的都不重要。这个分发函数单独写。
DriverObject->MajorFunction[IRP_MJ_READ] = c2pDispatchRead;
// 单独的填写一个IRP_MJ_POWER函数。这是因为这类请求中间要调用
// 一个PoCallDriver和一个PoStartNextPowerIrp,比较特殊。
DriverObject->MajorFunction [IRP_MJ_POWER] = c2pPower;
// 我们想知道什么时候一个我们绑定过的设备被卸载了(比如从机器上
// 被拔掉了?)所以专门写一个PNP(即插即用)分发函数
DriverObject->MajorFunction [IRP_MJ_PNP] = c2pPnP;
// 卸载函数。
DriverObject->DriverUnload = c2pUnload;
gDriverObject = DriverObject;
// 绑定所有键盘设备
status =c2pAttachDevices(DriverObject, RegistryPath);
return status;
}
代码全部结束。我开始分段来解释************************************************
**********************按照流程,先用入口函数谈起******************************
NTSTATUS DriverEntry(
IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath
)
{
ULONG i;
NTSTATUS status;
KdPrint (("c2p.SYS: entering DriverEntry\n"));
// 填写所有的分发函数的指针
for (i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)
{
DriverObject->MajorFunction[i] = c2pDispatchGeneral;
}
// 单独的填写一个Read分发函数。因为要的过滤就是读取来的按键信息
// 其他的都不重要。这个分发函数单独写。
DriverObject->MajorFunction[IRP_MJ_READ] = c2pDispatchRead;
// 单独的填写一个IRP_MJ_POWER函数。这是因为这类请求中间要调用
// 一个PoCallDriver和一个PoStartNextPowerIrp,比较特殊。
DriverObject->MajorFunction [IRP_MJ_POWER] = c2pPower;
// 我们想知道什么时候一个我们绑定过的设备被卸载了(比如从机器上
// 被拔掉了?)所以专门写一个PNP(即插即用)分发函数
DriverObject->MajorFunction [IRP_MJ_PNP] = c2pPnP;
// 卸载函数。
DriverObject->DriverUnload = c2pUnload;
gDriverObject = DriverObject;
// 绑定所有键盘设备
status =c2pAttachDevices(DriverObject, RegistryPath);
return status;
}
看到上面这一小段的代码,很多朋友感觉和上节的代码很相似,但是又有点不同。多了几个函数: c2pDispatchRead,c2pPower,c2pPnP。其他都差不多。之后跳转到c2pAttachDevices函数,我们跟到这个函数
**********************************c2pAttachDevices函数*************************************
NTSTATUS
c2pAttachDevices(
IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath
)
{
NTSTATUS status = 0;
UNICODE_STRING uniNtNameString;
PC2P_DEV_EXT devExt;
PDEVICE_OBJECT pFilterDeviceObject = NULL;
PDEVICE_OBJECT pTargetDeviceObject = NULL;
PDEVICE_OBJECT pLowerDeviceObject = NULL;
PDRIVER_OBJECT KbdDriverObject = NULL;
KdPrint(("MyAttach\n"));
// 初始化一个字符串,就是Kdbclass驱动的名字。
RtlInitUnicodeString(&uniNtNameString, KBD_DRIVER_NAME); //代码开头部分已经定义这个宏
// 请参照前面打开设备对象的例子。只是这里打开的是驱动对象。
status = ObReferenceObjectByName ( //这个函数非常重要,通过对象名得到其指针
&uniNtNameString, //Kbdclass是键盘类驱动,这个参数传入此驱动的路径
OBJ_CASE_INSENSITIVE,
NULL,
0,
IoDriverObjectType,
KernelMode,
NULL,
&KbdDriverObject //得到指向这个DRIVER结构的指针。
);
// 如果失败了就直接返回
if(!NT_SUCCESS(status)) //这段代码比较简单,因此不是我们重点关注对象
{
KdPrint(("MyAttach: Couldn't get the MyTest Device Object\n"));
return( status );
}
else
{
// 这个打开需要解应用。早点解除了免得之后忘记。
ObDereferenceObject(DriverObject);
}
// 这是设备链中的第一个设备
pTargetDeviceObject = KbdDriverObject->DeviceObject;//DeviceObject是Driver结构中的一个成员
// 现在开始遍历这个设备链
while (pTargetDeviceObject)
{
// 生成一个过滤设备,这是前面读者学习过的。这里的IN宏和OUT宏都是
// 空宏,只有标志性意义,表明这个参数是一个输入或者输出参数。
status = IoCreateDevice(
IN DriverObject,
IN sizeof(C2P_DEV_EXT),
IN NULL,
IN pTargetDeviceObject->DeviceType,
IN pTargetDeviceObject->Characteristics,
IN FALSE,
OUT &pFilterDeviceObject
);
// 如果失败了就直接退出。
if (!NT_SUCCESS(status))
{
KdPrint(("MyAttach: Couldn't create the MyFilter Filter Device Object\n"));
return (status);
}
// 绑定。pLowerDeviceObject是绑定之后得到的下一个设备。也就是
// 前面常常说的所谓真实设备。
pLowerDeviceObject =
IoAttachDeviceToDeviceStack(pFilterDeviceObject, pTargetDeviceObject);
//注意上面这个函数,第一个参数是过滤设备,第二个参数是设备栈上任意一个设备,返回的是过滤设备的下一层设备。设备对象结构中还有个成员,记录的是他上一层设备的指针。目前不深入,下面讨论到设备结构的时候详细研究。
// 如果绑定失败了,放弃之前的操作,退出。
if(!pLowerDeviceObject)
{
KdPrint(("MyAttach: Couldn't attach to MyTest Device Object\n"));
IoDeleteDevice(pFilterDeviceObject); //如果绑定失败,那么删除过滤设备,很好理解
pFilterDeviceObject = NULL; //指针赋NULL值
return( status );
}
// 设备扩展!下面要详细讲述设备扩展的应用。
devExt = (PC2P_DEV_EXT)(pFilterDeviceObject->DeviceExtension); //pFilterDeviceObject->DeviceExtension这段话是得到设备扩展的起始地址值,再加上PC2P_DEV_EXT之后强制把这个地址值“武装”成一个指针变量,并且此指针变量的跳跃数为C2P_DEV_EXT结构的大小。或许你可能不会理解为什么可以这样转变,因为在以前学习C语言,C++语言的时候从来没有人用过,但是至少目前你要足够的重视。因此内核编程中这样的技巧经常被使用。我会在下面几节的教程中详细探讨指针方面的问题。
c2pDevExtInit(
devExt,
pFilterDeviceObject,
pTargetDeviceObject,
pLowerDeviceObject );
// 下面的操作和前面过滤串口的操作基本一致。这里不再解释了。
pFilterDeviceObject->DeviceType=pLowerDeviceObject->DeviceType;
pFilterDeviceObject->Characteristics=pLowerDeviceObject->Characteristics;
pFilterDeviceObject->StackSize=pLowerDeviceObject->StackSize+1;
pFilterDeviceObject->Flags |= pLowerDeviceObject->Flags & (DO_BUFFERED_IO | DO_DIRECT_IO | DO_POWER_PAGABLE) ;
//next device
pTargetDeviceObject = pTargetDeviceObject->NextDevice;
}
return status;
}
***********************c2pDispatchRead跟读操作有关的分发函数****************************
NTSTATUS c2pDispatchRead(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp )
{
NTSTATUS status = STATUS_SUCCESS;
PC2P_DEV_EXT devExt;
PIO_STACK_LOCATION currentIrpStack; //当前IRP栈空间的结构,具体下面章节会谈到
KEVENT waitEvent; //定义一个事件对象,事件对象多用于进程间同步下面章节系统讨论
KeInitializeEvent( &waitEvent, NotificationEvent, FALSE ); //初始化这个事件对象为通知型,并且初始状态为未激活
if (Irp->CurrentLocation == 1) //CruuentLocation是IRP结构的一个成员变量。他是栈空间的索引,等讨论到IRP结构的时候详解。这里的意思是如果为设备栈的最后一个设备,那么
{
ULONG ReturnedInformation = 0;
KdPrint(("Dispatch encountered bogus current location\n"));
status = STATUS_INVALID_DEVICE_REQUEST;
Irp->IoStatus.Status = status;
Irp->IoStatus.Information = ReturnedInformation;
IoCompleteRequest(Irp, IO_NO_INCREMENT); //以上3句话就是完成一个IRP得固定用法。暂时死记
return(status);
}
// 全局变量键计数器加1
gC2pKeyCount++;
// 得到设备扩展。目的是之后为了获得下一个设备的指针。
devExt =
(PC2P_DEV_EXT)DeviceObject->DeviceExtension;
// 设置回调函数并把IRP传递下去。 之后读的处理也就结束了。
// 剩下的任务是要等待读请求完成。
currentIrpStack = IoGetCurrentIrpStackLocation(Irp);
IoCopyCurrentIrpStackLocationToNext(Irp);
IoSetCompletionRoutine( Irp, c2pReadComplete,
DeviceObject, TRUE, TRUE, TRUE ); // c2pReadComplete是个完成函数,当IRP完成之后会调用它,需要注意的是完成函数运行在DISPATCH中断级,因此此函数里面的代码尽量不要太多,还有必须使用DISPATCH中断级的函数。
return IoCallDriver( devExt->LowerDeviceObject, Irp ); //让I/O管理器把此IRP传到下层设备
}
*****************************************进入 c2pReadComplete完成例程看看*******************
NTSTATUS c2pReadComplete(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp,
IN PVOID Context
)
{
PIO_STACK_LOCATION IrpSp; //IRP栈空间的结构