使用IpGetDeviceObjectPointer()返回的设备对象上的I/O使用IRP

使用IpGetDeviceObjectPointer()返回的设备对象上的I/O使用IRP

问题描述:

可以使用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个严重错误。

  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