总版主
|
阅读:2738回复:9
Windows文件系统过滤驱动开发教程(8)
Windows文件系统过滤驱动开发教程
注: 有任何问题与建议请加QQ16191935,邮箱MFC_Tan_Wen@163.com 8 终于绑定了Volume,读操作的捕获与分析 上文已经讲到绑定Volume之前的关键操作.我们一路逢山开路,逢水架桥,相信你从中也学到了驱动开发的基本方法.以后的工作,无非灵活运用这些方法而已.前边已经举出过绑定FS CDO的操作.那么现在绑定Volume,无非照猫画虎,而以后的教程中,我也不会逐一详尽的列举出细节的代码了. 但是绑定Volume的过程中,还是有些地方需要稍微注意: 1.获得Storage Device Object的问题.前边已经说过,为了得到Vbp,我要先得到Storage Device Object,方法如下: wd_irpsp *irpsp = wd_cur_io_stack(irp); wd_dev *storage_dev = wd_irpsp_mount_storage(irpsp); 这是在Irp完成之前,这样调用是对的.但是到完成函数中,情况就不同了.因为这个irpsp下保存的storage_dev可能被修改掉(这并非我自己调试的结果,而是阅读sfilter代码中的注释而得到的信息).既然有这个可能,我们只好先把storage_dev这个东西保存下来.实际上,可以在Device扩展增加一个项目storage_dev.在irp完成之前,生成我要绑定的设备(只是不绑定),并把这个设备指针传入完成函数上下文. 这样在完成函数中,我就能得到这个storage_dev,最终找到Volmue设备的指针,此时进行绑定就可以了. 2.绑定的过程,未必一次就能成功.因为Volmue设备的Initilize标记被清除之前,我的绑定是不能成功的.对这种情况,我抄袭了sfilter中的方法,就是延时500毫秒,然后再尝试.一共尝试8次. 我包装了一个函数用来延时: _inline wd_void wd_delay_milli_se(wd_ulong milli_sec) { wd_llong delay = milli_sec*(-10)*1000; wd_lgint interval; interval.QuadPart = delay; KeDelayExecutionThread(KernelMode,FALSE,&interval); } 这个函数的参数是毫秒,但是我并不清楚有多小的精度. 其他的就不说了,祝愿你的运气足够的好.现在我们处理IRP_MJ_READ,如果你已经绑定了Volume,那么显然,发送给Volume的请求就会先发送给你.处理IRP_MJ_READ,能捕获文件的读操作. 进入你的my_disp_read()函数(假设你注册了这个函数来处理IRP_MJ_READ,请见前面关于分发函数的讲述),首先判断这个Dev是不是绑定Volume的设备.如果是,那么就是一个读文件的操作. 如何判断?记得我们先绑定Volume的时候,在我们的设备扩展中设置了storage_dev,如果不是(比如是FS CDO,我们没设置过),那么这么判断即可: if(is_my_dev(dev)) { my_dev_ext *ext = (my_dev_ext *)wd_dev_ext(dev); if(ext->storage_dev) { // ... 到这里说明是对文件的读操作 } } 其他的情况不需要捕获,请直接传递到下层. 读请求的IRP情况非常复杂,请有足够的心理准备.并不要过于依赖帮助,最好的办法就是自己打印IRP的各个细节,亲自查看文件读操作的完成过程.而我现在所说的东西,换另一我未尝试过的版本的windows是否还正确,我也无法下断言. 不过基本的东西是不会变的. 首先关心被读写的文件.IRP下有一个FileObject指针.这个东西指向一个文件对象.你可以得到文件对象的名字,这个名字是没有盘符的文件全路径.有人要问那么盘符如何获得?因为你已经知道了Volume,前边已经说过盘符不过是Volume的符号连接名,那么想要真正的全路径问题应该也不大了. 我现在偷一个懒,我现在只打印没有盘符的路径名.先写一个函数,从IRP得到FileObject. _inline wd_file *wd_irp_file(wd_irpsp *irpsp) { return irpsp->FileObject; } 然后写一个函数来获得文件名.这个函数参考的是FileMon的代码. wd_void wd_file_get_name(in wd_file *file, in out wd_ustr *name) { if( file->FileName.Buffer && !(file->Flags & FO_DIRECT_DEVICE_OPEN) ) RtlCopyUnicodeString(name,&file->FileName); } 接下来有必要得到读文件的偏移量.和vxd的文件系统驱动不同,2000下文件系统得到的偏移量似乎都是从文件起始位置开始计算的.偏移量是一个LARGE_INTEGER.因为现在确实有些文件已经超过了长整型所能表示的大小. 以下函数用来得到偏移量.wd_lgint是经过重定义的LARGE_INTEGER. _inline wd_lgint wd_irp_read_offset(wd_irpsp *irpsp) { return irpsp->Parameters.Read.ByteOffset; } 注意以上的参数不是irp.是当前IO_STACK_LOCATION,也就是我的wd_irpsp.前面已经讲述过如何获取当前irpsp. 此外我还希望能得到我所读到的数据.这要注意,我们捕获这个请求的时候,这个请求还没有完成.既然没有完成,当然无数据可读.如果要获取,那就设置完成函数,在完成函数中完成请求. 完成Irp的时候忽略还是拷贝当前IO_STACK_LOCATION,返回什么STATUS,以及完成函数中如何结束Irp,是不那么容易搞清楚的一件事情.我想做个总结如下: 1.如果对irp完成之后的事情无兴趣,直接忽略当前IO_STACK_LOCATION,(对我的程序来说,调用wd_ship_cur_io_stack),然后向下传递请求,返回wd_irp_call()所返回的状态. 2.不但对irp完成之后的事情无兴趣,而且我不打算继续传递,打算立刻返回成功或失败.那么我不用忽略或者拷贝当前IO_STACK_LOCATION,填写参数后调用IoCompleteRequest,并返回我想返回的结果. 3.如果对irp完成之后的事情有兴趣,并打算在完成函数中处理,应该首先拷贝当前IO_STACK_LOCATION(wd_copy_cur_io_stack()),然后指定完成函数,并返回wd_irp_call()所返回的status.完成函数中,不需要调用IoCompleteRequest!直接返回Irp的当前状态即可. 4.同3的情况,有时候,会把任务塞入系统工作者线程或者希望在另外的线程中去完成Irp,那么完成函数中应该返回wd_stat_more_processing,此时完成Irp的时候应该调用IoCompleteRequest.另一种类似的情况是在dispatch函数中等待完成函数中设置事件,那么完成函数返回wd_stat_more_processing,dispatch函数在等待结束后调用IoCompleteRequest. 前边已经提到过设备的DO_BUFFERED_IO,DO_DIRECT_IO这两个标记.情况是3种:要么是两个标记中其中一个,要么是一个都没有.Volume设备出现DO_BUFFERED的情况几乎没有,我碰到的都是一个标记都没有.DO_DIRECT_IO表示数据应该返回到Irp->MdlAddress所指向的MDL所指向的内存.在无标记的情况下,表明数据读好,请返回到 Irp->UseBuffer中即可. UseBuffer是一个只在当前线程上下文才有效的地址.如果你打算按这个地址获得数据,你最好在当前线程上下文中.完成函数与my_disp_read并非同一个线程.所以在完成函数中按这个地址去获取数据是不对的.如何回到当前线程?我采用简单的办法.在my_disp_read中设置一个事件,调用wd_irp_call(即ddk中的IoCallDriver)之后开始等待这个事件.而在完成函数中设置这个事件.这样等待结束的时候,刚好Irp已经完成,我也回到了我的my_disp_read原来的线程. wd_stat my_disp_read(in wd_dev *dev,in wd_pirp irp) { my_dev_ext *ext; wd_dev *attached_dev; wd_irpsp *irpsp = wd_cur_io_stack(irp); wd_stat status; wd_file *file = wd_irp_file(irpsp); wd_lgint offset = wd_irp_read_offset(irpsp); wd_size length = wd_irp_read_length(irpsp); wd_wchar name_buf[512]; wd_ustr name; wd_event event; // 检查是否我的设备 if(!is_my_dev(dev)) return wd_irp_failed(irp,wd_stat_invalid_dev_req); ext = (wdff_dev_ext *)wd_dev_ext(dev); attached_dev = wdff_dev_attached(dev); // 到这里判断得到这是对一个被绑定了的卷的读操作 if(ext->storage_dev == NULL) { wd_skip_io_stack(irp); return wd_irp_call(attached_dev,irp); } // 到了这里,确认是对文件的读 wd_ustr_init_em(&name,name_buf,512); if(file) wd_file_get_name((wd_void *)file,&name); else { wd_skip_io_stack(irp); return wd_irp_call(attached_dev,irp); } wd_printf1("xxx irp flag = %x\r\n",wd_irp_flags(irp)); wd_printf1("xxx file read: %wZ \r\n",&name); wd_printf1("xxx read offset = %ld ",offset); wd_printf1("xxx read length = %ld\r\n",length); // 以上我已经打印了读请求的相关参数,下面我希望得到读出的内容 wd_event_init(&event); // 先拷贝当前io_stack,然后再指定完成例程 wd_copy_io_stack(irp); wd_irp_set_comp(irp,my_disp_read_comp,&event); // 对实际设备呼叫irp status = wd_irp_call(attached_dev,irp); if(status == wd_stat_pending) wd_event_wait(&event); wd_printf1("test read end:status = %x \r\n",status); // 如果此时status = 0,那么内容应该就在Irp->UserBuffer中,请自己打印... wd_printf1("test read end:read length = %ld\r\n",wd_irp_infor(irp)); return wd_irp_over(irp); } 然后是my_disp_read_comp的内容,可以看见只是简单的设置事件,然后返回wd_stat_more_processing. wd_stat my_disp_read_comp(in wd_dev *dev, in wd_irp *irp, in wd_void *context) { wd_event * event= (wd_event *)context; UNREFERENCED_PARAMETER(dev); UNREFERENCED_PARAMETER(irp); wd_event_set(event); return wd_stat_more_processing; } 尽管已经写了很多,尽管我们得到了读过程的所有参数和结果,我们依然不知道如果自己写一个文件系统,该如何完成读请求,或者过滤驱动中,如何修改读请求等等.我们下一节继续讨论读操作. |
沙发#
发布于:2004-11-01 15:05
很好的帖子。
楼主能不能把这个连载整理成一个文件发出来,方便大家下载后学习,呵呵。 |
|
|
板凳#
发布于:2004-11-01 15:35
能不能给出个完整的例子
:) 边调试,边学习 |
|
|
总版主
|
地板#
发布于:2004-11-01 15:49
兄弟们的要求还真多诶!现在还没写完,写完再整理一下.例子从开头到现在应该一直是完整的,只是没有把代码打包下载而已了!逐章自己添加代码,学起来才会更有成就感嘛!
谢谢支持!祝各位学有所成! |
地下室#
发布于:2004-11-02 10:48
:cool: :o ;) :)帮你顶!!!
|
|
5楼#
发布于:2004-11-03 12:01
hao
Continue, Please |
|
6楼#
发布于:2004-11-04 09:28
顶
|
|
|
总版主
|
7楼#
发布于:2004-11-04 23:58
我自己顶!
|
8楼#
发布于:2005-01-31 16:32
顶。。。。。。。。。
|
|
9楼#
发布于:2005-04-12 20:26
ding too
|
|
|