这里记载一些心得经验和想法(没有实验). 这里大多是血的教训!请大家谨记. 1.ExAllocatePoolWithTag不但检查成功否和释放ExFreePoolWithTag,更重要的是要RtlZeroMemory,不然会有乱码等奇怪的现象.最好立马使用,最近使用. 不会C++,没有C++的思想。不过写下面的两个函数倒有进步的思想。 #define TAG 'tset' //驱动在内存的标志,即test //new delete PVOID allocate(IN SIZE_T NumberOfBytes) /* 不建议对申请内存函数的封装,这样对内存泄漏不好定位. */ { PVOID p = NULL; //PAGED_CODE(); if (KeGetCurrentIrql() > DISPATCH_LEVEL) { KdBreakPoint();//DbgBreakPoint() } /* Callers of ExAllocatePoolWithTag must be executing at IRQL <= DISPATCH_LEVEL. A caller executing at DISPATCH_LEVEL must specify a NonPagedXxx value for PoolType. A caller executing at IRQL <= APC_LEVEL can specify any POOL_TYPE value, but the IRQL and environment must also be considered for determining the page type. */ p = ExAllocatePoolWithTag(NonPagedPool, NumberOfBytes, TAG); if (p == NULL ) { return p; } /* Warning Memory that ExAllocatePoolWithTag allocates is uninitialized. A kernel-mode driver must first zero this memory if it is going to make it visible to user-mode software (to avoid leaking potentially privileged contents). */ RtlZeroMemory(p, NumberOfBytes); return p; } VOID free(IN PVOID p) { unsigned long r; /* Callers of ExFreePoolWithTag must be running at IRQL <= DISPATCH_LEVEL. A caller at DISPATCH_LEVEL must have specified a NonPagedXxx PoolType when the memory was allocated. Otherwise, the caller must be running at IRQL <= APC_LEVEL. */ //PAGED_CODE(); if (KeGetCurrentIrql() > DISPATCH_LEVEL) { KdBreakPoint();//DbgBreakPoint() } if (p) //防止多次释放导致的蓝屏。 { __try //防止传入非法的地址。 { r = MmIsAddressValid(p); ExFreePoolWithTag(p, TAG);//KeGetCurrentIrql() > DISPATCH_LEVEL时依旧蓝屏。 } __except (EXCEPTION_EXECUTE_HANDLER) { r = GetExceptionCode();//啥也不做。 } p = NULL; } } 2.不可用BOOL与true或者TRUE比较,因为:typedef int BOOL; 所以: BOOL b = PathIsDirectory(buffer); //if (b == true) //00B4161A cmp dword ptr [ebp-268h],1 //if (b == TRUE) //00B4161A cmp dword ptr [ebp-268h],1 if (b) //cmp dword ptr [ebp-268h],0 3.要对用:RtlAppendUnicodeStringToString或者RtlAppendUnicodeToString或者RtlAppendStringToString对STRING或者UNICODE_STRING追加字符,原有字符结构必须有内存,不能是初始化的. 就是初始化的时候要使用:RtlInitEmptyUnicodeString,而不能使用:RtlInitUnicodeString(&us1,L"\\REGISTRY\\USER\\"); 系统生成的UNICODE_STRING,如文件对象(FileObject)里面的,这是最好传递UNICODE_STRING指针,前提是不能修改这个输入参数,如要修改请备份或者复制一份. 如果传递字符串的地址,再用RtlInitUnicodeString初始化,不仅麻烦而且易出错,因为:字符串的地址没有结束标志,且字符串后面的地址有可能不可以访问,所以会蓝屏. 如果非要传递字符串的地址,建议一定加上字符串的长度,用原始的方法初始UNICODE_STRING,不要用RtlInitUnicodeString了. 4.FltRegisterFilter函数返回STATUS_OBJECT_NAME_NOT_FOUND #define STATUS_OBJECT_NAME_NOT_FOUND ((NTSTATUS)0xC0000034L) 原因是注册表中没有如下内容: Windows Registry Editor Version 5.00 [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\xxxxxx\Instances] "DefaultInstance"="xxxxxx" [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\xxxxxx\Instances\xxxxxx] "Altitude"="371100" "Flags"=dword:00000000 并注意:Altitude的值和注册时要一致. 说明:必须有一个名为"Instances"的子项用于存放驱动的实例信息,该子项下面的字符串值"DefaultInstance"指定了默认实例的名称。 "Instances"项下面的每一个子项表示一个实例,每个实例子项必须有一个字符串值"Altitude"。 FltRegisterFilter函数执行时,如果在注册表中没有找到默认实例的"Altitude"值,将会返回STATUS_OBJECT_NAME_NOT_FOUND错误。 5.ObRegisterCallbacks返回STATUS_ACCESS_DENIED (0xc0000022) 解决办法: 首选办法:sources里面加入:LINKER_FLAGS = $(LINKER_FLAGS)/INTEGRITYCHECK 第二个方案: 注册回调前加段代码,改一个比特位就解决了 代码: PLDR_DATA_TABLE_ENTRY pLdrEntry=(PLDR_DATA_TABLE_ENTRY)pDrvObj->DriverSection; pLdrEntry->Flags |=0x20; 6.如果有两个桌面,在内核中判断或者区分? 1.进程回调加父子关系。我是用链表实现了。 2.未公开的函数,详细的请查询http://doxygen.reactos.org。 3.判断进程内的某个类型的对象的值,即:DeskTop。 4.对象里面有个结构,就是使用这个对象的所有的进程的链表。 7.判断一个文件或者文件夹是不是在另一个文件夹里面。 应用层和驱动层通吃,这是一个方法和思路。 1.判断是不是这个文件夹,之前最好判断一下是不是目录。 2.如果待判断的文件或者目录的长度大于特定的目录,取代判断的目录或者文件夹的长度加一,然后和特定的目录加"\\"比较。 如果是miniFilter,有更方便的办法,因为它解析好了。 另外内核还有一些特殊的函数,至少三类: 1.以str,wcs,_wcs开头的函数。由内核导出,但是不建议使用,因为好多字符串不是以0x00结尾的. 2.刚开始还以为是:RtlLeftChild呢?后来发现了RtlPrefixUnicodeString,实验成功。不过要调用两次,后一次加一个\. 3.FsRtlIsNameInExpression或者FsRtlIsDbcsInExpression等。这个实验始终失败. 8.KeSetEvent使用的一个要点. LONG KeSetEvent(IN PRKEVENT Event, IN KPRIORITY Increment, IN BOOLEAN Wait); 最后一个参数为真,必须马上调用等待函数,不然蓝屏,注意IRQL还会升高,具体的看说明. 这是加班到凌晨4点才解决,后来才明白的. 9.UserMode or KernelMode 很多函数的说明中有这么一句话:Lower-level drivers should specify KernelMode. 实际要怎么做?其实大多说是UserMode.只有系统进程或者驱动专用的是KernelMode. 其实完美的办法是调用ObIsKernelHandle函数.ObOpenObjectByPointer这个函数有点麻烦. 彻底的解决办法是ExGetPreviousMode(VOID). 10.由于VS2013没有一键转换的功能,所以依旧用vs2012,但是要编译:Windows Driver Kit (WDK) 8.1 Preview Samples下的例子,要把工程的编译平台修改为8.0(WindowsKernelModeDriver8.0)即可,注意有两处.不然编译出错. error MSB8020: The builds tools for WindowsKernelModeDriver8.1 (Platform Toolset = 'WindowsKernelModeDriver8.1') cannot be found. To build using the WindowsKernelModeDriver8.1 build tools, either click the Project menu or right-click the solution, and then select "Update VC++ Projects...". Install WindowsKernelModeDriver8.1 to build using the WindowsKernelModeDriver8.1 build tools. 11.数字签名. 这个我也不太懂,有的要5级签名,微软的在最上等. 今天遇到一个,即使签名了也会出现:577 = 0x241 . 其含义为:Windows 无法验证此文件的数字签名。某软件或硬件最近有所更改,可能安装了签名错误或损毁的文件,或者安装的文件可能是来路不明的恶意软件。 改进办法是用命令行签名,最好加上时间信息,不过这又要开发环境了. 12.minifilter在卷挂载(PFLT_INSTANCE_SETUP_CALLBACK)的时候,获取卷设备的一些信息,更多信息请自己扩展. BOOLEAN PrintVolume(__in PCFLT_RELATED_OBJECTS FltObjects) /* 功能:打印挂载的卷的信息。 */ { NTSTATUS status; PVOID Buffer; BOOLEAN r = FALSE; ULONG BufferSizeNeeded; UNICODE_STRING Volume; status = FltGetVolumeName(FltObjects->Volume, NULL, &BufferSizeNeeded); if (status != STATUS_BUFFER_TOO_SMALL) { return FALSE; } Buffer = ExAllocatePoolWithTag(NonPagedPool, BufferSizeNeeded + 2, TAG); if (Buffer == NULL) { return FALSE; } RtlZeroMemory(Buffer,BufferSizeNeeded + 2); Volume.Buffer = Buffer; Volume.Length = (USHORT)BufferSizeNeeded; Volume.MaximumLength = (USHORT)BufferSizeNeeded + 2; status = FltGetVolumeName(FltObjects->Volume, &Volume, &BufferSizeNeeded);//最后一个参数为NULL失败。 if (!NT_SUCCESS(status)) { KdPrint(("FltGetVolumeName fail with error 0x%x!\n",status)); ExFreePoolWithTag(Buffer, TAG); return FALSE; } KdPrint(("挂载的卷为:%wZ\n",&Volume)); ExFreePoolWithTag(Buffer, TAG); return r; } 打印信息有: 挂载的卷为:\Device\Mup 挂载的卷为:\Device\HarddiskVolume1 挂载的卷为:\Device\HarddiskVolume2 挂载的卷为:\Device\HarddiskVolume4 挂载的卷为:\Device\HarddiskVolume3 挂载的卷为:\Device\HarddiskVolume5 挂载的卷为:\Device\CdRom0 13. BOOLEAN IsMyVolume(__in PCFLT_RELATED_OBJECTS FltObjects) { BOOLEAN b = FALSE; PFILE_OBJECT FileObject; UNICODE_STRING uni_disk; UNICODE_STRING Hide_name; NTSTATUS status = STATUS_SUCCESS; FileObject = FltObjects->FileObject;//sp->FileObject; //On Windows Vista and later operating systems, you must ensure that APCs are not disabled before calling this routine. //Call KeAreAllApcsDisabled for this purpose. //ObReferenceObjectByPointer((PVOID)FileObject,0,NULL,KernelMode); status = IoVolumeDeviceToDosName(FileObject->DeviceObject,&uni_disk);//RtlVolumeDeviceToDosName 支持xp之前,但是prefast警告. //ObDereferenceObject(FileObject); if (!NT_SUCCESS( status )) //如果失败了puni_disk->buffer == 0,下面的比较会蓝屏. { return b; } RtlInitUnicodeString(&Hide_name,L"X:"); if (RtlEqualUnicodeString(&Hide_name,&uni_disk,TRUE)) { b = TRUE; } ExFreePool(uni_disk.Buffer);//或者下面的办法. //RtlFreeUnicodeString(&uni_disk); return b; } 14.OACR的使用. 正常情况下,编译之后,双击图标即可显示. 但是非正常情况下: 1.编译驱动 2.check now -> 选项. 3.view warnings -> 选项. 以上是个人理解,并非正确. 15.C和CPP与布尔变量的关系。 C中默认情况下只能使用大写的布尔变量。 C++中注意这是两种不同的数据类型。 在C中测试效果如下: BOOLEAN BX;//BYTE //BOOL B;//int //bool b; BOOLEAN BX1 = TRUE; //BOOLEAN BX2 = true; 注意注释的是错误的,不过在CPP中是都可以的。 谨记,这是一会开发驱动程序,一会写应用层代码所得的。 16.再论UNICODE_STRING。 // // Unicode strings are counted 16-bit character strings. If they are // NULL terminated, Length does not include trailing NULL. // typedef struct _UNICODE_STRING { USHORT Length; USHORT MaximumLength; #ifdef MIDL_PASS [size_is(MaximumLength / 2), length_is((Length) / 2) ] USHORT * Buffer; #else // MIDL_PASS _Field_size_bytes_part_(MaximumLength, Length) PWCH Buffer; #endif // MIDL_PASS } UNICODE_STRING; //上面是文件中的定义。 //下面是文档中的说明。 typedef struct _UNICODE_STRING { USHORT Length; USHORT MaximumLength; PWSTR Buffer; } UNICODE_STRING, *PUNICODE_STRING; Length The length in bytes of the string stored in Buffer. MaximumLength The length in bytes of Buffer. Buffer Pointer to a buffer used to contain a string of wide characters. If the string is NULL-terminated, Length does not include the trailing NULL. 个人理解: 上面的必须看懂并记住。 尽管微软所这是安全的字符串,不会出所谓的问题。 但是使用不当还是会出现蓝屏和莫名奇怪的问题。 因为好像没有函数检验字符串的有效性。 至少在WINDBG的本地变量里面显示的字符串和长度是可以不相符的。 反过来说,理解了这个结构,可以写出一些技巧的代码。 再次重复,长度是字节的长度, 所以在内存地址中定位字符的时候要除以Buffer的单位再减一。 所以复制的时候千万不要长度再乘以Buffer的单位。 这些问题很难发现和定位,费了我两天的时间,才解决,所以写此心得。 17.看《windows内核情景分析》的9.12MDL章节: 更喜欢叫DeviceObject->Flags的: DO_BUFFERED_IO为复制方式,特点:系统申请非分页内存,然后再复制。 DO_DIRECT_IO为映射方式,特点:获取用户地址的物理地址的内核地址。 neither buffered nor direct I/O为直接方式,特点:很少使用或者直接使用,注意DPC/ISR中不可以用。 更多的官方信息: http://msdn.microsoft.com/en-us/library/windows/hardware/ff550869(v=vs.85).aspx Neither I/O Operations http://msdn.microsoft.com/en-us/library/windows/hardware/ff565381(v=vs.85).aspx Using Direct I/O with PIO http://msdn.microsoft.com/en-us/library/windows/hardware/ff565374(v=vs.85).aspx Using Direct I/O with DMA http://msdn.microsoft.com/en-us/library/windows/hardware/ff565356(v=vs.85).aspx Using Buffered I/O 具体的例子可看:\7600.16385.1\src\general\ioctl\wdm\sys。
驱动开发心得经验和想法
最新推荐文章于 2024-01-09 20:34:36 发布