📄 windows文件系统过滤驱动开发教程.txt
字号:
}
wd_printf0("attach fs dev: not a recogonizer.\r\n");
接下来我将要生成我的设备。这里要提到设备扩展的概念。设备对象是一个数据结构,为了表示不同的设备,里边将有一片自定义的空间,用来给你记录这个设备的特有信息。我们为我们所生成的设备确定设备扩展如下:
// 文件过滤系统驱动的设备扩展
typedef struct _my_dev_ext
{
// 我们绑定的文件系统驱动
wd_dev * attached_to;
// 上边这个设备的设备名。
wd_ustr dev_name;
// 这是上边的unicode字符串的缓冲区
wd_wchar name_buf[wd_dev_name_max_len];
} my_dev_ext;
之所以如此简单,是因为我们现在还没有多少东西要记录。只要记得自己绑定在哪个设备上就好了。如果以后需要更多的信息,再增加不迟。扩展空间的大小是在wdf_dev_create(也就是这个设备生成)的时候指定的。得到设备对象指针后,我用下面这个函数来获取设备扩展指针:
// --------------wdf.h中的内容------------------
_inline wd_void * wd_dev_ext(wd_dev *dev)
{
return (dev->DeviceExtension);
}
生成设备后,为了让系统看起来,你的设备和原来的设备没什么区别,你必须设置一些该设备的标志位与你所绑定的设备相同。
_inline wd_void wd_dev_copy_flag(wd_dev *new_dev,
wd_dev *old_dev)
{
if(old_dev->Flags & DO_BUFFERED_IO)
new_dev->Flags &= DO_BUFFERED_IO;
if(old_dev->Flags & DO_DIRECT_IO)
new_dev->Flags &= DO_DIRECT_IO;
if (old_dev->Characteristics & FILE_DEVICE_SECURE_OPEN)
new_dev->Characteristics &= FILE_DEVICE_SECURE_OPEN;
}
DO_BUFFERED_IO,DO_DIRECT_IO这两个标志的意义在于外部向这些设备发送读写请求的时候,所用的缓冲地址将有所不同。这点以后在过滤文件读写的时候再讨论。现在一切事情都搞完,你应该去掉你的新设备上的DO_DEVICE_INITIALIZING标志,以表明的的设备已经完全可以用了。
// --------------wdf.h中的内容------------------
_inline wd_void wd_dev_clr_init_flag(wd_dev *dev)
{
dev->Flags &= ~DO_DEVICE_INITIALIZING;
}
现在我写一个函数来完成以上的这个过程。你只要在上一节中提示的位置调用这个函数,就完成对文件系统控制设备的绑定了。
//-----------绑定一个文件系统驱动设备-------------------------
wd_stat my_attach_fs_dev(wd_dev *fs_dev)
{
wd_wchar name_buf[wd_dev_name_max_len];
wd_ustr name,tmp;
wd_dev *new_dev;
wd_stat status;
my_dev_ext *ext;
wd_ustr_init_em(&name,name_buf,wd_dev_name_max_len);
wd_ustr_init(&tmp,L"\\FileSystem\\Fs_Rec");
// 如果不是我关心的类型,我直接返回成功
if(!my_care(wd_dev_get_type(fs_dev)))
{
wd_printf0(("attach fs dev:not a cared type.\r\n"));
return wd_stat_suc;
}
wd_printf0("attach fs dev: is my cared type.\r\n");
// 我不绑定识别器。所以如果是识别器,我直接返回成功。查看是否是识别
// 器的办法是看是否是\FileSystem\Fs_Rec的设备。
wd_obj_get_name(wd_dev_drv(fs_dev),&name);
if(wd_ustr_cmp(&name,&tmp,wd_true) == 0)
{
wd_printf0("attach fs dev:is a recogonizer.\r\n");
return wd_stat_suc;
}
wd_printf0("attach fs dev: not a recogonizer.\r\n");
// 现在来生成一个设备用来绑定
status = wd_dev_create(g_drv,sizeof(my_dev_ext),NULL,
wd_dev_get_type(fs_dev),
0,wd_false,&new_dev);
if(!wd_suc(status))
{
wd_printf0("attach fs dev: dev create failed.\r\n");
return status;
}
wd_printf0("attach fs dev: create dev success.\r\n");
// 接着设置设备的各种标志与之要绑定的标志一致
wd_dev_copy_flag(new_dev,fs_dev);
ext = (my_dev_ext *)wd_dev_ext(new_dev);
wd_printf0("begin to attach.\r\n");
status = wd_dev_attach(new_dev,fs_dev,&ext->attached_to);
wd_printf0("attach over.status = %8x\r\n",status);
if(!wd_suc(status))
{
wd_printf0("attach fs dev: dev attach failed.\r\n");
UNREFERENCED_PARAMETER(new_dev);
wd_dev_del(new_dev);
return status;
}
wd_printf0("attach fs dev: attach %wZ succeed.\r\n",&name);
wd_ustr_init_em(&ext->dev_name,ext->name_buf,wd_dev_name_max_len);
wd_ustr_copy(&ext->dev_name,&name);
wd_dev_clr_init_flag(new_dev);
return status;
}
6.IRP的传递,File System Control Dispatch
我们现在不得不开始写dispatch functions.因为你的设备已经绑定到文件系统控制设备上去了。windows发给文件系统的请求发给你的驱动。如果你不能做恰当的处理,你的系统的就会崩溃。
最简单的处理方式是把请求不加改变的传递到我们所绑定的设备上去。如何获得我们所绑定的设备?上一节已经把该设备记录在我们的设备扩展里。
//------------我用这个函数快速得到我所绑定的设备-----------
// 得到绑定的设备
_inline wd_dev *my_dev_attached(wd_dev *dev)
{
return ((wdff_dev_ext *)wd_dev_ext(dev))->attached_to;
}
如何传递请求?使用IoCallDriver,该调用的第一个参数是设备对象指针,第二个参数是IRP指针。
一个IRP拥有一组IO_STACK_LOCATION.前面说过IRP在一个设备栈中传递。IO_STACK_LOCATION是和这个设备栈对应的。用于保存IRP请求在当前设备栈位置中的部分参数。如果我要把请求往下个设备传递,那么我应该把当前IO_STATCK_LOCATION复制到下一个。
我写了一些函数来处理IO_STACK_LOCATION,另外wd_irp_call用来包装IoCallDriver的功能。
//---------------------wdf.h中的内容----------------------------
typdef wd_irpsp PIO_STACK_LOCAION;
_inline wd_irpsp *wd_cur_io_stack(wd_irp *irp)
{
return IoGetCurrentIrpStackLocation(irp);
}
_inline wd_void wd_skip_io_stack(wd_pirp irp)
{
IoSkipCurrentIrpStackLocation(irp);
}
_inline wd_void wd_copy_io_stack(wd_irp *irp)
{
IoCopyCurrentIrpStackLocationToNext(irp);
}
_inline wd_stat wd_irp_call(wd_dev *dev,wd_pirp irp)
{
return IoCallDriver(dev,irp);
}
有了上边这些,我现在可以写一个默认的Dispatch Functions.
// 默认的处理很简单,忽略当前调用栈,直接发送给绑定设备
wd_stat my_disp_default(in wd_dev *dev,in wd_pirp irp)
{
wd_dev *attached_dev;
if(!is_my_dev(dev))
return wd_irp_failed(irp,wd_stat_invalid_dev_req);
if(is_my_cdo(dev))
return wd_irp_failed(irp,wd_stat_invalid_dev_req);
attached_dev = my_dev_attached(dev);
if(!attached_dev)
return wd_irp_failed(irp,wd_stat_invalid_dev_req);
wd_skip_io_stack(irp);
return wd_irp_call(attached_dev,irp);
}
上边有一个函数is_my_dev来判断是否我的设备。这个判断过程很简单。通过dev可以得到DriverObject指针,判断一下是否我自己的驱动即可。is_my_cdo()来判断这个设备是否是我的控制设备,不要忘记在wd_main()中我们首先生成了一个本驱动的控制设备。实际这个控制设备还不做任何事情,所以对它发生的任何请求也是非法的。返回错误即可。wd_irp_failed这个函数立刻让一个irp失败。其内容如下:
// 这个函数可以立刻失败掉一个irp
_inline wd_stat wd_irp_failed(wd_pirp irp,wd_stat status_error)
{
irp->IoStatus.Status = status_error;
irp->IoStatus.Information = 0;
return wd_irp_over(irp);
}
如此一来,本不改发到我的驱动的irp,就立刻返回错误非法请求。但是实际上这种情况是很少发生的。
如果你现在想要你的驱动立刻运行,让所有的dispacth functions都调用my_disp_default.这个驱动已经可以绑定文件系统的控制设备,并输出一些调试信息。但是还没有绑定Volume.所以并不能直接监控文件读写。
对于一个绑定文件系统控制设备的设备来说,其他的请求直接调用上边的默认处理就可以了。重点需要注意的是上边曾经挂接IRP_MJ_FILE_SYSTEM_CONTROL的dispatch处理的函数my_disp_file_sys_ctl().
IRP_MJ_FILE_SYSTEM_CONTROL这个东西是IRP的主功能号。每个主功能号下一般都有次功能号。这两个东西标示一个IRP的功能。
主功能号和次功能号是IO_STACK_LOCATION的开头两字节。
//----------------我重新定义的次功能号-------------------
enum {
wd_irp_mn_mount = IRP_MN_MOUNT_VOLUME,
wd_irp_mn_load_filesys = IRP_MN_LOAD_FILE_SYSTEM,
wd_irp_mn_user_req = IRP_MN_USER_FS_REQUEST
};
enum {
wdf_fsctl_dismount = FSCTL_DISMOUNT_VOLUME
};
要得到功能号,要先得到当前的IO_STACK_LOCATION,这个上边已经有函数wd_cur_io_stack,相信这个不能难倒你。
当有Volumne被Mount或者dismount,你写的my_disp_file_sys_ctl()就被调用。具体的判断方法,就见如下的代码了:
// 可以看到分发函数中其他的函数处理都很简单,但是file_sys_ctl的
// 处理会比较复杂。我们已经在notify函数中绑定了文件系统驱动的控
// 制对象。当文件系统得到实际的介质的时候,会生成新的设备对象,
// 这种设备称为卷(Volume),而这种设备是在file_sys中的mount中生
// 成的,而且也是unmount中注销掉的。我们捕获这样的操作之后,就必
// 须生成我们的设备对象,绑定在这样的“卷”上,才能绑定对这个卷
// 上的文件的操作。
wd_stat my_disp_file_sys_ctl(in wd_dev *dev,in wd_pirp irp)
{
wd_dev *attached_dev;
wd_io_stack *stack = wd_cur_io_stack(irp);
if(!is_my_dev(dev))
return wd_irp_failed(irp,wd_stat_invalid_dev_req);
switch(wd_irpsp_minor(stack))
{
case wd_irp_mn_mount:
// 在这里,一个Volume正在Mount
return my_fsctl_mount(dev,irp);
case wd_irp_mn_load_filesys:
return my_fsctl_load_fs(dev,irp);
case wd_irp_mn_user_req:
{
switch(wd_irpsp_fs_ctl_code(stack))
{
case wdf_fsctl_dismount:
// 在这里,一个Volume正dismount
return my_fsctl_dismount(dev,irp);
}
}
}
wd_skip_io_stack(irp);
attached_dev = my_dev_attached(dev);
return wd_irp_call(attached_dev,irp);
}
你发现你又得开始写两个新的函数,my_fsctl_mount()和my_fsctl_dismount(),来处理卷的Mount和Dismount.显然,你应该在其中生成设备或者删除,绑定或者解除绑定。很快,你就能完全监控所有的卷了。
这样做是动态监控所有的卷的完美的解决方案。
如果是在xp以上,有一个调用可以获得一个文件系统上已经被Mount的卷。但是2000下不能使用。所以我们没有使用那个方法。何况仅仅得到已经Mount的卷也不是我想要的。
这里另外还有一个my_fsctl_load_fs函数。发生于IRP_MN_LOAD_FILESYS。这个功能码我只做一点点解释:当一个文件识别器(见上文)决定加载真正的文件系统的时候,会产生一个这样的irp。
你现在可以修改你的驱动,使插入拔出u盘的时候,在Volume加载卸载时候输出调试信息。回首一下我们的脉络:
a.生成一个控制设备。当然此前你必须给控制设置指定名称。
b.设置Dispatch Functions.
c.设置Fast Io Functions.
d.编写一个my_fs_notify回调函数,在其中绑定刚激活的FS CDO.
e.使用wdff_reg_notify调用注册这个回调函数。
f.编写默认的dispatch functions.
e.处理IRP_MJ_FILE_SYSTEM_CONTROL,在其中监控Volumne的Mount和Dismount.
f.下一步自然是绑定Volumne了,请听下回分解。
7.IRP完成函数,中断级,如何超越中断级别的限制
先讨论一下Volumne设备是如何得到的.首先看以下几个函数:
// ------------------wdf.h 中的内容 -------------------------
typedef VPB wd_vpb;
_inline wd_vpb * wd_dev_vbp(wd_dev *dev)
{
return dev->Vpb;
}
_inline wd_dev * wd_vbp_dev(wd_vpb *vpb)
{
return vpb->DeviceObject;
}
VPB是Volume parameter block.一个数据结构.它的主要作用是把实际存储媒介设备对象和文件系统上的卷设备对象联系起来.
wd_dev_vbp可以让你从一个Storage Device Object得到一个VPB,而wd_vbp_dev这个函数可以得到这个VPB所对应的Volmue设备.
现在首先要得到Storage Device Object.实际上这个东西保存在当前IO_STACK_LOCATION中.
// ------------------wdf.h 中的内容 -----------------------
_inline wd_dev *wd_irpsp_mount_storage(wd_io_stack *irpsp)
{
return irpsp->Parameters.MountVolume.Vpb->RealDevice;
};
那么,从irp出发,我最终可以通过以下的方式得到Volumue设备:
wd_irpsp *irpsp = wd_cur_io_stack(irp);
wd_dev *storage_dev = wd_irpsp_mount_storage(irpsp);
wd_vpb *vpb = wd_dev_vbp(storage_dev);
wd_dev *volume_dev = wd_vbp_dev(vpb);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -