在網上找到一篇 AntBean寫的 《驅動入門科普:從WRK理解IRP IRP Stack之理論篇》特別好,所以摘抄下來。
由於網速問題 我把原文截取的圖片源碼替換成文字源碼。
寫這篇文章的主要目的是整理一下自己最近的驅動學習,從讀源碼的角度解析驅動程序中極其重要的概念IRP和IRP Stack。暗合侯捷老師所言:源碼之下,並無新事。
對像我這種死腦筋的初學者,驅動編寫教程上,如果要實現某功能就按這個格式的說法並不能讓我滿意,反而讓我更加糊塗。爲啥就要這樣編寫?IRP到底是啥?IRP Stack呢?爲啥我使用DeviceTree看到的IRP Stack的Stack Size並不像教程上講的那樣,我找到的Device Stack爲啥Attach時最下面的Device的Stack Size不是1?爲啥過濾驅動中要跳過某個IRP就使用
IoSkipCurrentIrpStackLocation而要處理某個IRP就IoCopyCurrentIrpStackLocationToNext()呢?設置IoCompleteRoutine到底有啥用呢?如果我想實現文件過濾驅動,我又怎麼知道我的過濾驅動該Attach到哪個Device上呢?啥是CDO?跟普通的Device Object有啥區別?
如果我想直接發送IRP ,我該怎麼做呢?能不能給我一個完整的實現文件夾保護的文件過濾驅動的代碼供我研究呢?
這些事實上就是我初次接觸文件過濾驅動時的疑問。楚狂人在文件過濾驅動教程二中有些說法讓我很困惑,我決定自己探究。
一、 IoCallDriver 與 IO_STACK_LOCATION
看WRK中IoCallDriver的代碼:
NTSTATUS
FORCEINLINE
IopfCallDriver(
IN PDEVICE_OBJECT DeviceObject,
IN OUT PIRP Irp
)
/*++
Routine Description:
This routine is invoked to pass an I/O Request Packet (IRP) to another
driver at its dispatch routine.
Arguments:
DeviceObject - Pointer to device object to which the IRP should be passed.
Irp - Pointer to IRP for request.
Return Value:
Return status from driver's dispatch routine.
--*/
{
PIO_STACK_LOCATION irpSp;
PDRIVER_OBJECT driverObject;
NTSTATUS status;
//
// Ensure that this is really an I/O Request Packet.
//
ASSERT( Irp->Type == IO_TYPE_IRP );
//
// Update the IRP stack to point to the next location.
//
<strong>Irp->CurrentLocation--;</strong>
if (Irp->CurrentLocation <= 0) {
KiBugCheck3( NO_MORE_IRP_STACK_LOCATIONS, (ULONG_PTR) Irp, 0, 0 );
}
<strong>irpSp = IoGetNextIrpStackLocation( Irp );
Irp->Tail.Overlay.CurrentStackLocation = irpSp;</strong>
//
// Save a pointer to the device object for this request so that it can
// be used later in completion.
//
<strong>irpSp->DeviceObject = DeviceObject;</strong>
//
// Invoke the driver at its dispatch routine entry point.
//
<strong>driverObject = DeviceObject->DriverObject;</strong>
//
// Prevent the driver from unloading.
//
<strong> status = driverObject->MajorFunction[irpSp->MajorFunction]( DeviceObject,
Irp );</strong>
return status;
}
可以看到,IoCallDriver做了以下幾件事:
1. 遞減了IRP的CurrentLocation
2. 獲得下個位置的StackLocation,並設置到IRP的CurrentStackLocation中
3. 設置StackLocation的DeviceObject,並調用該DeviceObject的DriverObject中該StackLocation指定的MajorFunction。
此刻,我相信,你一定對IoGetNextIrpStackLocation做的事很好奇,同時對IO_STACK_LOCATION的結構產生了濃厚的興趣。
先看IoGetNextIrpStackLocation
#define IoGetNextIrpStackLocation( Irp ) (\
(Irp)->Tail.Overlay.CurrentStackLocation - 1 )
看來IoGetNextIrpStackLocation啥事也沒做,一切的奧祕盡在IO_STACK_LOCATION的結構中。
如果你讀過OSROnline上那篇著名的Build your own Irp,我想你一定對作者給出的創建自己的IRP並填充的方法感到好奇,心裏想他是怎麼知道這麼做的?
一般是先IoAllocateIrp,在填充後調用IoCallDriver。
此刻我們先看看IoAllocateIrp函數的實現,這將有助於我們理解IO_STACK_LOCATION。
WRK中IoAllocateIrp函數的實際實現是IopAllocateIrpPrivate,代碼先嚐試從look aside list中分配該IRP,如果不行就從NonPagedPool中分配該IRP,有意思的是分配的IRP的大小,
PIRP
IopAllocateIrpPrivate(
IN CCHAR StackSize,
IN BOOLEAN ChargeQuota
)
/*++
Routine Description:
This routine allocates an I/O Request Packet from the system nonpaged pool.
The packet will be allocated to contain StackSize stack locations. The IRP
will also be initialized.
Arguments:
StackSize - Specifies the maximum number of stack locations required.
ChargeQuota - Specifies whether quota should be charged against thread.
Return Value:
The function value is the address of the allocated/initialized IRP,
or NULL if one could not be allocated.
--*/
{
USHORT allocateSize;
UCHAR fixedSize;
PIRP irp;
UCHAR lookasideAllocation;
PGENERAL_LOOKASIDE lookasideList;
PP_NPAGED_LOOKASIDE_NUMBER number;
USHORT packetSize;
PKPRCB prcb;
CCHAR largeIrpStackLocations;
//
// If the size of the packet required is less than or equal to those on
// the lookaside lists, then attempt to allocate the packet from the
// lookaside lists.
//
if (IopIrpProfileStackCountEnabled()) {
IopProfileIrpStackCount(StackSize);
}
irp = NULL;
fixedSize = 0;
packetSize = IoSizeOfIrp(StackSize);
allocateSize = packetSize;
prcb = KeGetCurrentPrcb();
//
// Capture this value once as it can change and use it.
//
largeIrpStackLocations = (CCHAR)IopLargeIrpStackLocations;
if ((StackSize <= (CCHAR)largeIrpStackLocations) &&
((ChargeQuota == FALSE) || (prcb->LookasideIrpFloat > 0))) {
fixedSize = IRP_ALLOCATED_FIXED_SIZE;
number = LookasideSmallIrpList;
if (StackSize != 1) {
allocateSize = IoSizeOfIrp((CCHAR)largeIrpStackLocations);
number = LookasideLargeIrpList;
}
lookasideList = prcb->PPLookasideList[number].P;
lookasideList->TotalAllocates += 1;
irp = (PIRP)InterlockedPopEntrySList(&lookasideList->ListHead);
if (irp == NULL) {
lookasideList->AllocateMisses += 1;
lookasideList = prcb->PPLookasideList[number].L;
lookasideList->TotalAllocates += 1;
irp = (PIRP)InterlockedPopEntrySList(&lookasideList->ListHead);
if (irp == NULL) {
lookasideList->AllocateMisses += 1;
}
}
if (IopIrpAutoSizingEnabled() && irp) {
//
// See if this IRP is a stale entry. If so just free it.
// This can happen if we decided to change the lookaside list size.
// We need to get the size of the IRP from the information field as the size field
// is overlayed with single list entry.
//
if (irp->IoStatus.Information < packetSize) {
lookasideList->TotalFrees += 1;
ExFreePool(irp);
irp = NULL;
} else {
//
// Update allocateSize to the correct value.
//
allocateSize = (USHORT)irp->IoStatus.Information;
}
}
}
//
// If an IRP was not allocated from the lookaside list, then allocate
// the packet from nonpaged pool and charge quota if requested.
//
lookasideAllocation = 0;
if (!irp) {
//
// There are no free packets on the lookaside list, or the packet is
// too large to be allocated from one of the lists, so it must be
// allocated from nonpaged pool. If quota is to be charged, charge it
// against the current process. Otherwise, allocate the pool normally.
//
if (ChargeQuota) {
irp = ExAllocatePoolWithQuotaTag(NonPagedPool|POOL_QUOTA_FAIL_INSTEAD_OF_RAISE,
allocateSize,' prI');
} else {
//
// Attempt to allocate the pool from non-paged pool. If this
// fails, and the caller's previous mode was kernel then allocate
// the pool as must succeed.
//
irp = ExAllocatePoolWithTag(NonPagedPool, allocateSize, ' prI');
}
if (!irp) {
return NULL;
}
} else {
if (ChargeQuota != FALSE) {
lookasideAllocation = IRP_LOOKASIDE_ALLOCATION;
InterlockedDecrement( &prcb->LookasideIrpFloat );
}
ChargeQuota = FALSE;
}
//
// Initialize the packet.
// Note that irp->Size may not be equal to IoSizeOfIrp(StackSize)
//
IopInitializeIrp(irp, allocateSize, StackSize);
irp->AllocationFlags = (fixedSize | lookasideAllocation);
if (ChargeQuota) {
irp->AllocationFlags |= IRP_QUOTA_CHARGED;
}
return irp;
}
<pre name="code" class="cpp">//++
//
// USHORT
// IoSizeOfIrp(
// IN CCHAR StackSize
// )
//
// Routine Description:
//
// Determines the size of an IRP given the number of stack locations
// the IRP will have.
//
// Arguments:
//
// StackSize - Number of stack locations for the IRP.
//
// Return Value:
//
// Size in bytes of the IRP.
//
//--
#define IoSizeOfIrp( StackSize ) \
((USHORT) (sizeof( IRP ) + ((StackSize) * (sizeof( IO_STACK_LOCATION )))))
也就是說,連着該StackSize的IO_STACK_LOCATION一併分配了。還記得在Build your own Irp中,該處的StackSize是從哪兒獲得的?我先將一個自己分配Irp實現讀文件的代碼貼在下面,然後在分析的過程中回頭看該代碼,再結合WRK,將會有通透的感覺。
PIRP pIrp;
PMDL pMdl = 0;
KEVENT kEvent;
pIrp = IoAllocateIrp(fileObj->DeviceObject->StackSize, FALSE);
if (!pIrp)
{
return -1;
}
if (fileObj->DeviceObject->Flags & DO_BUFFERED_IO)
{
pIrp->AssociatedIrp.SystemBuffer = lpBuffer;
}else if (fileObj->DeviceObject->Flags & DO_DIRECT_IO)
{
pMdl = IoAllocateMdl(lpBuffer, bufferSize, 0, 0, 0);
MmBuildMdlForNonPagedPool(pMdl);
pIrp->MdlAddress = pMdl;
}else
{
pIrp->UserBuffer = lpBuffer;
}
PIO_STACK_LOCATION pIoStackLoc = IoGetNextIrpStackLocation(pIrp);
pIoStackLoc->FileObject = fileObj;
pIoStackLoc->MajorFunction = IRP_MJ_READ;
pIoStackLoc->MinorFunction = IRP_MN_NORMAL;
pIoStackLoc->Parameters.Read.ByteOffset = 0;
pIoStackLoc->Parameters.Read.Key = 0;
pIoStackLoc->Parameters.Read.Length = bufferSize;
IoSetCompletionRoutine(pIrp, IoCompletion, &kEvent, TRUE, TRUE, TRUE);
ntSattus = IoCallDriver(fileObj->DeviceObject, pIrp);
好了,這裏可以看到,該StackSize來源於那個DeviceObject。這就驗證了那句設備棧和IRP Stack是一一對應的。
事實上,IO_STACK_LOCATION纔是指定Driver調用的函數和參數的東東。IRP只是IO_STACK_LOCATION的集合而已。
換句話講,我們在應用層都是直接調用一個API的。而驅動層呢?驅動之間相互調用,並不是直接調該驅動提供的API,而是初始化好IO_STACK_LOCATION,在IO_STACK_LOCATION中保存了我們要調用的MajorFunction和傳遞的參數。
現在可以來看IO_STACK_LOCATION結構了。
在WRK中的定義比較長,這裏我省略了,貼出簡化版。
struct IO_STACK_LOCATION
{
MajorFunction //要調用的函數
Parameters; //函數傳入的參數
DeviceObject; //該DeviceObject
CompletionRoutine //下面具體講解
}
這裏我省略了FileObjectMinorFunction Control等。因爲這些到具體要用到的時候再查。這樣的省略並不妨礙我們理解整個IRP Stack。這纔是困擾我們的重點。我們現在的目標是在驅動中給某個DeviceObject發IRP。讓我們的IRP能正確執行。顯然,我們得調用IoCallDriver發送該IRP。在IoCallDriver時,IRP中的IoStackLocation遞減1後該IoStackLocation的某些值被取出來使用。這就意味着,我們分配IRP後得將其IoStackLocation減1的位置的MajorFunction和其他參數正確初始化。這樣就理解了前面那個自己發送IRP爲啥使用GetNextIrpStackLocation獲得後再初始化。
對照上面的自己發送IRP的過程,我們已經理解了IoAllocateIrp, IoGetNextIrpStackLocation和IoCallDriver。現在,我們還有點不清楚,那個IoSetCompleteRoutine有啥用
一、 IoCompleteRequest 與Completion Routine
先看IoSetCompletionRoutine幹啥了:
<pre name="code" class="cpp">#define IoSetCompletionRoutine( Irp, Routine, CompletionContext, Success, Error, Cancel ) { \
PIO_STACK_LOCATION __irpSp; \
ASSERT( ((Success) | (Error) | (Cancel)) ? (Routine) != NULL : TRUE ); \
__irpSp = IoGetNextIrpStackLocation( (Irp) ); \
__irpSp->CompletionRoutine = (Routine); \
__irpSp->Context = (CompletionContext); \
__irpSp->Control = 0; \
if ((Success)) { __irpSp->Control = SL_INVOKE_ON_SUCCESS; } \
if ((Error)) { __irpSp->Control |= SL_INVOKE_ON_ERROR; } \
if ((Cancel)) { __irpSp->Control |= SL_INVOKE_ON_CANCEL; } }
也就是僅僅設置了下一層IO_STACK_LOCATION的CompletionRoutine。到底有啥用呢?
我們來看一段常見的驅動處理完一個IRP後的代碼:
pIrp->IoStatus.Status = STATUS_ACCESS_DENIED;
pIrp->IoStatus.Information = 0;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return STATUS_ACCESS_DENIED;
也就是說,一個驅動在完成IRP後會調用IoCallDriver該函數中到底做了啥?代碼比較長,這裏就不貼了,直接說,IoCompleteRequest()中做了一些清理操作。最重要的是調用了IRP該層的IO_STACK_LOCATION中的CompleteRoutine函數。也就是說,我們在IO_STACK_LOCATION中設置的CompleteRoutine會在某層被調用。調用後根據返回值,IoCompleteRequest做一定的處理。如果是STATUS_MORE_PROCESSING_REQUIRED直接返回。否則清空當前棧,並做一些處理。
NTSTATUS
FORCEINLINE
IopfCallDriver(
IN PDEVICE_OBJECT DeviceObject,
IN OUT PIRP Irp
)
{
PIO_STACK_LOCATION irpSp;
PDRIVER_OBJECT driverObject;
NTSTATUS status;
//
// Ensure that this is really an I/O Request Packet.
//
ASSERT( Irp->Type == IO_TYPE_IRP );
//
// Update the IRP stack to point to the next location.
//
Irp->CurrentLocation--;
if (Irp->CurrentLocation <= 0) {
KiBugCheck3( NO_MORE_IRP_STACK_LOCATIONS, (ULONG_PTR) Irp, 0, 0 );
}
irpSp = IoGetNextIrpStackLocation( Irp );
Irp->Tail.Overlay.CurrentStackLocation = irpSp;
//
// Save a pointer to the device object for this request so that it can
// be used later in completion.
//
irpSp->DeviceObject = DeviceObject;
//
// Invoke the driver at its dispatch routine entry point.
//
driverObject = DeviceObject->DriverObject;
//
// Prevent the driver from unloading.
//
status = driverObject->MajorFunction[irpSp->MajorFunction]( DeviceObject,
Irp );
return status;
}
我相信,這樣你應該能理解很多教程中告訴你的Complete Routine的編寫法則,何時返回STATUS_MORE_PROCESSING_REQUIRED。
到此,IRP和IRP Stack的理論知識科普完了。下一節將會結合文件過濾驅動深入理解IRP IRPSTACK,同時將提供一個文件過濾驅動實現文件夾保護的示例代碼。