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

Using IRPs for I/O on device object returned by IoGetDeviceObjectPointer()

能否在 IoGetDeviceObjectPointer() 返回的设备对象上将 IoCallDriver() 与 IoBuildAsynchronousFsdRequest() 创建的 IRP 一起使用?我目前遇到的问题是蓝屏 (BSOD) 0x7E(未处理的异常),当被捕获时显示访问冲突 (0xc0000005)。当设备堆叠时,相同的代码有效(使用 IoAttachDeviceToDeviceStack() 返回的设备对象)。

所以我有以下内容:

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 是 \Device\HarddiskVolume7 根据 WinObj.exe 存在。

缓冲区有足够的 space 并且是 read/writable。 offset 和 io_stat 在堆栈上(也尝试过堆,没有帮助)。当捕获异常(SEH 异常)时,它不会蓝屏,但会显示访问冲突作为异常的原因。 io 是 IRP_MJ_READ.

我是否漏掉了一些明显的东西?通常使用 IRP 比使用 ZwCreateFile / ZwReadFile / ZwWriteFile API 更好吗(这是一个选项,但不是更慢吗?)?我还尝试了一个 ZwCreateFile 来获得额外的参考,但这也没有帮助。

感谢您的任何见解。

你在这段代码中至少犯了 2 个严重错误。

  1. 请问 - 您尝试从哪个文件读取(或写入)数据?从 FileObject 你说?但是文件系统驱动程序如何处理 这个要求知道吗?您没有将任何文件对象传递给 newIrp。 寻找 IoBuildAsynchronousFsdRequest - 它没有文件对象 参数(并且不可能从设备对象获取文件对象 - 仅 反之亦然 - 因为在设备上可以打开多个文件)。所以 newIrp中的这个api不能填写。你必须设置它 你自己:

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

    我猜错误正是在文件系统尝试访问时出现的 FileObject 来自 irp,在您的情况下为 0。还阅读文档 IRP_MJ_READ - IrpSp->FileObject - 指向与 DeviceObject 关联的文件对象的指针

  2. 你把我猜的局部变量io_stat(和offset)传递给 IoBuildAsynchronousFsdRequest。结果 io_stat 必须有效 直到 newIrp 完成 - I/O 子系统将最终结果写入其中 操作完成时。但你不会等到请求才起作用 将完成(如果 STATUS_PENDING returned)但只是退出 从功能。作为稍后 I/O 子系统的结果,如果操作完成 异步,写数据到任意地址&io_stat(变成了 退出函数后任意)。所以你需要或检查 for STATUS_PENDING returned 并在这种情况下等待(实际上 同步 io 请求)。但更合乎逻辑的使用 IoBuildSynchronousFsdRequest 在这种情况下。或分配 io_stat 不是来自堆栈,而是在对应于文件的对象中说。在 在这种情况下,你不能有超过一个 io 请求 当时的对象。或者如果你想要完全异步 I/O - 你可以做 下一个技巧 - newIrp->UserIosb = &newIrp->IoStatus。结果你 iosb 始终对 newIrp 有效。和实际运行状态 你 check/use 在 DrbdIoCompletion

你还能解释下一行代码吗(不是为了我——为了我自己)?:

status = ObReferenceObjectByPointer(newIrp->Tail.Overlay.Thread, THREAD_ALL_ACCESS, NULL, KernelMode);

谁和在哪里取消引用线程,这有什么意义?

Can one use ...

我们可以全部使用,但有条件——我们了解我们在做什么,并且深入了解内部系统。

Is it in general better to use IRPs than the ZwCreateFile / ZwReadFile / ZwWriteFile API

性能 - 是的,更好。但这需要更多代码和更复杂的代码比较 api 调用。并且需要更多的知识。另外,如果你知道以前的模式是内核模式——你可以使用 NtCreateFile、NtWriteFile、NtReadFile——这当然会有点慢(每次都需要通过句柄引用文件对象)但比较 Zw 版本更快


Just wanted to add that the ObReferenceObjectByPointer is needed because the IRP references the current thread which may exit before the request is completed. It is dereferenced in the Completion Routine. Also as a hint the completion routine must return STATUS_MORE_PROCESSING_REQUIRED if it frees the IRP (took me several days to figure that out).

这里你又犯了几个错误。我如何理解你在完成例程中的下一步:

IoFreeIrp(Irp);
return StopCompletion;

但调用简单地调用 IoFreeIrp 这里是错误 - 资源泄漏。我建议您此时检查 (DbgPrint) Irp->MdlAddress。如果您从文件系统对象读取数据并请求异步完成 - 文件系统总是分配 Mdl 用于在任意上下文中访问用户缓冲区。现在的问题 - 谁释放这个 MdlIoFreeIrp - 只需释放 Irp 内存 - 仅此而已。你自己做吗?怀疑。但是 Irp 是一个复杂的对象,它内部拥有很多资源。结果不仅需要释放它的内存,还需要为它调用 "destructor"。这个 "destructor" 是 IofCompleteRequest。当你 return StopCompletion (=STATUS_MORE_PROCESSING_REQUIRED) 你一开始就破坏了这个析构函数。但您必须稍后再次调用 IofCompleteRequest 以继续 Irp(及其资源)正确销毁。

关于引用 Tail.Overlay.Thread - 你在做什么 - 毫无意义:

It is dereferenced in the Completion Routine.

  1. IofCompleteRequest在之后访问Tail.Overlay.Thread 调用你的完成例程(如果你不 return StopCompletion)。结果你的 reference/dereference 线程丢失了 感觉 - 因为你过早地尊重它,before 系统 实际访问它。
  2. 如果你 return StopCompletion 也不会再打电话 IofCompleteRequest 对于此 Irp - 系统无法访问 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;

A second callIofCompleteRequest 导致 I/O 管理器恢复调用 IRP 的完成。这里 io 管理器和访问 Tail.Overlay.Thread 插入 Apc 给他。最后你调用 ObfDereferenceObject(Thread); 已经 系统访问它之后 return StopCompletion 中断第一次调用 IofCompleteRequest。看起来正确但是.. 如果线程已经终止,我在 3 中的解释将是错误的,因为 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
!!!!!!!!!!!

and KernelRoutine 将不会被调用(就像 and 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 读取正常