XiangXiangRen
总版主
总版主
  • 注册日期2003-02-22
  • 最后登录2015-09-01
  • 粉丝13
  • 关注0
  • 积分1042分
  • 威望472点
  • 贡献值1点
  • 好评度145点
  • 原创分13分
  • 专家分1分
阅读:2675回复:7

Windows驱动编程基础教程(连载1)

楼主#
更多 发布于:2008-05-22 21:19
本文作者为楚狂人,有问题请联系QQ16191935,msn: walled_river@hotmail.com

前言

    我经常在网上遇到心如火燎的提问者。他们碰到很多工作中的技术问题,是关于驱动开发的。其实绝大部分他们碰到的“巨大困难”是被老牛们看成初级得不能再初级的问题。比如经常有人定义一个空的UNICODE_STRING,然后往里面拷贝字符串。结果无论如何都是蓝屏。也有人在堆栈中定义一个局部SPIN_LOCK,作为下面的同步用——这样用显然没有任何意义。我无法一一回答这些问题:因为往往要耐心的看他们的代码,才能很不容易的发现这些错误。而且我又不是总是空闲的,可以无休止的去帮网友阅读代码和查找初级错误。但是归根结底,这些问题的出现,是因为现在写驱动的同行越来越多,但是做驱动开发又没有比较基础的,容易读懂的资料。为此我决定从今天开始连载一篇超级入门级的教程,来解决那些最基本的开发问题。老牛们就请无视这篇教程,一笑而过了。

Windows驱动编程基础教程(1.1-1.3)

1.1 使用字符串结构

    常常使用传统C语言的程序员比较喜欢用如下的方法定义和使用字符串:
    
    char    *str = { “my first string” };            // ansi字符串
    wchar_t    *wstr = { L”my first string” };    // unicode字符串
    size_t len = strlen(str);                    // ansi字符串求长度
    size_t wlen = wcslen(wstr);                // unicode字符串求长度
    printf(“%s %ws %d %d”,str,wstr,len,wlen);    // 打印两种字符串

    但是实际上这种字符串相当的不安全。很容易导致缓冲溢出漏洞。这是因为没有任何地方确切的表明一个字符串的长度。仅仅用一个’\0’字符来标明这个字符串的结束。一旦碰到根本就没有空结束的字符串(可能是攻击者恶意的输入、或者是编程错误导致的意外),程序就可能陷入崩溃。
    使用高级C++特性的编码者则容易忽略这个问题。因为常常使用std::string和CString这样高级的类。不用去担忧字符串的安全性了。
    在驱动开发中,一般不再用空来表示一个字符串的结束。而是定义了如下的一个结构:

    typedef struct _UNICODE_STRING {
        USHORT Length;                // 字符串的长度(字节数)
        USHORT MaximumLength;        // 字符串缓冲区的长度(字节数)
        PWSTR    Buffer;                // 字符串缓冲区
    } UNICODE_STRING, *PUNICODE_STRING;

    以上是Unicode字符串,一个字符为双字节。与之对应的还有一个Ansi字符串。Ansi字符串就是C语言中常用的单字节表示一个字符的窄字符串。

    typedef struct _STRING {
        USHORT Length;
        USHORT MaximumLength;
        PSTR Buffer;
    } ANSI_STRING, *PANSI_STRING;

    在驱动开发中四处可见的是Unicode字符串。因此可以说:Windows的内核是使用Uincode编码的。ANSI_STRING仅仅在某些碰到窄字符的场合使用。而且这种场合非常罕见。
    UNICODE_STRING并不保证Buffer中的字符串是以空结束的。因此,类似下面的做法都是错误的,可能会会导致内核崩溃:

    UNICODE_STRING str;
    …
    len = wcslen(str.Buffer);            // 试图求长度。
    DbgPrint(“%ws”,str.Buffer);        // 试图打印str.Buffer。
    
    如果要用以上的方法,必须在编码中保证Buffer始终是以空结束。但这又是一个麻烦的问题。所以,使用微软提供的Rtl系列函数来操作字符串,才是正确的方法。下文逐步的讲述这个系列的函数的使用。

1.2 字符串的初始化

    请回顾之前的UNICODE_STRING结构。读者应该可以注意到,这个结构中并不含有字符串缓冲的空间。这是一个初学者常见的出问题的来源。以下的代码是完全错误的,内核会立刻崩溃:
    
    UNICODE_STRING str;
    wcscpy(str.Buffer,L”my first string!”);
    str.Length = str.MaximumLength = wcslen(L”my first string!”) * sizeof(WCHAR);

    以上的代码定义了一个字符串并试图初始化它的值。但是非常遗憾这样做是不对的。因为str.Buffer只是一个未初始化的指针。它并没有指向有意义的空间。相反以下的方法是正确的:

    // 先定义后,再定义空间
