在笔者接触驱动到如今以来一以后大半个月的时间,从中让我深深的体会到了万事开头难,以及学习持之以恒的重要性。笔者也是个驱动新人,開始接触驱动的时候看着张帆的《Windows驱动开发技术具体解释》讲的挺细,对新手来说是个不错的学习资料,可是更重要的还是自己要多动手练习,笔者在学习到同步操作的相关知识的时候,实在是看天书。最后还是放弃了学习本书。再找了本楚狂人的资料学习,感觉本书对新手来说还是比較吃力的,当中笔者就是这样,非常多知识点不是非常明确,仅仅能凭借自己的感觉去做,只是造成的后果就是无情的蓝屏^_^。终于要的是笔者坚持下来了。
今天来分享下学习过程中,编写键盘过滤的心得。关于工作原理由于笔者也是一知半解,就不在阐述。
我们的目的就是将自己的驱动设备挂接/driver/kbdclass驱动下的全部设备,如图所看到的:
然后通过处理来达到过滤我们想要的按键信息。挂接后的驱动中的第一个设备就是我们的过滤设备,当有按键触发,按键信息首先会被我们自己写的设备所拦截,可是这时候拦截到的是没有处理的按键信息,那改怎么处理呢?我们去问键盘驱动,当我们拦截到按键IRP的时候先不做处理,给IRP设置完毕回调函数并传递给键盘驱动的设备。这样一来,当按键IRP被键盘驱动处理完毕之后就会运行我们的回调函数,这时我们在处理按键信息。当卸载我们的过滤设备的时候会有个麻烦就是会有个IRP已经设备了回调例程,而且在等待按键触发。假设这个IRP在没有处理之前就卸载掉我们的过滤驱动,就会引发按键蓝盘。为什么会蓝屏呢?由于这个IRP是已经被设置了回调函数,当IRP被处理完毕之后去找我们设置的回调函数,由于我们在IRP没有处理之前已经卸载了,所以这时IRP已经找不到回调函数了,所以导致蓝屏。大部分都的解决方式是在处理IRP的时候放置个计数器,当计数器不为0的时候说明还有IRP未完毕,这是卸载的时候就用while来一直等待这个IRP完毕,假设我们要是不按键盘的话,它会无休止的等待下去,而且也影响系统性能。
笔者通过相关资料的查阅,另个解决方式就是做个代理IRP,然后保存原来的IRP,由于我们能够取消自己的IRP。在卸载的时候先卸载我们的代理IRP,然后在发送原来保存的IRP,这样就非常好的攻克了无限的等待的BUG...可是笔者也没有找到相关代码,仅仅好自己动手试。经过一下午的測试,笔者发现我们仅仅须要做一个代理IRP就可以,并不须要保存原来的IRP,卸载的时候直接取消我们的IRP,并不须要又一次发送个IRP。以下我们来通过详细代码学习一下键盘过滤驱动。
首先:
//驱动入口
extern "C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject,IN PUNICODE_STRING pRegistryPath)
{
NTSTATUS status;
DbgPrint("驱动载入開始.../n");
pDriverObject->DriverUnload=FilterUnload;
//设置读取派遣函数
pDriverObject->MajorFunction[IRP_MJ_READ]=FilterDispatchRoutin;
BindDevice(pDriverObject);
DbgPrint("驱动载入结束.../n");
return STATUS_SUCCESS;
}
在主函数中,调用BindDevice来实现过滤驱动的创建与绑定,代码例如以下:
//设备类型
extern "C" POBJECT_TYPE IoDriverObjectType;
NTSTATUS BindDevice(PDRIVER_OBJECT pDriverObject)
{
NTSTATUS status;
UNICODE_STRING uniNtNameString;
//要打开的驱动对象
PDRIVER_OBJECT KbdDriverObject = NULL;
//驱动对象的设备
PDEVICE_OBJECT kbdDeviceOjbect;
//初始化一个字符串,就是kbdclass驱动的名子
RtlInitUnicodeString(&uniNtNameString,KBD_DRIVER_NAME);
//依据名字打开驱动对象
status=ObReferenceObjectByName(
&uniNtNameString,
OBJ_CASE_INSENSITIVE,
NULL,
0,
IoDriverObjectType,
KernelMode,
NULL,
(PVOID*)&KbdDriverObject);
//假设失败了就直接返回
if(!NT_SUCCESS(status))
{
DbgPrint("打开设备失败.../n");
return status;
}
//调用ObReferenceObjectByName会导致对驱动对象的引用计数添加
//必须响应的调用解引用ObDereferenceObject
ObDereferenceObject(pDriverObject);
DbgPrint("打开成功,解除引用.../n");
//键盘驱动的第一个设备
kbdDeviceOjbect=KbdDriverObject->DeviceObject;
while(kbdDeviceOjbect!=NULL)
{
//创建并绑定过滤设备
CreateDevice(pDriverObject,kbdDeviceOjbect);
//下一个设备
kbdDeviceOjbect=kbdDeviceOjbect->NextDevice;
}
return status;
}
在这里说一下ObReferenceObjectByName函数,该方法没有被导出,知我我们在头文件里声明一下就可以使用,声明例如以下:
//依据名字获取设备对象,此函数没有公开,声明一下就能够直接使用了
extern "C" NTSTATUS ObReferenceObjectByName(
PUNICODE_STRING objectName,
ULONG Attributes,
PACCESS_STATE AccessState,
ACCESS_MASK DesiredAccess,
POBJECT_TYPE objectType,
KPROCESSOR_MODE accessMode,
PVOID ParseContext,
PVOID *Object);
在BindDevice方法中,调用了一个CreateDevice方法,该方法负责创建过滤设备,而且附加在目标设备上,详细代码例如以下:
//创建设备
NTSTATUS CreateDevice(IN PDRIVER_OBJECT pDriverObject,IN PDEVICE_OBJECT oldDevObj)
{
NTSTATUS status;
PDEVICE_OBJECT pDevObj;
//谁被扩展
PDEVICE_EXTENSION pDevExt;
status=IoCreateDevice(pDriverObject,
sizeof(PDEVICE_EXTENSION),
NULL,
oldDevObj->DeviceType,//设备类型须要和被附加的设备类型相等
0,
FALSE,//假设指定设备是独占的,大部分驱动程序设置这个值为FALSE,假设不是独占的话设置为TRUE.
&pDevObj);
if(!NT_SUCCESS(status))
{
DbgPrint("创建设备失败..../n");
return NULL;
}
pDevExt=(PDEVICE_EXTENSION)pDevObj->DeviceExtension;
//存储设备对象
pDevExt->pDevice=pDevObj;
//绑定前原设备
pDevExt->poldDevice=oldDevObj;
//标志位
pDevObj->Flags |=oldDevObj->Flags & (DO_BUFFERED_IO | DO_DIRECT_IO | DO_POWER_PAGABLE);
//该标识指示I/O管理器对全部发送到控制设备对象的Open请求进行安全检測
pDevObj->Characteristics=oldDevObj->Characteristics;
//绑定设备
PDEVICE_OBJECT topDev = IoAttachDeviceToDeviceStack(pDevObj,oldDevObj);
if(topDev==NULL)
{
//假设绑定失败,销毁设备
IoDeleteDevice(pDevObj);
status=STATUS_UNSUCCESSFUL;
return status;
}
//将绑定的设备和原始设备放入设备扩展中
pDevExt->poldDevice=oldDevObj;
pDevExt->pbindDevice=topDev;
pDevObj->Flags=pDevObj->Flags & ~DO_DEVICE_INITIALIZING;
KdPrint(("绑定成功../n"));
return STATUS_SUCCESS;
}
通过以上代码能够实现过滤设备的绑定,绑定了之后还是主要处理派遣函数,功能例如以下:
//派遣函数
NTSTATUS FilterDispatchRoutin(IN PDEVICE_OBJECT pDevObj,IN PIRP pIrp)
{
PIO_STACK_LOCATION currentIrpStack;
PDEVICE_EXTENSION pDevExt;
//得到设备扩展
pDevExt=(PDEVICE_EXTENSION)pDevObj->DeviceExtension;
//得到当前irp包
currentIrpStack=IoGetCurrentIrpStackLocation(pIrp);
//将当前irp拷贝到下层设备irp堆栈
IoCopyCurrentIrpStackLocationToNext(pIrp);
//保存原来的irp
//pDevExt->tagIrp=pIrp;
//代理irp
pDevExt->proxyIrp=pIrp;
//设置当irp完毕时的回调例程
IoSetCompletionRoutine(pDevExt->proxyIrp,CallBackKbdFilter,pDevObj,TRUE,TRUE,TRUE);
DbgPrint("irp回调例程设置完毕.../n");
return IoCallDriver(pDevExt->poldDevice,pDevExt->proxyIrp);
}
注意的是在处理派遣函数的时候我们将IRP换成我们自己的IRP,这样就能达到取消IRP的目的,我们给IRP设置了回调函数,当IRP处理完毕的时候就去运行回调函数,回调函数例如以下:
// flags for keyboard status
#define S_SHIFT 1
#define S_CAPS 2
#define S_NUM 4
static int kb_status = S_NUM;
void __stdcall print_keystroke(UCHAR sch)
{
UCHAR ch = 0;
int off = 0;
if ((sch & 0x80) == 0) //make
{
if ((sch < 0x47) ||
((sch >= 0x47 && sch < 0x54) && (kb_status & S_NUM))) // Num Lock
{
ch = asciiTbl[off+sch];
}
switch (sch)
{
case 0x3A:
kb_status ^= S_CAPS;
break;
case 0x2A:
case 0x36:
kb_status |= S_SHIFT;
break;
case 0x45:
kb_status ^= S_NUM;
}
}
else //break
{
if (sch == 0xAA || sch == 0xB6)
kb_status &= ~S_SHIFT;
}
if (ch >= 0x20 && ch < 0x7F)
{
DbgPrint("%C /n",ch);
}
}
NTSTATUS CallBackKbdFilter( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID Context )
{
PIO_STACK_LOCATION currentIrp;
PKEYBOARD_INPUT_DATA keyData;
currentIrp=IoGetCurrentIrpStackLocation(Irp);
if(NT_SUCCESS(Irp->IoStatus.Status))
{
keyData=(PKEYBOARD_INPUT_DATA)Irp->AssociatedIrp.SystemBuffer;
//DbgPrint("扫描码:%x",keyData->MakeCode);
DbgPrint("键盘 :%s",keyData->Flags?"弹起":"按下");
print_keystroke((UCHAR)keyData->MakeCode);
}
if( Irp->PendingReturned )
{
IoMarkIrpPending( Irp );
}
return Irp->IoStatus.Status;
}
函数就不说明了,主要就是对makecode的处理,只是在回调函数中引用了对比表,例如以下:
unsigned char asciiTbl[]={
0x00, 0x1B, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30, 0x2D, 0x3D, 0x08, 0x09, //normal
0x71, 0x77, 0x65, 0x72, 0x74, 0x79, 0x75, 0x69, 0x6F, 0x70, 0x5B, 0x5D, 0x0D, 0x00, 0x61, 0x73,
0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x3B, 0x27, 0x60, 0x00, 0x5C, 0x7A, 0x78, 0x63, 0x76,
0x62, 0x6E, 0x6D, 0x2C, 0x2E, 0x2F, 0x00, 0x2A, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x37, 0x38, 0x39, 0x2D, 0x34, 0x35, 0x36, 0x2B, 0x31,
0x32, 0x33, 0x30, 0x2E,
0x00, 0x1B, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30, 0x2D, 0x3D, 0x08, 0x09, //caps
0x51, 0x57, 0x45, 0x52, 0x54, 0x59, 0x55, 0x49, 0x4F, 0x50, 0x5B, 0x5D, 0x0D, 0x00, 0x41, 0x53,
0x44, 0x46, 0x47, 0x48, 0x4A, 0x4B, 0x4C, 0x3B, 0x27, 0x60, 0x00, 0x5C, 0x5A, 0x58, 0x43, 0x56,
0x42, 0x4E, 0x4D, 0x2C, 0x2E, 0x2F, 0x00, 0x2A, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x37, 0x38, 0x39, 0x2D, 0x34, 0x35, 0x36, 0x2B, 0x31,
0x32, 0x33, 0x30, 0x2E,
0x00, 0x1B, 0x21, 0x40, 0x23, 0x24, 0x25, 0x5E, 0x26, 0x2A, 0x28, 0x29, 0x5F, 0x2B, 0x08, 0x09, //shift
0x51, 0x57, 0x45, 0x52, 0x54, 0x59, 0x55, 0x49, 0x4F, 0x50, 0x7B, 0x7D, 0x0D, 0x00, 0x41, 0x53,
0x44, 0x46, 0x47, 0x48, 0x4A, 0x4B, 0x4C, 0x3A, 0x22, 0x7E, 0x00, 0x7C, 0x5A, 0x58, 0x43, 0x56,
0x42, 0x4E, 0x4D, 0x3C, 0x3E, 0x3F, 0x00, 0x2A, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x37, 0x38, 0x39, 0x2D, 0x34, 0x35, 0x36, 0x2B, 0x31,
0x32, 0x33, 0x30, 0x2E,
0x00, 0x1B, 0x21, 0x40, 0x23, 0x24, 0x25, 0x5E, 0x26, 0x2A, 0x28, 0x29, 0x5F, 0x2B, 0x08, 0x09, //caps + shift
0x71, 0x77, 0x65, 0x72, 0x74, 0x79, 0x75, 0x69, 0x6F, 0x70, 0x7B, 0x7D, 0x0D, 0x00, 0x61, 0x73,
0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x3A, 0x22, 0x7E, 0x00, 0x7C, 0x7A, 0x78, 0x63, 0x76,
0x62, 0x6E, 0x6D, 0x3C, 0x3E, 0x3F, 0x00, 0x2A, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x37, 0x38, 0x39, 0x2D, 0x34, 0x35, 0x36, 0x2B, 0x31,
0x32, 0x33, 0x30, 0x2E
};
就是卸载函数,在卸载的时候我们要删除设备和附加的设备,然后取消最后一个IRP,代码例如以下:
//卸载例程
void FilterUnload(IN PDRIVER_OBJECT pDriverObject)
{
//得到设备
PDEVICE_OBJECT pDevObj=pDriverObject->DeviceObject;
while(pDevObj!=NULL)
{
//设备扩展
PDEVICE_EXTENSION pDevExt=(PDEVICE_EXTENSION)pDevObj->DeviceExtension;
PDEVICE_OBJECT pTagObj=pDevExt->pbindDevice;
//解除绑定
if(pDevExt->pbindDevice!=NULL)
{
IoDetachDevice(pDevExt->pbindDevice);
}
//删除设备
if(pDevExt->pDevice!=NULL)
{
IoDeleteDevice(pDevExt->pDevice);
}
if(pDevExt->proxyIrp!=NULL)
{
if(CancelIrp(pDevExt->proxyIrp))
{
DbgPrint("取消成功。。。/n");
}
else
{
DbgPrint("取消失败。。。/n");
}
}
//下一个设备
pDevObj=pDevObj->NextDevice;
}
}
载函数中调用了个取消IRP的方法,代码例如以下:
BOOLEAN CancelIrp(PIRP pIrp)
{
if(pIrp==NULL)
{
DbgPrint("取消irp错误.../n");
return FALSE;
}
if(pIrp->Cancel || pIrp->CancelRoutine==NULL)
{
DbgPrint("取消irp错误.../n");
return FALSE;
}
if(FALSE==IoCancelIrp(pIrp))
{
DbgPrint("IoCancelIrp to irp错误.../n");
return FALSE;
}
//取消后重设此例为空
IoSetCancelRoutine(pIrp,NULL);
return TRUE;
}
整个键盘过滤驱动就完毕了,以后还得多多学习,多多总结。
转载请注明来自:http://blog.csdn.net/ms2146