驱动开发心得经验和想法

来源:互联网 发布:纵横家 知大局 编辑:程序博客网 时间:2024/06/04 20:09
这里记载一些心得经验和想法(没有实验).这里大多是血的教训!请大家谨记.1.ExAllocatePoolWithTag不但检查成功否和释放ExFreePoolWithTag,更重要的是要RtlZeroMemory,不然会有乱码等奇怪的现象.最好立马使用,最近使用.不会C++,没有C++的思想。不过写下面的两个函数倒有进步的思想。#define TAG 'tset' //驱动在内存的标志,即test//new deletePVOID 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],03.要对用: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\CdRom013.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 Operationshttp://msdn.microsoft.com/en-us/library/windows/hardware/ff565381(v=vs.85).aspx Using Direct I/O with PIOhttp://msdn.microsoft.com/en-us/library/windows/hardware/ff565374(v=vs.85).aspx Using Direct I/O with DMAhttp://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。
0 0
原创粉丝点击