越是现在这个时候,就越应该加油,我不应该昏昏的过,我不想以后后面的自己跟现在一样,我不应该跟别人一样。吃得苦上苦,方为人上人,我应该跟之前一样。现在蛮好,不受别人的重视,其实也无所谓,我正想要这样的时间,宝贵的时间,可以自己做点东西出来。还是千万不要浮躁,还是用之前自己喜欢的那句话吧。总有一天,我也会破解成蝶,飞向天空。
文件系统过滤驱动的派遣例程:
文件系统过滤驱动的派遣例程跟设备驱动的派遣例程一样,一个派遣例程处理一种或多种IRP.(IRP的类型通过主函数代码,就是驱动对象中的主功能函数指针列表),这些派遣函数,我们一般在DriverEntry例程的驱动对象的函数列表进行设置注册。当某一个IRP发送到驱动,IO子系统根据IRP的主功能码,调用相应的派遣例程进行IRP的处理。
NTSTATUS
(*PDRIVER_DISPATCH) (
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
);
一般来说,文件系统过滤驱动的派遣函数都运行在PASSIVE_LEVEL中断优先级,在原始发送请求的用户线程的上下文空间中。但是有时候也有例外。举例来说,页错误,导致在APC_LEVEL上面调用读写派遣例程。后面我们会详细说明。当然,我们也不能避免在过滤链中在IRPL > PASSIVE_LEVEL上调用IoCallDriver(举例来说,释放自旋锁或快速互斥体失败),我们强烈建议在被调用的派遣函数中的IRQL跟我们要调用IoCallDriver的IRQL保持一致。
当然,我们的派遣例程也可以在分页代码中,但是必须遵照Making Driver Pageable说明执行。
如果文件系统过滤驱动拥有控制设备对象(CDO),它的派遣例程必须能够探测其IRP的目标设备对象是CDO而不是绑定卷上的VDO.下面,我们具体来讨论派遣函数可以进行的操作。
完成IRP:
每个派遣函数都会接收到一个指向IRP的目标设备对象的指针,一般就是DeviceObject参数,如果过滤驱动拥有CDO,派遣例程应该在对IRP进行任何操作前,先检查这个DeviceObject是否其CDO.
文件系统过滤驱动的CDO不需要支持任何类型IO操作,但是CDO必须完成所有发送给其的请求。
完成一个IRP,派遣例程必须执行如下这些步骤:
1,设置Irp->IoStatus.Status为一个合适的值。
2,调用IoCompleteRequest返回IRP给IO管理器。
3,返回和步骤1同样的status值给调用者。
完成一个IRP,通常就是成功或者失败IRP.
成功完成一个IRP,一般就设置其NTSTATUS为STATUS_SUCCESS.
失败一个IRP,通常就是返回一个错误或警告值,比如,STAUTS_INVALID_DEVICE_REQUEST或STAUS_BUFFER_OVERFLOW.
NTSTATUS值定义在ntstatus.h中,这些一般分为四类,成功,信息,警告,错误。
注意:虽然STATUS_PENDING是一个成功NTSTATUS值,但是一般视为错误的返回值。
将IRP传递到驱动堆栈的下一层驱动:
在检查完IRP的目标设备对象后,派遣例程默认会调用IoCallDriver将IRP传递给驱动堆栈的下一层驱动,特别需要注意的是,过滤驱动千万不要简单将不识别或者自己不处理的IRP直接返回错误,而不传递给下层驱动,因为这样可能导致操作系统本身以不可预知的方式崩溃。比如,如果文件系统过滤驱动接到IRP_MJ_PNP时候,返回失败,它将干扰电源管理的系统休眠操作。这也就是为什么,文件系统过滤驱动不涉及到电源管理,不会受到IRP_MJ_POWER请求的原因。
除了直接完成IRP外,没有设置完成例程的派遣例程应该使用由IoCallDriver调用后的返回值进行返回。除了返回值为STATUS_PENDING外,我们必须确保这个返回值跟Irp->IoStatus.Status设置的值一致。
如果一个派遣例程设置了一个完成例程,发送IRP到工作队列,应该遵照如下的说明进行操作:
1,返回有IoCallDriver调用的返回值。
2,等待完成例程的调用,发出事件信号,返回值到Irp->IoStatus.Status中。
3,标记IRP为等待状态,将它发送到工作队列,然后返回STAUTS_PENDING.
4, 如果完成例程中发送IRP到工作队列,派遣例程必须标记IRP为等待状态并返回STATUS_PENDING.
当然,这上面的说明也不一定正确,取决于当时指定的操作,比如目录改变通知,不能同步,有些oplocks,不能异步等,详细的,我们将在下面介绍。
举例说明,没有设置完成例程的派遣例程怎样将IRP传递给驱动堆栈中的下一层驱动。
1,首先调用IoSkipCurrentIrpStackLocation移除当前的IRP堆栈位置,告诉IO管理器在IRP的结束的流程中不要查找其完成例程。
2,调用IoCallDriver将IRP传递到驱动堆栈中的下一层驱动.
IoSkipCurrentIrpStackLocation ( Irp );
return IoCallDriver ( NextLowerDriverDeviceObject, Irp );
或者:
IoSkipCurrentIrpStackLocation ( Irp );
status = IoCallDriver ( NextLowerDriverDeviceObject, Irp );
/* log or debugprint the status value here */
return status;
IoCallDriver的第一个参数是下一层驱动的设备对象,第二个参数是指向IRP的指针。
这里我们应该强调, 在没有设置完成例程的派遣函数里,都应该调用IoSkipCurrentIrpStackLocation 进行操作,因为这样很方便。
缺点是,这里指向IRP的指针,传递给IoCallDrive后,不再有效,我们不能解引用,从而不能释放其资源。如果要释放其资源还是应该设置完成例程。
注意:如果我们调用IoSkipCurrentIrpStackLocation,就不能为其设置完成例程。
派遣例程的约束:
IRQL方面的约束:
使用分页IO的派遣例程不能在任何高于APC_LEVEL的IRQL上面调用IoCallDriver,如果派遣例程临时提升了IRQL,必须在调用IoCallDriver前,降低其IRQL。
使用分页IO,比如读写的派遣例程,不能安全调用需要运行在IRQL在PASSIVE_LEVEL上的内核例程。
使用分页IO的派遣例程,不能安全调用需要运行在IRQL小于DISPATCH_LEVEL上的内核例程。
使用非分页IO的派遣例程,不应该在高于PASSIVE_LEVEL上面的IRQL上调用IoCallDriver,如果派遣例程临时提升了IRQL,必须在调用IoCallDriver前,将其降低。
处理IRP的约束:
如果IRP的参数包含任何用户模式的地址,在使用前,必须进行测试。
如果IPR包含一个IOCTL或FSCTL空间,从32位平台传输到64位平台,空间的内容需要进行检查。
不像文件系统,文件系统过滤驱动禁止在调用ExAcquireFastMutexUnsafe or ExAcquireResourceExclusiveLite.前调用FsRtlEnterFileSystem或者FsRtlExitFileSystem,因为FsRtlEnterFileSystem和FsRtlExitFileSystem会禁止大多数文件系统需要的普通的内涵APC。
完成IRP的约束:
当结束一个IRP的时候,文件系统过滤驱动因该只适用成功或这错误的返回值。
虽然STATUS_PENGING是一个成功类型NTSTATUS值,但是我们应该将其视为错误的返回值。
在派遣例程调用IoCompleteRequest后,其IRP的指针不再有效,不能安全进行卸载操作。
设置完成例程的约束:
当派遣例程调用IoSetCompletionRoutine设置完成例程时,可以传递一个指向上下文空间的结构体指针,当处理这个IRP的时候,完成例程可以使用这个上下文空间,这个结构体空间必须分配为不分页的空间,因为完成例程可以在DISPATCH_LEVEL上被调用。
如果派遣例程设置完成例程,并返回STATUS_MORE_PROCESSING_REQUIRED,必须进行如下的操作,避免IRP预前结束。
标志IRP为等待状态,调用IoCallDriver并返回STATUS_PENDING.
调用KeWaitForSingleObject等待完成例程执行,然后调用IoCompleteRequest结束IRP。
传递IRP的约束:
派遣例程调用IoCallDriver后,IRP的指针不再有效,不能被安全的卸载,除非派遣例程等待完成例程被调用以后,在可以进行安全的卸载。
禁止在文件系统过滤驱动中调用PoCallDriver例程(因为,文件系统过滤驱动不会接收到IRP_MJ_POWER的请求)
返回状态的约束:
除了直接完成IRP外,没有设置完成例程的派遣例程应该使用由IoCallDriver调用后的返回值进行返回。除了返回值为STATUS_PENDING外,我们必须确保这个返回值跟Irp->IoStatus.Status设置的值一致。
当调用IoCallDriver返回STATUS_PENDING,派遣例程也应该返回STATUS_PENDING,除非它等待完成例程将事件置为由信号。
当发送IRP到工作队列进行进一步处理,派遣例程应该标记IRP为等待状态,并返回STATUS_PENDING .
当设置的完成例程,将IRP发送到工作队列进行进一步处理,派遣例程应该标记IRP为等待状态并返回STATUS_PENDING.
标志IRP为pengidng状态的派遣例程必须返回STATUS_PENDING.
Oplock操作不应该在pended状态(发送到工作队列),派遣例程不能为他们返回STATUS_PENDING.
发送IRP到工作队列的约束:
如果派遣例程要发送IRP到工作队列,在此之前必须调用IoMarkIrpPending。否则,IRP不能在队列中被另外的驱动删除,或者完成,不能被系统释放,而导致系统CRASH.
派遣例程的IRQL和线程上下文。
Dispatch routine Caller's IRQL: Caller's thread context:
Cleanup PASSIVE_LEVEL Nonarbitrary
Close APC_LEVEL Arbitrary
Create PASSIVE_LEVEL Nonarbitrary
DeviceControl (except paging I/O) PASSIVE_LEVEL Nonarbitrary
DeviceControl (paging I/O path) APC_LEVEL Arbitrary
DirectoryControl APC_LEVEL Arbitrary
FlushBuffers PASSIVE_LEVEL Nonarbitrary
FsControl (except paging I/O) PASSIVE_LEVEL Nonarbitrary
FsControl (paging I/O path) APC_LEVEL Arbitrary
LockControl PASSIVE_LEVEL Nonarbitrary
PnP PASSIVE_LEVEL Arbitrary
QueryEa PASSIVE_LEVEL Nonarbitrary
QueryInformation PASSIVE_LEVEL Nonarbitrary
QueryQuota PASSIVE_LEVEL Nonarbitrary
QuerySecurity PASSIVE_LEVEL Nonarbitrary
QueryVolumeInfo PASSIVE_LEVEL Nonarbitrary
Read (except paging I/O) PASSIVE_LEVEL Nonarbitrary
Read (paging I/O path) APC_LEVEL Arbitrary
SetEa PASSIVE_LEVEL Nonarbitrary
SetInformation PASSIVE_LEVEL Nonarbitrary
SetQuota PASSIVE_LEVEL Nonarbitrary
SetSecurity PASSIVE_LEVEL Nonarbitrary
SetVolumeInfo PASSIVE_LEVEL Nonarbitrary
Shutdown PASSIVE_LEVEL Arbitrary
Write (except paging I/O) PASSIVE_LEVEL Nonarbitrary
Write (paging I/O path) APC_LEVEL Arbitrary
文件系统过滤驱动的派遣例程:
文件系统过滤驱动的派遣例程跟设备驱动的派遣例程一样,一个派遣例程处理一种或多种IRP.(IRP的类型通过主函数代码,就是驱动对象中的主功能函数指针列表),这些派遣函数,我们一般在DriverEntry例程的驱动对象的函数列表进行设置注册。当某一个IRP发送到驱动,IO子系统根据IRP的主功能码,调用相应的派遣例程进行IRP的处理。
NTSTATUS
(*PDRIVER_DISPATCH) (
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
);
一般来说,文件系统过滤驱动的派遣函数都运行在PASSIVE_LEVEL中断优先级,在原始发送请求的用户线程的上下文空间中。但是有时候也有例外。举例来说,页错误,导致在APC_LEVEL上面调用读写派遣例程。后面我们会详细说明。当然,我们也不能避免在过滤链中在IRPL > PASSIVE_LEVEL上调用IoCallDriver(举例来说,释放自旋锁或快速互斥体失败),我们强烈建议在被调用的派遣函数中的IRQL跟我们要调用IoCallDriver的IRQL保持一致。
当然,我们的派遣例程也可以在分页代码中,但是必须遵照Making Driver Pageable说明执行。
如果文件系统过滤驱动拥有控制设备对象(CDO),它的派遣例程必须能够探测其IRP的目标设备对象是CDO而不是绑定卷上的VDO.下面,我们具体来讨论派遣函数可以进行的操作。
完成IRP:
每个派遣函数都会接收到一个指向IRP的目标设备对象的指针,一般就是DeviceObject参数,如果过滤驱动拥有CDO,派遣例程应该在对IRP进行任何操作前,先检查这个DeviceObject是否其CDO.
文件系统过滤驱动的CDO不需要支持任何类型IO操作,但是CDO必须完成所有发送给其的请求。
完成一个IRP,派遣例程必须执行如下这些步骤:
1,设置Irp->IoStatus.Status为一个合适的值。
2,调用IoCompleteRequest返回IRP给IO管理器。
3,返回和步骤1同样的status值给调用者。
完成一个IRP,通常就是成功或者失败IRP.
成功完成一个IRP,一般就设置其NTSTATUS为STATUS_SUCCESS.
失败一个IRP,通常就是返回一个错误或警告值,比如,STAUTS_INVALID_DEVICE_REQUEST或STAUS_BUFFER_OVERFLOW.
NTSTATUS值定义在ntstatus.h中,这些一般分为四类,成功,信息,警告,错误。
注意:虽然STATUS_PENDING是一个成功NTSTATUS值,但是一般视为错误的返回值。
将IRP传递到驱动堆栈的下一层驱动:
在检查完IRP的目标设备对象后,派遣例程默认会调用IoCallDriver将IRP传递给驱动堆栈的下一层驱动,特别需要注意的是,过滤驱动千万不要简单将不识别或者自己不处理的IRP直接返回错误,而不传递给下层驱动,因为这样可能导致操作系统本身以不可预知的方式崩溃。比如,如果文件系统过滤驱动接到IRP_MJ_PNP时候,返回失败,它将干扰电源管理的系统休眠操作。这也就是为什么,文件系统过滤驱动不涉及到电源管理,不会受到IRP_MJ_POWER请求的原因。
除了直接完成IRP外,没有设置完成例程的派遣例程应该使用由IoCallDriver调用后的返回值进行返回。除了返回值为STATUS_PENDING外,我们必须确保这个返回值跟Irp->IoStatus.Status设置的值一致。
如果一个派遣例程设置了一个完成例程,发送IRP到工作队列,应该遵照如下的说明进行操作:
1,返回有IoCallDriver调用的返回值。
2,等待完成例程的调用,发出事件信号,返回值到Irp->IoStatus.Status中。
3,标记IRP为等待状态,将它发送到工作队列,然后返回STAUTS_PENDING.
4, 如果完成例程中发送IRP到工作队列,派遣例程必须标记IRP为等待状态并返回STATUS_PENDING.
当然,这上面的说明也不一定正确,取决于当时指定的操作,比如目录改变通知,不能同步,有些oplocks,不能异步等,详细的,我们将在下面介绍。
举例说明,没有设置完成例程的派遣例程怎样将IRP传递给驱动堆栈中的下一层驱动。
1,首先调用IoSkipCurrentIrpStackLocation移除当前的IRP堆栈位置,告诉IO管理器在IRP的结束的流程中不要查找其完成例程。
2,调用IoCallDriver将IRP传递到驱动堆栈中的下一层驱动.
IoSkipCurrentIrpStackLocation ( Irp );
return IoCallDriver ( NextLowerDriverDeviceObject, Irp );
或者:
IoSkipCurrentIrpStackLocation ( Irp );
status = IoCallDriver ( NextLowerDriverDeviceObject, Irp );
/* log or debugprint the status value here */
return status;
IoCallDriver的第一个参数是下一层驱动的设备对象,第二个参数是指向IRP的指针。
这里我们应该强调, 在没有设置完成例程的派遣函数里,都应该调用IoSkipCurrentIrpStackLocation 进行操作,因为这样很方便。
缺点是,这里指向IRP的指针,传递给IoCallDrive后,不再有效,我们不能解引用,从而不能释放其资源。如果要释放其资源还是应该设置完成例程。
注意:如果我们调用IoSkipCurrentIrpStackLocation,就不能为其设置完成例程。
派遣例程的约束:
IRQL方面的约束:
使用分页IO的派遣例程不能在任何高于APC_LEVEL的IRQL上面调用IoCallDriver,如果派遣例程临时提升了IRQL,必须在调用IoCallDriver前,降低其IRQL。
使用分页IO,比如读写的派遣例程,不能安全调用需要运行在IRQL在PASSIVE_LEVEL上的内核例程。
使用分页IO的派遣例程,不能安全调用需要运行在IRQL小于DISPATCH_LEVEL上的内核例程。
使用非分页IO的派遣例程,不应该在高于PASSIVE_LEVEL上面的IRQL上调用IoCallDriver,如果派遣例程临时提升了IRQL,必须在调用IoCallDriver前,将其降低。
处理IRP的约束:
如果IRP的参数包含任何用户模式的地址,在使用前,必须进行测试。
如果IPR包含一个IOCTL或FSCTL空间,从32位平台传输到64位平台,空间的内容需要进行检查。
不像文件系统,文件系统过滤驱动禁止在调用ExAcquireFastMutexUnsafe or ExAcquireResourceExclusiveLite.前调用FsRtlEnterFileSystem或者FsRtlExitFileSystem,因为FsRtlEnterFileSystem和FsRtlExitFileSystem会禁止大多数文件系统需要的普通的内涵APC。
完成IRP的约束:
当结束一个IRP的时候,文件系统过滤驱动因该只适用成功或这错误的返回值。
虽然STATUS_PENGING是一个成功类型NTSTATUS值,但是我们应该将其视为错误的返回值。
在派遣例程调用IoCompleteRequest后,其IRP的指针不再有效,不能安全进行卸载操作。
设置完成例程的约束:
当派遣例程调用IoSetCompletionRoutine设置完成例程时,可以传递一个指向上下文空间的结构体指针,当处理这个IRP的时候,完成例程可以使用这个上下文空间,这个结构体空间必须分配为不分页的空间,因为完成例程可以在DISPATCH_LEVEL上被调用。
如果派遣例程设置完成例程,并返回STATUS_MORE_PROCESSING_REQUIRED,必须进行如下的操作,避免IRP预前结束。
标志IRP为等待状态,调用IoCallDriver并返回STATUS_PENDING.
调用KeWaitForSingleObject等待完成例程执行,然后调用IoCompleteRequest结束IRP。
传递IRP的约束:
派遣例程调用IoCallDriver后,IRP的指针不再有效,不能被安全的卸载,除非派遣例程等待完成例程被调用以后,在可以进行安全的卸载。
禁止在文件系统过滤驱动中调用PoCallDriver例程(因为,文件系统过滤驱动不会接收到IRP_MJ_POWER的请求)
返回状态的约束:
除了直接完成IRP外,没有设置完成例程的派遣例程应该使用由IoCallDriver调用后的返回值进行返回。除了返回值为STATUS_PENDING外,我们必须确保这个返回值跟Irp->IoStatus.Status设置的值一致。
当调用IoCallDriver返回STATUS_PENDING,派遣例程也应该返回STATUS_PENDING,除非它等待完成例程将事件置为由信号。
当发送IRP到工作队列进行进一步处理,派遣例程应该标记IRP为等待状态,并返回STATUS_PENDING .
当设置的完成例程,将IRP发送到工作队列进行进一步处理,派遣例程应该标记IRP为等待状态并返回STATUS_PENDING.
标志IRP为pengidng状态的派遣例程必须返回STATUS_PENDING.
Oplock操作不应该在pended状态(发送到工作队列),派遣例程不能为他们返回STATUS_PENDING.
发送IRP到工作队列的约束:
如果派遣例程要发送IRP到工作队列,在此之前必须调用IoMarkIrpPending。否则,IRP不能在队列中被另外的驱动删除,或者完成,不能被系统释放,而导致系统CRASH.
派遣例程的IRQL和线程上下文。
Dispatch routine Caller's IRQL: Caller's thread context:
Cleanup PASSIVE_LEVEL Nonarbitrary
Close APC_LEVEL Arbitrary
Create PASSIVE_LEVEL Nonarbitrary
DeviceControl (except paging I/O) PASSIVE_LEVEL Nonarbitrary
DeviceControl (paging I/O path) APC_LEVEL Arbitrary
DirectoryControl APC_LEVEL Arbitrary
FlushBuffers PASSIVE_LEVEL Nonarbitrary
FsControl (except paging I/O) PASSIVE_LEVEL Nonarbitrary
FsControl (paging I/O path) APC_LEVEL Arbitrary
LockControl PASSIVE_LEVEL Nonarbitrary
PnP PASSIVE_LEVEL Arbitrary
QueryEa PASSIVE_LEVEL Nonarbitrary
QueryInformation PASSIVE_LEVEL Nonarbitrary
QueryQuota PASSIVE_LEVEL Nonarbitrary
QuerySecurity PASSIVE_LEVEL Nonarbitrary
QueryVolumeInfo PASSIVE_LEVEL Nonarbitrary
Read (except paging I/O) PASSIVE_LEVEL Nonarbitrary
Read (paging I/O path) APC_LEVEL Arbitrary
SetEa PASSIVE_LEVEL Nonarbitrary
SetInformation PASSIVE_LEVEL Nonarbitrary
SetQuota PASSIVE_LEVEL Nonarbitrary
SetSecurity PASSIVE_LEVEL Nonarbitrary
SetVolumeInfo PASSIVE_LEVEL Nonarbitrary
Shutdown PASSIVE_LEVEL Arbitrary
Write (except paging I/O) PASSIVE_LEVEL Nonarbitrary
Write (paging I/O path) APC_LEVEL Arbitrary