驱动开发心得经验和想法

这里记载一些心得经验和想法(没有实验).

这里大多是血的教训!请大家谨记.


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。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值