磁盤的過濾
10.3 驅動分析
10.3.5 DeviceIoControl 請求的處理
該請求的處理函數是 DPDispatchDeviceControl ,作爲一個磁盤過濾設備,理論上是不需要對 DeciceControl 做任何處理的,只需要轉發給下層設備即可。但是這裏本驅動需要截獲一個特殊的請求——IOCTL_VOLUME_ONLINE,這個請求是由Windows系統發出的,它本身的作用是把目標卷設備設置爲在線狀態,在這個在線狀態設置完成之後,纔會有對這個卷的讀寫等操作發生。
對於這個以還原爲目的的驅動來說,最好是儘量在早的時候就對讀寫操作進行處理。基於這個理由,IOCTL_VOLUME_ONLINE 需要儘量早的被設置好。所以在該驅動中初始化完成。
我們在初始化的時候,需要目標卷的一些信息,例如需要這個卷的卷標,但是獲取這一切的信息又必須要等過濾驅動的下層設備也就是真正的卷設備開始運行之後才能夠提供,而卷設備開始運行缺需要這個 IOCTL_VOLUME_ONLINE 的請求發下去。所以,我們採用了同步的方式。就是讓請求先發下去,等下層設備處理完畢之後再進行初始化工作,同時由於下發是採用了同步的方式,因此在完成請求之前是不會有其他的請求發生的。
WDM 驅動框架爲實現上述的操作提供了相當方便的操作,只需要複製一份 irp stack ,設置好完成函數和一個等待時間,在調用下層設備之後就開始等待這個時間,當下層設備處理完成之後設置的完成函數就會被調用,在完成函數中會喚醒剛纔所說的等待時間。
NTSTATUS
DPDispatchDeviceControl(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
{
//用來指向過濾設備的設備擴展的指針
PDP_FILTER_DEV_EXTENSION DevExt = DeviceObject->DeviceExtension;
//返回值
NTSTATUS ntStatus = STATUS_SUCCESS;
//用來指向irp stack的指針
PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(Irp);
//用來同步IOCTL_VOLUME_ONLINE處理的事件
KEVENT Event;
//用來傳給IOCTL_VOLUME_ONLINE的完成函數的上下文
VOLUME_ONLINE_CONTEXT context;
switch (irpsp->Parameters.DeviceIoControl.IoControlCode)
{
case IOCTL_VOLUME_ONLINE:
{
//如果是卷設備的IOCTL_VOLUME_ONLINE,會進入到這裏
//我們打算自己處理這個irp請求,這裏先初始化一個事件用來在這個請求的完成函數裏面做同步信號
KeInitializeEvent(&Event, NotificationEvent, FALSE);
//給這個請求的完成函數初始化參數
context.DevExt = DevExt;
context.Event = &Event;
//這裏copy一份irp stack
IoCopyCurrentIrpStackLocationToNext(Irp);
//設置完成函數
IoSetCompletionRoutine(
Irp,
DPVolumeOnLineCompleteRoutine,
&context,
TRUE,
TRUE,
TRUE);
//調用下層設備來處理這個irp
ntStatus = IoCallDriver(DevExt->LowerDevObj, Irp);
//等待下層設備處理結束這個irp
KeWaitForSingleObject(
&Event,
Executive,
KernelMode,
FALSE,
NULL);
//返回
return ntStatus;
}
default:
//對於其它DeviceIoControl,我們一律調用下層設備去處理
break;
}
return DPSendToNextDriver(DevExt->LowerDevObj,Irp);
}
在這裏設置了DPVolumeOnLineCompleteRoutine 作爲下層驅動完成後的完成函數。需要注意的是,在該完成驅動裏,下層設備所對應的磁盤卷設備已經可以工作了。因爲請求已經被完成。
在完成函數裏,首先獲取了卷的名詞,即常見的 C D E等盤符。這是通過系統調用獲取到的,這個系統調用無法在 IOCTL_VOLUME_ONLINE 被下發之前使用。在獲取了盤符之後,就可以選擇我們感興趣的盤符獲取對應的信息。這些信息大部分都存儲在 DBR 中。在獲取了卷的信息之後,還需要初始化一個 bitmap,這個 bitmap 是還原功能的核心數據結構,具體的作用和實現在下面介紹。目前只需要知道初始化該結構的時候需要卷的總大小作爲參數即可。在這些工作都完成之後,將用來標識還原卷的全局變量賦值,在今後運行的讀寫分發函數和boot驅動回調函數等衆多函數中,都會引用這個全局變量,並根據它的內容來確定哪個是需要保護的卷。在該完成函數中,還喚醒了上述的事件等待,使得 DeviceControl 可以繼續執行。代碼如下:
NTSTATUS
DPVolumeOnLineCompleteRoutine(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp,
IN PVOLUME_ONLINE_CONTEXT Context
)
{
//返回值
NTSTATUS ntStatus = STATUS_SUCCESS;
//這個卷設備的dos名字,也就是C,D等
UNICODE_STRING DosName = { 0 };
//在這裏Context是不可能爲空的,爲空就是出錯了
ASSERT(Context!=NULL);
//下面調用我們自己的VolumeOnline處理
//獲取這個卷的dos名字
ntStatus = IoVolumeDeviceToDosName(Context->DevExt->PhyDevObj, &DosName);
if (!NT_SUCCESS(ntStatus))
goto ERROUT;
//將dos名字變成大寫形式
Context->DevExt->VolumeLetter = DosName.Buffer[0];
if (Context->DevExt->VolumeLetter > L'Z')
Context->DevExt->VolumeLetter -= (L'a' - L'A');
//我們只保護“D”盤
if (Context->DevExt->VolumeLetter == L'D')
{
//獲取這個卷的基本信息
ntStatus = DPQueryVolumeInformation(
Context->DevExt->PhyDevObj,
&(Context->DevExt->TotalSizeInByte),
&(Context->DevExt->ClusterSizeInByte),
&(Context->DevExt->SectorSizeInByte));
if (!NT_SUCCESS(ntStatus))
{
goto ERROUT;
}
//建立這個卷對應的位圖
ntStatus = DPBitmapInit(
&Context->DevExt->Bitmap,
Context->DevExt->SectorSizeInByte,
8,
25600,
(DWORD)(Context->DevExt->TotalSizeInByte.QuadPart /
(LONGLONG)(25600 * 8 * Context->DevExt->SectorSizeInByte)) + 1);
if (!NT_SUCCESS(ntStatus))
goto ERROUT;
//對全局量賦值,說明我們找到需要保護的那個設備了
gProtectDevExt = Context->DevExt;
}
ERROUT:
if (!NT_SUCCESS(ntStatus))
{
if (NULL != Context->DevExt->Bitmap)
{
DPBitmapFree(Context->DevExt->Bitmap);
}
if (NULL != Context->DevExt->TempFile)
{
ZwClose(Context->DevExt->TempFile);
}
}
if (NULL != DosName.Buffer)
{
ExFreePool(DosName.Buffer);
}
//設置等待同步事件,這樣可以讓我們等待的DeviceIoControl處理過程繼續運行
KeSetEvent(
Context->Event,
0,
FALSE);
return STATUS_SUCCESS;
}
明日計劃
不得不說在搞論文的時候,有點沒心思學別的。。論文好折磨。
除了論文就是想划水休息。但是培養習慣還是要堅持。
明天繼續學習 bitmap的作用和分析 以及最主要的論文。俺要畢業。