键盘过滤驱动

http://hi.baidu.com/code_tin/blog/item/e47c1501b054291b738b65c5.html

驱动基础4键盘过滤驱动
2009-12-04 01:49

符号连接:
符号连接,其实就是一个别名.可以用一个不同的名字代表一个设备对象.

csrss.exe中的win32!RawInputThread通过一个GUID(GUID_CLASS_KEYBOARD)
获得键盘设备栈中PDO符号连接名.

PS/2键盘设备栈,
最顶层的设备对象是驱动Kbdclass生成的设备对象
中间层的设备对象是驱动i8042prt生成的设备对象
最底层的设备对象是驱动ACPI生成的设备对象.

CPU与键盘交互方式是中断和读取端口
一个键需要两个扫描码.
按下的扫描码为x,则同一个键弹起为x+0x80

windows xp下端口号与中断号固定
中断号为0x93,端口号为0x60

ObReferenceObjectByName通过一个名字获得一个对象的指针
IoEnumerateDeviceObjectList
The IoEnumerateDeviceObjectList routine enumerates a driver's device object list.

NTSTATUS
IoEnumerateDeviceObjectList(   
IN PDRIVER_OBJECT DriverObject,   
IN PDEVICE_OBJECT *DeviceObjectList,   
IN ULONG DeviceObjectListSize,   
OUT PULONG ActualNumberDeviceObjects    );

Parameters
DriverObject
Pointer to the driver object for the driver.
DeviceObjectList
Pointer to a caller-allocated array that receives the device
object pointers. This parameter can be NULL.
DeviceObjectListSize
Size, in bytes, of the DeviceObjectList array. Can be zero.
ActualNumberDeviceObjects
Actual number of device objects found in the driver object's device
object list. Note that if the array at DeviceObjectList is too small,
the number of device object pointers that are copied into the array
will be less than ActualNumberDeviceObjects.

Return Value
IoEnumerateDeviceObjectList can return one of the following:

STATUS_SUCCESS
The call to IoEnumerateDeviceObjectList was successful.
STATUS_BUFFER_TOO_SMALL
The array at DeviceObjectList is too small to hold the entire device
object list. In this case, IoEnumerateDeviceObjectList copies as many
device object pointers into the array as possible.


// Kbdclass驱动的名字
#define KBD_DRIVER_NAME L"//Driver//Kbdclass"

// 这个函数是事实存在的,只是文档中没有公开。声明一下
// 就可以直接使用了。
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;
}

应用设备扩展:
原始方法定义两个数组,用来存储原来的设备和绑定的设备
现方法:
生成一个过滤设备时,给设备指定一个任意长度的"设备扩展",这个扩展中内容可以任意填写,
作为一个自定义的数据结构.

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;

生成一个带有设备扩展的的设备对象,在IoCreateDevice时,注意第二个参数填入的扩展长度,
// 生成一个过滤设备,这是前面读者学习过的。这里的IN宏和OUT宏都是
// 空宏,只有标志性意义,表明这个参数是一个输入或者输出参数。
status = IoCreateDevice(
IN DriverObject,
    IN sizeof(C2P_DEV_EXT),
    IN NULL,
    IN pTargetDeviceObject->DeviceType,
    IN pTargetDeviceObject->Characteristics,
    IN FALSE,
    OUT &pFilterDeviceObject
);

生成设备后填写这个区域:
// 设备扩展!下面要详细讲述设备扩展的应用。
devExt = (PC2P_DEV_EXT)(pFilterDeviceObject->DeviceExtension);
c2pDevExtInit(
devExt,
    pFilterDeviceObject,
    pTargetDeviceObject,
    pLowerDeviceObject );

c2pDevExtInit函数

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 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;
}

键盘过滤模块de动态卸载.
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;
}

防止未决请求没有完成的方法是设置一个全局变量:

键盘过滤请求的处理:

一般的过滤请求处理:
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);
}

(PC2P_DEV_EXT)DeviceObject->DeviceExtension的真实设备指针

电源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 );
}
和普通irp的skip处理没有啥区别
两点注意:
1.调用IoSkipCurrentIrpStackLocation之前调用PoStartNextPowerIrp
2.用PoCallDriver代替IoCallDriver

c2pPower处理功能号为IRP_MJ_POWER的IRP;而c2pDispatchGeneral处理我们不关心的IRP

PNP(可插拔请求)处理
设备被拔出,接触绑定,删除过滤设备
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;
}