UNICODE_STRING str;
    str.Buffer = L”my first string!”;
    str.Length = str.MaximumLength = wcslen(L”my first string!”) * sizeof(WCHAR);
    … …
    
    上面代码的第二行手写的常数字符串在代码中形成了“常数”内存空间。这个空间位于代码段。将被分配于可执行页面上。一般的情况下不可写。为此,要注意的是这个字符串空间一旦初始化就不要再更改。否则可能引发系统的保护异常。实际上更好的写法如下:
 
    //请分析一下为何这样写是对的:
    UNICODE_STRING str = {
        sizeof(L”my first string!”) – sizeof((L”my first string!”)[0]),    
        sizeof(L”my first string!”),
        L”my first_string!” };

    但是这样定义一个字符串实在太繁琐了。但是在头文件ntdef.h中有一个宏方便这种定义。使用这个宏之后,我们就可以简单的定义一个常数字符串如下:

    #include <ntdef.h>
    UNICODE_STRING str = RTL_CONSTANT_STRING(L“my first string!”);
    
    这只能在定义这个字符串的时候使用。为了随时初始化一个字符串,可以使用RtlInitUnicodeString。示例如下:
    
    UNICODE_STRING str;
    RtlInitUnicodeString(&str,L”my first string!”);

    用本小节的方法初始化的字符串,不用担心内存释放方面的问题。因为我们并没有分配任何内存。

1.3 字符串的拷贝

    因为字符串不再是空结束的,所以使用wcscpy来拷贝字符串是不行的。UNICODE_STRING可以用RtlCopyUnicodeString来进行拷贝。在进行这种拷贝的时候,最需要注意的一点是:拷贝目的字符串的Buffer必须有足够的空间。如果Buffer的空间不足,字符串会拷贝不完全。这是一个比较隐蔽的错误。
    下面举一个例子。

    UNICODE_STRING dst;            // 目标字符串
    WCHAR dst_buf[256];                // 我们现在还不会分配内存,所以先定义缓冲区
    UNICODE_STRING src = RTL_CONST_STRING(L”My source string!”);
    
    // 把目标字符串初始化为拥有缓冲区长度为256的UNICODE_STRING空串。
    RtlInitEmptyString(dst,dst_buf,256*sizeof(WCHAR));
    RtlCopyUnicodeString(&dst,&src);    // 字符串拷贝!
    
    以上这个拷贝之所以可以成功,是因为256比L” My source string!”的长度要大。如果小,则拷贝也不会出现任何明示的错误。但是拷贝结束之后,与使用者的目标不符,字符串实际上被截短了。
    我曾经犯过的一个错误是没有调用RtlInitEmptyString。结果dst字符串被初始化认为缓冲区长度为0。虽然程序没有崩溃,却实际上没有拷贝任何内容。
    在拷贝之前,最谨慎的方法是根据源字符串的长度动态分配空间。在1.2节“内存与链表”中,读者会看到动态分配内存处理字符串的方法。

最新喜欢:

greenpeacegreenp... g20062558g20062... linshierlinshi...
fcgboy
驱动牛犊
驱动牛犊
  • 注册日期2007-08-22
  • 最后登录2016-01-09
  • 粉丝0
  • 关注0
  • 积分18分
  • 威望113点
  • 贡献值0点
  • 好评度23点
  • 原创分0分
  • 专家分0分
沙发#
发布于:2008-05-22 23:44
不错不错,对新手来说很不错。谢谢
microbe
驱动小牛
驱动小牛
  • 注册日期2007-12-10
  • 最后登录2011-01-17
  • 粉丝1
  • 关注0
  • 积分914分
  • 威望420点
  • 贡献值1点
  • 好评度88点
  • 原创分0分
  • 专家分1分
板凳#
发布于:2008-05-23 08:33
有这样的老鸟,对新手来说是莫大的福气!对楚狂人表示由衷的敬佩!
hhyDriver
驱动小牛
驱动小牛
  • 注册日期2007-06-06
  • 最后登录2009-01-19
  • 粉丝0
  • 关注0
  • 积分154分
  • 威望150点
  • 贡献值0点
  • 好评度146点
  • 原创分0分
  • 专家分0分
地板#
发布于:2008-05-23 11:11
顶 顶 顶
alwaysrun
驱动小牛
驱动小牛
  • 注册日期2006-06-01
  • 最后登录2016-01-09
  • 粉丝0
  • 关注0
  • 积分1059分
  • 威望752点
  • 贡献值1点
  • 好评度98点
  • 原创分0分
  • 专家分0分
地下室#
发布于:2008-05-23 11:25
顶一下,楚狂人就是好人啊,^_^
一颗平常的心!
cm007
驱动牛犊
驱动牛犊
  • 注册日期2007-10-31
  • 最后登录2009-11-04
  • 粉丝0
  • 关注0
  • 积分2分
  • 威望38点
  • 贡献值0点
  • 好评度21点
  • 原创分0分
  • 专家分0分
5楼#
发布于:2008-05-23 14:10
强烈支持,刚开始就是基础太差,写代码经常蓝。。。
dt1985324
驱动牛犊
驱动牛犊
  • 注册日期2008-05-06
  • 最后登录2009-02-10
  • 粉丝0
  • 关注0
  • 积分16分
  • 威望106点
  • 贡献值1点
  • 好评度20点
  • 原创分0分
  • 专家分0分
6楼#
发布于:2008-05-30 14:41
膜拜楚大大,准备进入驱动开发了,绝对菜鸟.您对我们的帮助很大
zhoujiamurong
驱动小牛
驱动小牛
  • 注册日期2006-03-20
  • 最后登录2009-05-06
  • 粉丝4
  • 关注0
  • 积分1081分
  • 威望360点
  • 贡献值0点
  • 好评度215点
  • 原创分0分
  • 专家分0分
7楼#
发布于:2008-06-19 10:33
看完,谢谢
游客

返回顶部