你在這段代碼中至少犯了2個嚴重錯誤。
我可以問 - 從哪個文件中嘗試讀取(或寫入)數據?從 FileObject
你說?但如何處理 這個請求的文件系統驅動程序知道這一點?您不會將任何文件對象傳遞給newIrp
。 尋找IoBuildAsynchronousFsdRequest
- 它沒有文件對象 參數(並且不可能從設備對象獲取文件對象 - 只有 反過來 - 因爲在設備上可以打開多個文件)。所以它 ,並且不能用newIrp
中的這個api填充。你必須設置它 自己:
PIO_STACK_LOCATION irpSp = IoGetNextIrpStackLocation(newIrp);
irpSp->FileObject = FileObject;
我猜的錯誤是什麼時候的文件系統嘗試從IRP這是在你的情況下0訪問FileObject
。還閱讀文檔的 IRP_MJ_READ
- IrpSp->文件對象 - 指向該文件對象與設備對象
- 你通過我猜局部變量
io_stat
(和offset
)關聯 IoBuildAsynchronousFsdRequest
。結果io_stat
必須有效 直到newIrp
完成 - 當操作完成時,I/O子系統將最終結果寫入 。但您不等功能,直到要求 將完成(如果STATUS_PENDING
返回),但只是從函數退出 。如果操作完成 異步,將數據寫入任意地址&io_stat
(它在退出函數後變成了任意值)。所以你需要或檢查 爲STATUS_PENDING
返回並在這種情況下等待(實際上有 同步IO請求)。但在這種情況下更合乎邏輯地使用 IoBuildSynchronousFsdRequest
。或者不是從棧中分配io_stat
,而是在對應於文件的對象中說。在 這種情況下,您不能在此時與此 對象有多個單個IO請求。或者如果你想要完全異步的I/O - 你可以做 下一個技巧 - newIrp->UserIosb = &newIrp->IoStatus
。結果你 iosb總是有效的newIrp
。和實際運行狀態 您檢查/使用DrbdIoCompletion
也可以解釋(不適合我 - 自我)下一行代碼:
status = ObReferenceObjectByPointer(newIrp->Tail.Overlay.Thread, THREAD_ALL_ACCESS, NULL, KernelMode);
誰,解引用線程以及在何種意義這個 ?
可以在一個使用...
,我們可以使用所有,但條件 - 我們明白我們在做和深厚的瞭解系統內部。
它是一般最好使用的IRP比ZwCreateFile函數/ ZwReadFile /ZwWriteFile API
性能 - 是的,效果更好。但是這需要更多的代碼和更復雜的代碼比較api調用。並需要更多的知識。此外,如果你知道,以前的模式是內核模式 - 您可以使用NtCreateFile,NtWriteFile,NtReadFile然後 - 這當然會有點慢(需要通過手柄每次引用文件對象),但更多更快的對比度Zw版本
只是想補充一點,ObReferenceObjectByPointer
需要 ,因爲IRP引用當前線程可能會退出之前請求完成 。它在完成 例程中被取消。此外,如果完成程序釋放了IRP(花了幾天的時間弄清楚),完成程序必須返回 STATUS_MORE_PROCESSING_REQUIRED
。
這裏你再犯幾次錯誤。我怎麼知道你在完成日常下一步:
IoFreeIrp(Irp);
return StopCompletion;
但撥打只需撥打IoFreeIrp
這裏是錯誤 - 資源泄漏。我建議你在這一點上檢查(DbgPrint)Irp->MdlAddress
。如果從文件系統對象讀取數據並請求完成異步 - 文件系統始終爲任意上下文中的訪問用戶緩衝區分配Mdl。現在的問題 - 誰釋放這個Mdl? IoFreeIrp
- 簡單免費Irp記憶 - 僅此而已。你自己做這個?懷疑。但Irp是一個複雜的對象,它內部擁有許多資源。因爲結果不僅需要釋放它的內存,而且還需要調用「析構函數」。這個「析構函數」是IofCompleteRequest
。當您返回StopCompletion
(=STATUS_MORE_PROCESSING_REQUIRED
)時,您將立即破解此析構函數。但你必須後者再次呼籲IofCompleteRequest
繼續Irp(和它的資源)正確銷燬。
有關引用Tail.Overlay.Thread
- 你在做什麼 - 有沒有意義:
它在完成例程間接引用。
- 但
IofCompleteRequest
訪問Tail.Overlay.Thread
後 打電話給你的完成例程(如果你沒有返回 StopCompletion
)。因爲你的參考/解引用線程丟失了 的意義 - 因爲你太早,之前系統 實際上訪問它。
- ,如果你還爲這個的Irp返回
StopCompletion
,而不是多個呼叫 IofCompleteRequest
- 系統無法訪問 Tail.Overlay.Thread
可言。你不需要在這個 的情況下參考它。
- 並存在其他原因之一,爲什麼參考線程是無意義的。系統 訪問
Tail.Overlay.Thread
僅用於向他插入Apc - 致電 Irp破壞的最後部分(IopCompleteRequest
)在原始 線程上下文中。真正這隻需要用戶模式Irp的請求, 其中緩衝區和iosb位於用戶模式,並且僅在 過程(原始線程)的上下文中有效。但是如果線程被終止 - 調用KeInsertQueueApc
失敗 - 系統不允許插入apc到 死了線程。因爲結果IopCompleteRequest
將不會被調用,並且 資源沒有被釋放。
因此您或提領Tail.Overlay.Thread
太早,或者您不需要這樣做。反正死亡線程的參考無濟於事。在任何情況下你所做的都是錯誤的。
可以嘗試下一步這裏:
PETHREAD Thread = Irp->Tail.Overlay.Thread;
IofCompleteRequest(Irp, IO_NO_INCREMENT);// here Thread will be referenced
ObfDereferenceObject(Thread);
return StopCompletion;
一個second call到IofCompleteRequest
導致I/O管理器來恢復調用IRP的完成。這裏io經理和訪問Tail.Overlay.Thread
插入Apc
給他。最後您撥打ObfDereferenceObject(Thread);
已經後系統訪問它並返回StopCompletion
休息第一次打電話給IofCompleteRequest
。看起來像正確,但..如果線程已經終止,我怎麼解釋這將是錯誤,因爲KeInsertQueueApc
失敗。擴展測試 - 從單獨的線程調用IofCallDriver
並退出。並在完成時運行下面的代碼:
PETHREAD Thread = Irp->Tail.Overlay.Thread;
if (PsIsThreadTerminating(Thread))
{
DbgPrint("ThreadTerminating\n");
if (PKAPC Apc = (PKAPC)ExAllocatePool(NonPagedPool, sizeof(KAPC)))
{
KeInitializeApc(Apc, Thread, 0, KernelRoutine, 0, 0, KernelMode, 0);
if (!KeInsertQueueApc(Apc, 0, 0, IO_NO_INCREMENT))
{
DbgPrint("!KeInsertQueueApc\n");
ExFreePool(Apc);
}
}
}
PMDL MdlAddress = Irp->MdlAddress;
IofCompleteRequest(Irp, IO_NO_INCREMENT);
ObfDereferenceObject(Thread);
if (MdlAddress == Irp->MdlAddress)
{
// IopCompleteRequest not called due KeInsertQueueApc fail
DbgPrint("!!!!!!!!!!!\n");
IoFreeMdl(MdlAddress);
IoFreeIrp(Irp);
}
return StopCompletion;
//---------------
VOID KernelRoutine (PKAPC Apc,PKNORMAL_ROUTINE *,PVOID *,PVOID *,PVOID *)
{
DbgPrint("KernelRoutine(%p)\n", Apc);
ExFreePool(Apc);
}
,你必須得在下一個調試輸出:
ThreadTerminating
!KeInsertQueueApc
!!!!!!!!!!!
和KernelRoutine
將不會調用(如與IopCompleteRequest
) - 無論從它的打印。
那麼什麼是正確的解決方案?這當然沒有記錄在任何地方,但基於深刻的內部理解。你不需要參考原始線程。你需要做的未來:
Irp->Tail.Overlay.Thread = KeGetCurrentThread();
return ContinueCompletion;
你可以安全的改變Tail.Overlay.Thread
- 如果你沒有隻在原來的進程上下文任何有效的指針。這對於內核模式請求來說是正確的 - 所有的緩衝區都在內核模式下並且在任何情況下都有效當然你不需要破壞Irp破壞,但是繼續它。爲正確的免費mdl和所有irp資源。最後系統爲你撥打IoFreeIrp
。
並再次爲iosb指針。我怎麼說通過局部變量地址,如果你在IRP完成之前退出函數(並且這個iosb訪問)是錯誤的。如果你破壞Irp破壞,當然不會訪問iosb,但在這種情況下,更好地傳遞0指針作爲iosb。 (如果後者發生了某些變化,iosb指針將被訪問 - 將成爲最糟糕的錯誤 - 任意內存被破壞 - 帶來不可預知的影響,而且研究失敗將非常困難)。但如果你完成日常工作 - 你根本不需要單獨的iosb - 你可以完成irp並且可以直接訪問它內部的iosb - 你需要什麼?
Irp->UserIosb = &Irp->IoStatus;
完全正確的例子,如何讀取文件異步:
NTSTATUS DemoCompletion (PDEVICE_OBJECT /*DeviceObject*/, PIRP Irp, BIO* bio)
{
DbgPrint("DemoCompletion(p=%x mdl=%p)\n", Irp->PendingReturned, Irp->MdlAddress);
bio->CheckResult(Irp->IoStatus.Status, Irp->IoStatus.Information);
bio->Release();
Irp->Tail.Overlay.Thread = KeGetCurrentThread();
return ContinueCompletion;
}
VOID DoTest (PVOID buf)
{
PFILE_OBJECT FileObject;
NTSTATUS status;
UNICODE_STRING ObjectName = RTL_CONSTANT_STRING(L"\\Device\\HarddiskVolume2");
OBJECT_ATTRIBUTES oa = { sizeof(oa), 0, &ObjectName, OBJ_CASE_INSENSITIVE };
if (0 <= (status = GetDeviceObjectPointer(&oa, &FileObject)))
{
status = STATUS_INSUFFICIENT_RESOURCES;
if (BIO* bio = new BIO(FileObject))
{
if (buf = bio->AllocBuffer(PAGE_SIZE))
{
LARGE_INTEGER ByteOffset = {};
PDEVICE_OBJECT DeviceObject = IoGetRelatedDeviceObject(FileObject);
if (PIRP Irp = IoBuildAsynchronousFsdRequest(IRP_MJ_READ, DeviceObject, buf, PAGE_SIZE, &ByteOffset, 0))
{
Irp->UserIosb = &Irp->IoStatus;
Irp->Tail.Overlay.Thread = 0;
PIO_STACK_LOCATION IrpSp = IoGetNextIrpStackLocation(Irp);
IrpSp->FileObject = FileObject;
bio->AddRef();
IrpSp->CompletionRoutine = (PIO_COMPLETION_ROUTINE)DemoCompletion;
IrpSp->Context = bio;
IrpSp->Control = SL_INVOKE_ON_CANCEL|SL_INVOKE_ON_ERROR|SL_INVOKE_ON_SUCCESS;
status = IofCallDriver(DeviceObject, Irp);
}
}
bio->Release();
}
ObfDereferenceObject(FileObject);
}
DbgPrint("DoTest=%x\n", status);
}
struct BIO
{
PVOID Buffer;
PFILE_OBJECT FileObject;
LONG dwRef;
void AddRef()
{
InterlockedIncrement(&dwRef);
}
void Release()
{
if (!InterlockedDecrement(&dwRef))
{
delete this;
}
}
void* operator new(size_t cb)
{
return ExAllocatePool(PagedPool, cb);
}
void operator delete(void* p)
{
ExFreePool(p);
}
BIO(PFILE_OBJECT FileObject) : FileObject(FileObject), Buffer(0), dwRef(1)
{
DbgPrint("%s<%p>(%p)\n", __FUNCTION__, this, FileObject);
ObfReferenceObject(FileObject);
}
~BIO()
{
if (Buffer)
{
ExFreePool(Buffer);
}
ObfDereferenceObject(FileObject);
DbgPrint("%s<%p>(%p)\n", __FUNCTION__, this, FileObject);
}
PVOID AllocBuffer(ULONG NumberOfBytes)
{
return Buffer = ExAllocatePool(PagedPool, NumberOfBytes);
}
void CheckResult(NTSTATUS status, ULONG_PTR Information)
{
DbgPrint("CheckResult:status = %x, info = %p\n", status, Information);
if (0 <= status)
{
if (ULONG_PTR cb = min(16, Information))
{
char buf[64], *sz = buf;
PBYTE pb = (PBYTE)Buffer;
do sz += sprintf(sz, "%02x ", *pb++); while (--cb); sz[-1]= '\n';
DbgPrint(buf);
}
}
}
};
NTSTATUS GetDeviceObjectPointer(POBJECT_ATTRIBUTES poa, PFILE_OBJECT *FileObject)
{
HANDLE hFile;
IO_STATUS_BLOCK iosb;
NTSTATUS status = IoCreateFile(&hFile, FILE_READ_DATA, poa, &iosb, 0, 0,
FILE_SHARE_VALID_FLAGS, FILE_OPEN, FILE_NO_INTERMEDIATE_BUFFERING, 0, 0, CreateFileTypeNone, 0, 0);
if (0 <= (status))
{
status = ObReferenceObjectByHandle(hFile, 0, *IoFileObjectType, KernelMode, (void**)FileObject, 0);
NtClose(hFile);
}
return status;
}
輸出:
BIO::BIO<FFFFC000024D4870>(FFFFE00001BAAB70)
DoTest=103
DemoCompletion(p=1 mdl=FFFFE0000200EE70)
CheckResult:status = 0, info = 0000000000001000
eb 52 90 4e 54 46 53 20 20 20 20 00 02 08 00 00
BIO::~BIO<FFFFC000024D4870>(FFFFE00001BAAB70)
的eb 52 90 4e 54 46 53
OK(確定)基於
所以最好的解決方案接下來將做你的評論我延伸答案。我如何理解你在完成時仍然出錯(不是因爲來自這個地方的'IoFreeIrp'不正確,因此可能會從irp中獲得其他資源)。並且你的引用/解引用線程仍然沒有意義 – RbMm
非常感謝,我確實已經在完成例程中解釋了MSDN中描述的MDL頁面(以及創建請求期間的錯誤,我沒有將我的原始代碼複製粘貼到題)。關於引用/取消引用線程,我在MSDN上找到了這個:https://msdn.microsoft.com/en-us/library/windows/hardware/ff548310(v=vs.85).aspx它說的要保持線程對象有效......。我寫的是從Linux到Microsoft Windows的DRBD(分佈式複製塊設備)端口,它是開源的:請訪問https://github.com/LINBIT/wdrbd9看一看。 –
我會在一到兩週內發佈更新到github,所以最新的更改還沒有。大小寫爲'IoSetHardErrorOrVerifyDevice'的情況下爲 –