IO_STACK_LOCATION

本文深入探讨了NT内核的驱动模型,特别是IO_STACK_LOCATION的使用方式及其相关函数的功能和应用场景,包括IoCopyCurrentIrpStackLocationToNext、IoSkipCurrentIrpStackLocation等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

如前文所述,nt内核的驱动模型没有完全使用函数调用栈,而是自己山寨出来一个IO_STACK_LOCATION,里面保存了驱动调用序列。我们知道函数调用栈的push和pop都是编译器帮忙弄的,你甚至都可以在完全不了解内幕的前提下写代码,但是驱动开发不一样,调用序列要你自己去关心,何时入栈,何时出栈,栈内保留的什么内容,全部都要照顾好,否则BSOD就在前方不远等你。

与IO_STACK_LOCATION有关的函数有以下几个:IoSkipCurrentIrpStackLocation, IoSetNextIrpStackLocation, IoGetNextIrpStackLocation, IoCopyCurrentIrpStackLocationToNext,外加IoCallDriver等往下传irp的函数。

IoCopyCurrentIrpStackLocationToNext

该函数将current location里的内容全部拷贝到next location中去,一般是你设置了CompleteRoutine之后会用。函数实现非常简单,用宏的形式存放在wdm.h中:

 

1
2
3
4
5
6
7
#define IoCopyCurrentIrpStackLocationToNext( Irp ) { \
    PIO_STACK_LOCATION __irpSp; \
    PIO_STACK_LOCATION __nextIrpSp; \
    __irpSp = IoGetCurrentIrpStackLocation( (Irp) ); \
    __nextIrpSp = IoGetNextIrpStackLocation( (Irp) ); \
    RtlCopyMemory( __nextIrpSp, __irpSp, FIELD_OFFSET(IO_STACK_LOCATION, CompletionRoutine)); \
    __nextIrpSp->Control = 0; }

如上所示,其实就是一个RtlCopyMemory。一般在本层驱动需要CompleteRoutine时使用。常见的调用序列如下:

 

1
2
3
4
5
6
7
8
9
10
11
irpStack = IoGetCurrentIrpStackLocation(Irp);
//…your code with irpStack
IoCopyCurrentIrpStackLocationToNext(Irp);
IoSetCompletionRoutine(
     Irp,
     CompleteRoutine,
     deviceExtension,
     TRUE,
     TRUE,
     TRUE);
IoCallDriver(deviceExtension->nextLower, Irp);

注意,IoCallDriver会把stack location减一。

IoSkipCurrentIrpStackLocation

该函数的实现就更简单了

 

1
2
3
#define IoSkipCurrentIrpStackLocation( Irp ) { \
    (Irp)->CurrentLocation++; \
    (Irp)->Tail.Overlay.CurrentStackLocation++; }

也就是把当前的location设置成上一层。该函数一般和IoCallDriver配合使用

 

1
2
IoSkipCurrentIrpStackLocation(Irp);//location+1
IoCallDriver(deviceExtension->nextLower, Irp);//location-1

执行完上面两步之后,location正好跟调用者一样,IO_STACK_LOCATION中的内容也不变。Filter driver常用此种手段转发irp:收到一个irp,获取或者修改其数据,继续转发,因为location没变所以上层驱动设置的CompleteRoutine依然会被filter之下的那个驱动call到,filter就跟透明一样。

IoGetNextIrpStackLocation

该函数获取下一层location的指针

 

1
2
#define IoGetNextIrpStackLocation( Irp ) (\
    (Irp)->Tail.Overlay.CurrentStackLocation - 1 )

获取该指针后可以设置IoControlCode等内容然后传给下层驱动处理,一般也和IoCallDriver配套使用

 

1
2
3
4
5
6
nextStack = IoGetNextIrpStackLocation(Irp);   
nextStack->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;
nextStack->Parameters.DeviceIoControl.IoControlCode = code;
nextStack->Parameters.DeviceIoControl.OutputBufferLength = 0;
nextStack->Parameters.DeviceIoControl.InputBufferLength = 0;
IoCallDriver(deviceExtension->nextLower, Irp);

IoSetNextIrpStackLocation

我们重点来讲讲IoSetNextIrpStackLocation函数,该函数实现如下:

 

1
2
3
#define IoSetNextIrpStackLocation( Irp ) { \
    (Irp)->CurrentLocation++; \
    (Irp)->Tail.Overlay.CurrentStackLocation—; }

与IoSkipCurrentIrpStackLocation正好相反,它把location设置成下一级。我们知道IoCallDriver等函数在转发irp的时候会帮我们把location减1,所以需要我们主动把location往下降的时刻很少见,一般是由于此种需求才会用到:你调用IoAllocateIrp生成一个irp,并且需要用到irp的current location。因为刚生成的irp的current location是不可用的所以必须主动往下降一级。正如wdk文档所说,这种需求是极其罕见的,原因有二:1. 大多数情况下你处理的irp是别人传给你的。2. 即使确实要生成一个新的irp,本层所需的数据也不需要存到current location中去,因为IoSetCompleteRoutine可以接受一个context域,每次CompleteRoutine被调到时,该context域也会一起返回给你,所有本层驱动所需的信息完全可以放在context中。但是我们都知道凡事都有个例外,其实还真就有需要用的IoSetNextIrpStackLocation的时刻,我们来看一个例子。