按键读的处理:
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 );
}

完成读处理:
// 这是一个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<buf_len;++i)
        {
            DbgPrint("ctrl2cap: %2x/r/n", buf[i]);
        }
    }
    gC2pKeyCount--;

if( Irp->PendingReturned )
{
   IoMarkIrpPending( Irp );
}
    return Irp->IoStatus.Status;
}

从缓冲区获得KEYBOARD_INPUT_DATA
KEYBOARD_INPUT_DATA contains one packet of keyboard input data.

typedef struct _KEYBOARD_INPUT_DATA {
//根据设备的值
USHORT UnitId;
//扫描码
USHORT MakeCode;
//标记,标记这个键释放还是其他扫描码
USHORT Flags;
//保留
USHORT Reserved;
//扩展信息
ULONG ExtraInformation;
} KEYBOARD_INPUT_DATA, *PKEYBOARD_INPUT_DATA;
Members

UnitId
Not used.
MakeCode
Specifies the scan code associated with a key press.
Flags
Specifies a bitwise OR of one or more of the following flags that indicate
whether a key was pressed or released, and other miscellaneous information.
Value        Meaning
KEY_MAKE        The key was pressed.
KEY_BREAK        The key was released.
KEY_E0         Extended scan code used to indicate special
          keyboard functions. See the Kbdclass sample code.
KEY_E1         Extended scan code used to indicate special
          keyboard functions. See the Kbdclass sample code.


Reserved
Reserved for operating system use.
ExtraInformation
Specifies device-specific information associated with a keyboard event.
Headers
Declared in ntddkbd.h. Include ntddkbd.h
Comments
In response to an IRP_MJ_READ request, Kbdclass transfers zero or more
KEYBOARD_INPUT_DATA structures from its internal data queue to the Win32
subsystem buffer.

计算有多少个KEYBOARD_INPUT_DATA这样的结构:
size=buf_len/sizeof(KEYBOARD_INPUT_DATA);

从KEYBOARD_INPUT_DATA中得到键信息:
keyData = Irp->AssociatedIrp.SystemBuffer;
numkeys = Irp->IoStatus.Information / sizeof(KEYBOARD_INPUT_DATA);
for(i=0;i<numkeys;i++)
{
//下面显示打印按键的信息
DbgPrint("numkeys:%d ",numkeys);
DbgPrint("ScanCode:%x ",keyData->MakeCode);
DbgPrint("%s/n",KeyData->Flags? "Up" : "Down" );
MyPrintKeyStroke((UCHAR)keyData->MakeCode);

//这是一个小测试,改写键值
if(keyData->MakeCode==CAPS_LOCK)
{
   keyData->MakeCode = LCANTROL;
}
}

从MakeCode得到实际字符.
把按键显示成可以显示的字符.实际字符就是ASCII码.
//flags for keyboard status
#define S_SHIFT 1
#define S_CAPS 2
#define S_NUM 4

//标记用来保存当前键盘的状态
//其中3个键用来表示Caps Lock键,Num Lock键,Shift键是否被按下
//控制键状态
static int kb_status=S_NUM;

void _stdcall print_keystroke(UCHAR sch)
{
UCHAR ch = 0;
int off = 0;


if((sch & 0x80) == 0)//如果是按下
{
     //按下数字或者字母等可见字符
   if((sch<0x47)||((sch>=0x47&&sch<0x54) && (kb_status & S_NUM))
   {
    //最终得到哪个键由Caps Lock键,Num Lock键,Shift键
    //三个键决定,所以写在一张表中.
    ch = asciiTbl[off+sch];
   }
   switch(sch)
   {
    //Caps Lock键,Num Lock键按两次等于没按
    //用异或设置标志,按一次起作用,再按一次不起作用
    case 0x3A:
     kb_status^=S_CAPS;
     break;
    //注意shift键两个特点
    //(1)shift键有两个,左右一个,扫描码不同
    //(2)shift按下起作用,弹起则作用消失
    case 0x2A:
    case 0x36:
     kb_status |= S_SHIFT;
     break;
    //NumLock键
    case 0x45:
     kb_status ^= S_NUM;
   }
   else //break
   {
    if(sch>=0x20&&ch<=0x7F)
    {
     kb_status &= ~S_SHIFT;
    }
   }
   if(ch>=0x20 && ch <0x7F)
   {
    DbgPrint(("%C /n",ch))
   }
}
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值