2017-10-14 121 views
1

可以使用IoCallDriver()與IoBuildAsynchronousFsdRequest()創建的IRP IoGetDeviceObjectPointer()返回的設備對象?我目前失敗的藍屏(藍屏)0x7E(未處理的異常),當捕獲顯示訪問衝突(0xc0000005)。設備堆疊時使用相同的代碼(使用由IoAttachDeviceToDeviceStack()返回的設備對象)。使用IpGetDeviceObjectPointer()返回的設備對象上的I/O使用IRP

所以我有什麼是瞭解以下信息:

status = IoGetDeviceObjectPointer(&device_name, FILE_ALL_ACCESS, &FileObject, &windows_device); 
if (!NT_SUCCESS(status)) { 
    return -1; 
} 
offset.QuadPart = 0; 
newIrp = IoBuildAsynchronousFsdRequest(io, windows_device, buffer, 4096, &offset, &io_stat); 
if (newIrp == NULL) { 
    return -1; 
} 
IoSetCompletionRoutine(newIrp, DrbdIoCompletion, bio, TRUE, TRUE, TRUE); 

status = ObReferenceObjectByPointer(newIrp->Tail.Overlay.Thread, THREAD_ALL_ACCESS, NULL, KernelMode); 
if (!NT_SUCCESS(status)) { 
    return -1; 
} 
status = IoCallDriver(bio->bi_bdev->windows_device, newIrp); 
if (!NT_SUCCESS(status)) { 
    return -1; 
} 
return 0; 

DEVICE_NAME是存在根據WinObj.exe \設備\ HarddiskVolume7。

緩衝區有足夠的空間並且是可讀/寫的。偏移和io_stat在堆棧上(也嘗試堆,沒有幫助)。捕獲異常(SEH異常)時,它不會藍屏,但顯示訪問衝突是異常的原因。 io是IRP_MJ_READ。

我想念一些明顯的東西嗎?通常使用IRP比ZwCreateFile/ZwReadFile/ZwWriteFile API更好(這可能是一個選項,但速度並不慢)?我也嘗試了一個ZwCreateFile來獲得額外的參考,但這也沒有幫助。

感謝您的任何見解。

+0

所以最好的解決方案接下來將做你的評論我延伸答案。我如何理解你在完成時仍然出錯(不是因爲來自這個地方的'IoFreeIrp'不正確,因此可能會從irp中獲得其他資源)。並且你的引用/解引用線程仍然沒有意義 – RbMm

+0

非常感謝,我確實已經在完成例程中解釋了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看一看。 –

+0

我會在一到兩週內發佈更新到github,所以最新的更改還沒有。大小寫爲'IoSetHardErrorOrVerifyDevice'的情況下爲 –

回答

2

你在這段代碼中至少犯了2個嚴重錯誤。

  1. 我可以問 - 從哪個文件中嘗試讀取(或寫入)數據?從 FileObject你說?但如何處理 這個請求的文件系統驅動程序知道這一點?您不會將任何文件對象傳遞給newIrp。 尋找IoBuildAsynchronousFsdRequest - 它沒有文件對象 參數(並且不可能從設備對象獲取文件對象 - 只有 反過來 - 因爲在設備上可以打開多個文件)。所以它 ,並且不能用newIrp中的這個api填充。你必須設置它 自己:

    PIO_STACK_LOCATION irpSp = IoGetNextIrpStackLocation(newIrp); 
        irpSp->FileObject = FileObject; 
    

    我猜的錯誤是什麼時候的文件系統嘗試從IRP這是在你的情況下0訪問FileObject 。還閱讀文檔的 IRP_MJ_READ - IrpSp->文件對象 - 指向該文件對象與設備對象

  2. 你通過我猜局部變量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。現在的問題 - 誰釋放這個MdlIoFreeIrp - 簡單免費Irp記憶 - 僅此而已。你自己做這個?懷疑。但Irp是一個複雜的對象,它內部擁有許多資源。因爲結果不僅需要釋放它的內存,而且還需要調用「析構函數」。這個「析構函數」是IofCompleteRequest。當您返回StopCompletion=STATUS_MORE_PROCESSING_REQUIRED)時,您將立即破解此析構函數。但你必須後者再次呼籲IofCompleteRequest繼續Irp(和它的資源)正確銷燬。

有關引用Tail.Overlay.Thread - 你在做什麼 - 有沒有意義:

它在完成例程間接引用。

  1. IofCompleteRequest訪問Tail.Overlay.Thread後 打電話給你的完成例程(如果你沒有返回 StopCompletion)。因爲你的參考/解引用線程丟失了 的意義 - 因爲你太早,之前系統 實際上訪問它。
  2. ,如果你還爲這個的Irp返回StopCompletion,而不是多個呼叫 IofCompleteRequest - 系統無法訪問 Tail.Overlay.Thread可言。你不需要在這個 的情況下參考它。
  3. 並存在其他原因之一,爲什麼參考線程是無意義的。系統 訪問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 callIofCompleteRequest導致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(確定)基於

+0

首先,將FileObject設置爲IRP堆棧位置解決了藍屏。看起來有些設備(由IoGetDeviceObjectPointer()返回)與一個文件對象相關聯,而其他設備則不是(由IoAttchDeviceToDeviceStack返回)。此外,對於設備控制IRP,File對象也不是必需的(查詢磁盤幾何圖形時IRP始終工作)。關於偏移量,io_stat參數我也嘗試從堆中分配(使用ExFreePool),只是爲了讓您知道我在回答中知道問題#2。現在我在生物中使用內存(完成時釋放) –

+0

非常感謝FileObject真的做到了這一點。只是想補充一點,ObReferenceObjectByPointer是需要的,因爲IRP引用了可能在請求完成之前退出的當前線程。它在完成程序中被取消。此外,作爲提示,如果完成例程釋放IRP,則必須返回STATUS_MORE_PROCESSING_REQUIRED(花了我幾天的時間才弄清楚)。 –

+0

@JohannesThoma - *似乎某些設備(由IoGetDeviceObjectPointer()返回)與一個文件對象相關聯,而其他設備則不是(由IoAttchDeviceToDeviceStack返回)* - 這完全是無稽之談。看起來你不懂基地的事情。我們打開設備對象時創建的文件對象。文件對象具有指向設備的指針。上設備可以存在多個文件 – RbMm

相關問題