IO_STACK_LOCATION

如前文所述,nt內核的驅動模型沒有完全使用函數調用棧,而是自己山寨出來一個IO_STACK_LOCATION,裏面保存了驅動調用序列。我們知道函數調用棧的push和pop都是編譯器幫忙弄的,你甚至都可以在完全不瞭解內幕的前提下寫代碼,但是驅動開發不一樣,調用序列要你自己去關心,何時入棧,何時出棧,棧內保留的什麼內容,全部都要照顧好,否則BSOD就在前方不遠等你。

與IO_STACK_LOCATION有關的函數有以下幾個:IoSkipCurrentIrpStackLocation, IoSetNextIrpStackLocation, IoGetNextIrpStackLocation, IoCopyCurrentIrpStackLocationToNext,外加IoCallDriver等往下傳irp的函數。

IoCopyCurrentIrpStackLocationToNext

該函數將current location裏的內容全部拷貝到next location中去,一般是你設置了CompleteRoutine之後會用。函數實現非常簡單,用宏的形式存放在wdm.h中:

 

1
2
3
4
5
6
7
#define IoCopyCurrentIrpStackLocationToNext( Irp ) { \
    PIO_STACK_LOCATION __irpSp; \
    PIO_STACK_LOCATION __nextIrpSp; \
    __irpSp = IoGetCurrentIrpStackLocation( (Irp) ); \
    __nextIrpSp = IoGetNextIrpStackLocation( (Irp) ); \
    RtlCopyMemory( __nextIrpSp, __irpSp, FIELD_OFFSET(IO_STACK_LOCATION, CompletionRoutine)); \
    __nextIrpSp->Control = 0; }

如上所示,其實就是一個RtlCopyMemory。一般在本層驅動需要CompleteRoutine時使用。常見的調用序列如下:

 

1
2
3
4
5
6
7
8
9
10
11
irpStack = IoGetCurrentIrpStackLocation(Irp);
//…your code with irpStack
IoCopyCurrentIrpStackLocationToNext(Irp);
IoSetCompletionRoutine(
     Irp,
     CompleteRoutine,
     deviceExtension,
     TRUE,
     TRUE,
     TRUE);
IoCallDriver(deviceExtension->nextLower, Irp);

注意,IoCallDriver會把stack location減一。

IoSkipCurrentIrpStackLocation

該函數的實現就更簡單了

 

1
2
3
#define IoSkipCurrentIrpStackLocation( Irp ) { \
    (Irp)->CurrentLocation++; \
    (Irp)->Tail.Overlay.CurrentStackLocation++; }

也就是把當前的location設置成上一層。該函數一般和IoCallDriver配合使用

 

1
2
IoSkipCurrentIrpStackLocation(Irp);//location+1
IoCallDriver(deviceExtension->nextLower, Irp);//location-1

執行完上面兩步之後,location正好跟調用者一樣,IO_STACK_LOCATION中的內容也不變。Filter driver常用此種手段轉發irp:收到一個irp,獲取或者修改其數據,繼續轉發,因爲location沒變所以上層驅動設置的CompleteRoutine依然會被filter之下的那個驅動call到,filter就跟透明一樣。

IoGetNextIrpStackLocation

該函數獲取下一層location的指針

 

1
2
#define IoGetNextIrpStackLocation( Irp ) (\
    (Irp)->Tail.Overlay.CurrentStackLocation - 1 )

獲取該指針後可以設置IoControlCode等內容然後傳給下層驅動處理,一般也和IoCallDriver配套使用

 

1
2
3
4
5
6
nextStack = IoGetNextIrpStackLocation(Irp);   
nextStack->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;
nextStack->Parameters.DeviceIoControl.IoControlCode = code;
nextStack->Parameters.DeviceIoControl.OutputBufferLength = 0;
nextStack->Parameters.DeviceIoControl.InputBufferLength = 0;
IoCallDriver(deviceExtension->nextLower, Irp);

IoSetNextIrpStackLocation

我們重點來講講IoSetNextIrpStackLocation函數,該函數實現如下:

 

1
2
3
#define IoSetNextIrpStackLocation( Irp ) { \
    (Irp)->CurrentLocation++; \
    (Irp)->Tail.Overlay.CurrentStackLocation—; }

與IoSkipCurrentIrpStackLocation正好相反,它把location設置成下一級。我們知道IoCallDriver等函數在轉發irp的時候會幫我們把location減1,所以需要我們主動把location往下降的時刻很少見,一般是由於此種需求才會用到:你調用IoAllocateIrp生成一個irp,並且需要用到irp的current location。因爲剛生成的irp的current location是不可用的所以必須主動往下降一級。正如wdk文檔所說,這種需求是極其罕見的,原因有二:1. 大多數情況下你處理的irp是別人傳給你的。2. 即使確實要生成一個新的irp,本層所需的數據也不需要存到current location中去,因爲IoSetCompleteRoutine可以接受一個context域,每次CompleteRoutine被調到時,該context域也會一起返回給你,所有本層驅動所需的信息完全可以放在context中。但是我們都知道凡事都有個例外,其實還真就有需要用的IoSetNextIrpStackLocation的時刻,我們來看一個例子。

假設我們需要allocate一個新的irp並用同步的方法傳遞給下層驅動處理,我們先用IoAllocateIrp獲取一個irp,設置相應的內容,然後調用IoGetNextIrpStackLocation獲取下層location以便將control code設爲IRP_MJ_INTERNAL_DEVICE_CONTROL,最後調用IoForwardIrpSynchronously函數同步完成irp。但是問題就來了:IoForwardIrpSynchronously函數內部會做一個IoCopyCurrentIrpStackLocationToNext動作,這個函數會把next location的內容替換成current location裏的內容,所以我們設置的control code啥信息全被沖掉了。怎麼辦呢?既然它會用current的替換next的,那麼我們直接把control code放在current裏不就完了,IoForwardIrpSynchronously會把它拷到下一層去。願望是美好的,但殘酷的現實就是:irp是新生成的,還沒有所謂的current location!這時候IoSkipCurrentIrpStackLocation就會顯得很有用:先調用IoSkipCurrentIrpStackLocation將location下降,然後GetCurrentIrpStackLocation獲取當前的location,設置control code,最後調用IoForwardIrpSynchronously同步完成irp。

看到這裏也許你要發問了:爲什麼IoForwardIrpSynchronously要主動幫我們做copy to的動作,不是吃飽撐了嘛。問的好,答案就是:沒辦法只能這麼做。我記得咱們說過,nt內核裏的io全是異步的,上述所謂同步操作也都是用event模擬出來的:調完IoCallDriver後wait在一個event上,在CompleteRoutine裏set event。返回到前面對IoCopyCurrentIrpStackLocationToNext的描述我們知道,既然要用到CompleteRoutine,那麼IoCopyCurrentIrpStackLocationToNext操作就是免不的了。

 

update:

另有一種情況也會有用到IoSetNextIrpStackLocation。當你allocate了一個irp,set了CompleteRoutine,調了IoCallDriver,一切都完成等complete時,問題就來了:你設置的CompleteRoutine被調到時傳入的DeviceObject爲NULL。Why?Because CompleteRoutine是設置在next location中的,而DeviceObject保存在current location中。 當一個irp被create出來之後,它的current location是無效值,所以沒地方存放DeivceObject。怎麼辦?按上面所述方法,藉助IoSetNextIrpStackLocation完成。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章