一、首先irp的傳遞流程
1、I/O管理器創建一個空的IRP,然後將該IRP沿設備堆棧向下傳遞,比如IRP_MJ_CREATE、IRP_MJ_READ
2、如果驅動程序設置的分發函數捕獲到IRP_MJ_CREATE,那麼可以在這個分發函數(CREATEdispatch)中對該IRP進行操作,比如IoSetCompletionRoutine,這樣當該IRP返回的時候,我們的驅動程序就可以再次捕捉到該IRP,這時候可以用來讀取其中的數據,設置返回值等。
如果沒有設置分發函數來捕獲這個IRP,那麼就直接將該IRP傳遞給下層的驅動程序,這時候需要將lower DeviceObject的堆棧環境設置成和我們的過濾驅動的堆棧環境相同。需調用兩個函數
IoGetCurrentIrpStackLocation and IoGetNextIrpStackLocation. 然後調用IoCallDriver將IRP傳遞給lower設備對象
3、IRP繼續向下傳遞
4、如果該IRP完成了,那麼CompletionRoutine就會被再次調用,之後沿着設備堆棧一直往上傳遞,返回給用戶態的應用程序。
二、鍵盤過濾的基礎
1、符號連接:
符號連接,其實就是一個別名.可以用一個不同的名字代表一個設備對象.
2.、csrss.exe中的win32!RawInputThread通過一個GUID(GUID_CLASS_KEYBOARD)
獲得鍵盤設備棧中PDO符號連接名.
3、PS/2鍵盤設備棧,
最頂層的設備對象是驅動Kbdclass生成的設備對象
中間層的設備對象是驅動i8042prt生成的設備對象
最底層的設備對象是驅動ACPI生成的設備對象.
CPU與鍵盤交互方式是中斷和讀取端口
一個鍵需要兩個掃描碼.
按下的掃描碼爲x,則同一個鍵彈起爲x+0x80
windows xp下端口號與中斷號固定
中斷號爲0x93,端口號爲0x60
三、鍵盤過濾的代碼(基礎)
///
/// @file ctrl2cap.c
/// @author wowocock
/// @date 2009-1-27
///
#include <wdm.h>
// 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;
if(Irp->MdlAddress != NULL)
buf =
(PUCHAR)
MmGetSystemAddressForMdlSafe(Irp->MdlAddress,NormalPagePriority);
else
buf = (PUCHAR)Irp->UserBuffer;
if(buf == NULL)
buf = (PUCHAR)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;
}
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"));
__asm int 3;
// 填寫所有的分發函數的指針
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;
}