符号连接:
符号连接,事实上就是一个别名.能够用一个不同的名字代表一个设备对象.
csrss.exe中的win32!RawInputThread通过一个GUID(GUID_CLASS_KEYBOARD)
获得键盘设备栈中PDO符号连接名.
PS/2键盘设备栈,
最顶层的设备对象是驱动Kbdclass生成的设备对象
中间层的设备对象是驱动i8042prt生成的设备对象
最底层的设备对象是驱动ACPI生成的设备对象.
CPU与键盘交互方式是中断和读取port
一个键须要两个扫描码.
按下的扫描码为x,则同一个键弹起为x+0x80
windows xp下port号与中断号固定
中断号为0x93,port号为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;
//… 这里能够做进一步的处理。我这里非常easy的打印出全部的扫
// 描码。
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))
}
}
}