假设我们需要allocate一个新的irp并用同步的方法传递给下层驱动处理,我们先用IoAllocateIrp获取一个irp,设置相应的内容,然后调用IoGetNextIrpStackLocation获取下层location以便将control code设为IRP_MJ_INTERNAL_DEVICE_CONTROL,最后调用IoForwardIrpSynchronously函数同步完成irp。但是问题就来了:IoForwardIrpSynchronously函数内部会做一个IoCopyCurrentIrpStackLocationToNext动作,这个函数会把next location的内容替换成current location里的内容,所以我们设置的control code啥信息全被冲掉了。怎么办呢?既然它会用current的替换next的,那么我们直接把control code放在current里不就完了,IoForwardIrpSynchronously会把它拷到下一层去。愿望是美好的,但残酷的现实就是:irp是新生成的,还没有所谓的current location!这时候IoSkipCurrentIrpStackLocation就会显得很有用:先调用IoSkipCurrentIrpStackLocation将location下降,然后GetCurrentIrpStackLocation获取当前的location,设置control code,最后调用IoForwardIrpSynchronously同步完成irp。

看到这里也许你要发问了:为什么IoForwardIrpSynchronously要主动帮我们做copy to的动作,不是吃饱撑了嘛。问的好,答案就是:没办法只能这么做。我记得咱们说过,nt内核里的io全是异步的,上述所谓同步操作也都是用event模拟出来的:调完IoCallDriver后wait在一个event上,在CompleteRoutine里set event。返回到前面对IoCopyCurrentIrpStackLocationToNext的描述我们知道,既然要用到CompleteRoutine,那么IoCopyCurrentIrpStackLocationToNext操作就是免不的了。

 

update:

另有一种情况也会有用到IoSetNextIrpStackLocation。当你allocate了一个irp,set了CompleteRoutine,调了IoCallDriver,一切都完成等complete时,问题就来了:你设置的CompleteRoutine被调到时传入的DeviceObject为NULL。Why?Because CompleteRoutine是设置在next location中的,而DeviceObject保存在current location中。 当一个irp被create出来之后,它的current location是无效值,所以没地方存放DeivceObject。怎么办?按上面所述方法,借助IoSetNextIrpStackLocation完成。

/* File Kmd.cpp By WzrterFX */ #include "Kmd.h" namespace Kmd { NTSTATUS Kmd::Create(PDRIVER_OBJECT driverObject) { NTSTATUS status = STATUS_UNSUCCESSFUL; UNICODE_STRING deviceName { }; RtlInitUnicodeString(&deviceName, L"\\Device\\Kmd"); status = IoCreateDevice( driverObject, NULL, &deviceName, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &_deviceObject ); if (!NT_SUCCESS(status)) { DbgPrintEx(0, 0, "Fatal, failed to create driver device.\n" ); return status; } UNICODE_STRING symbolicLink { }; RtlInitUnicodeString(&symbolicLink, L"\\DosDevices\\Kmd"); status = IoCreateSymbolicLink(&symbolicLink, &deviceName); if (!NT_SUCCESS(status)) { DbgPrintEx(0, 0, "Fatal, failed to establish driver link.\n" ); IoDeleteDevice(_deviceObject); return status; } SetFlag(_deviceObject->Flags, DO_BUFFERED_IO); driverObject->MajorFunction[IRP_MJ_CREATE] = [](PDEVICE_OBJECT, PIRP io) -> NTSTATUS { IoCompleteRequest(io, IO_NO_INCREMENT); return io->IoStatus.Status; }; driverObject->MajorFunction[IRP_MJ_CLOSE] = [](PDEVICE_OBJECT, PIRP io) -> NTSTATUS { IoCompleteRequest(io, IO_NO_INCREMENT); return io->IoStatus.Status; }; driverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = &this->KmdControl; ClearFlag(_deviceObject->Flags, DO_DEVICE_INITIALIZING); return STATUS_SUCCESS; } NTSTATUS Kmd::KmdControl(PDEVICE_OBJECT, PIRP io) { NTSTATUS status = STATUS_UNSUCCESSFUL; PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(io); if (!stack) { IoCompleteRequest(io, IO_NO_INCREMENT); DbgPrintEx(0, 0, "Fatal, missing driver stack location.\n" ); return status; } PKmdRequest request = reinterpret_cast<PKmdRequest>(io->AssociatedIrp.SystemBuffer); if (!request) { IoCompleteRequest(io, IO_NO_INCREMENT); DbgPrintEx(0, 0, "Fatal, missing driver associated request.\n" ); return status; } static PEPROCESS process { }; static SIZE_T reserved { }; switch (stack->Parameters.DeviceIoControl.IoControlCode) { case ::Kmd::_IoCtls::attach: { status = ::Kmd::_NtifsApi::PsLookupProcessByProcessId( request->attachRequest.process, &process ); break; } case ::Kmd::_IoCtls::read: { if (process) { status = ::Kmd::_NtifsApi::MmCopyVirtualMemory( process, request->copyMemoryRequest.from, PsGetCurrentProcess(), request->copyMemoryRequest.to, request->copyMemoryRequest.requested, MODE::KernelMode, &reserved ); } break; } case ::Kmd::_IoCtls::write: { if (process) { status = ::Kmd::_NtifsApi::MmCopyVirtualMemory( PsGetCurrentProcess(), request->copyMemoryRequest.to, process, request->copyMemoryRequest.from, request->copyMemoryRequest.requested, MODE::KernelMode, &reserved ); } break; } default: { status = STATUS_INVALID_DEVICE_REQUEST; break; } } io->IoStatus.Status = status; io->IoStatus.Information = sizeof(KmdRequest); IoCompleteRequest(io, IO_NO_INCREMENT); return status; } }帮我全部加上注释
最新发布
03-22
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值