BDA驱动学习笔记(6):错误处理,内存管理以及字符串

 

错误处理:错误处理分为状态代码返回,异常处理和bug check三种,第三种(bug check)也就是我们在98系统里经常见到的蓝屏,nt系统里不常见但也会发生,真是很让人讨厌。不过万一驱动代码执行过程中发现了及其严重的错误,那么给个蓝屏应该是最好的选择,因为既然是“及其严重”的错误,继续执行下去操作系统会被搞坏。

状态代码NTSTATUS是一个32位的整数,表征代码执行成功与否,它的结构如下:

 



Sev
表示严重程度,C表明该状态代码要被原封不动的传回给用户,Facility指出错误由哪个部件产生,Code保存了错误信息。一般而言是否执行成功应该用NT_SUCCESS宏来判断,直接看NTSTATUS是否为0是不对的。

驱动开发中也可以用类似try catch的方法处理异常,不过驱动中的异常处理机制和c++中的机制并不是同一套。而且值得注意的是,开发过程中要严格区分异常和错误,不能吧错误当异常,也最好别把异常当错误。按照我的理解两者的差别是,错误出现后程序就应该退出了,而异常出现的时候你还有补救的机会。

驱动程序中用保存在程序堆栈里的异常帧来处理异常。每次异常发生的时候,系统扫描堆栈,找到异常帧。异常帧中保留了一个过滤程序和一个异常处理程序。如果过滤程序中指定的条件都符合,那么异常处理程序被执行到,否则就跳过。如果没有过滤程序,系统会指派一个默认的;如果没有异常处理程序,系统也会指派一个默认的动作,那就是死机。

驱动程序中的__try , __except__finally关键字的意义和c++中的差不多。__finally块中的代码无论如何都会被执行一遍,即使你的__try块中有return或者goto这样的语句。__except语句中要求有过滤代码,一般是以下三个之一:

EXCEPTION_EXECUTE_HANDLER:告诉系统要执行异常处理程序。如果处理程序中有return或者goto,那么接下来要执行的是__except块之后的代码,而不是返回或者跳转到指定地点。这一点很奇怪,DDK中的描述并非如此,而某本书的作者信誓旦旦的说他做过实验,他是对的,DDK写错了。我选择相信做过实验的人。

EXCEPTION_CONTINUE_SEARCH:告诉系统继续找下一个异常处理程序。如果找不到,系统崩溃。

EXCEPTION_CONTINUE_EXECUTION:告诉系统返回到发生异常的地方继续执行。一般来讲没什么意义,除非你确实可以改变出异常的情况,让异常消失。

值得注意的是,算术异常(除0),页故障和非法指针都不能用异常处理机制处理,其实它们都算是错误。并且能用NTSTATUS处理掉的事情,尽量不要用异常,因为异常处理机制非常耗资源。

bug check是调用KeBugCheckEx()函数来实现的,这个函数从来不返回,因为已经蓝屏了。

我们都知道NT中的内存分为两块,按照安全性和完整性分可以分为用户模式地址和内核模式地址,按照分页能力分可以分为分页内存和非分页内存。内核模式驱动中不能随便使用用户模式下的内存,因为用户传给你一个地址是虚拟地址,指向的内容不定是在内存里还是在磁盘上,如果贸然访问,会引发一个页错误。用户模式可以随便访问用户模式,因为技术指向的内容在磁盘里,虚拟内存组件也会帮你把它倒回到内存中,内核模式就没那么好运了,它什么事情都得自己干。

内核模式内存分为分页内存和非分页内存两种,用户模式全是分页内存,是没权限访问非分页内存的。非分页内存区域永远在内存里,不会被倒到磁盘上去,所以它是一种很宝贵的资源,内存容量怎么说也比磁盘小太多。你可以用alloc_text编译指令指示某个程序段运行在分页程序中,比如#pragma alloc_text(PAGE, AddDevice)指明AddDevice程序段要运行在分页内存里,如果没有指明,那么默认是在非分页内存中。同样的编译指令还有data_seg(“PAGE”),指明让变量存放在分页内存里;code_seg(“PAGE”),指明让程序段运行在分页内存里。记得要用data_seg()code_set()回复默认行为。

内核中申请动态内存的方法是ExAllocatePoll(type, size),和malloc差不多,不过这里需要指定申请内存的类型,常见的类型如下

内存池类型

描述

NonPagedPool

从非分页内存池中分配内存

PagedPool

从分页内存池中分配内存

NonPagedPoolMustSucceed

从非分页内存池中分配内存,如果不能分配则产生bugcheck

NonPagedPoolCacheAligned

从非分页内存池中分配内存,并确保内存与CPU cache对齐

NonPagedPoolCacheAlignedMustS

NonPagedPoolCacheAligned类似,但如果不能分配则产生bugcheck

PagedPoolCacheAligned

从分页内存池中分配内存,并确保内存与CPU cache对齐

注意,申请来的内存至少应该是8字节对齐的。

释放动态内存用ExFreePoolPVOID),动态内存的申请释放千万要小心对付,内核里有内存泄露那是很可怕的事情。另一个申请动态内存的函数是

ExAllocatePoolWithTag(type, size, tag); ExAllocatePoll不同的是,这个函数还需要指定一个标签,放在申请来的内容的开头。微软建议我们一直使用ExAllocatePoolWithTag,为申请来的内存加上我们自己的标签。

驱动中开提供了一组数据结构以方便我们保存数据。Stl中的listqueue等数据结构不能随便用在驱动开发中,很危险,相反的我们应该一直使用内置的数据结构。以链表为例,内核中提供了一个LIST_ENTRY的数据结构好让我们生成需要的链表。这个结构只是用来构造链表和链接链表用的,链表中存放什么数据还得自己定义。这里有个很奇特的地方在于,你创建一个链表Node结构后,LIST_ENTRY是作为它的子项存放的,而并不是LIST_ENTRY中有一个指针可以指向我们的数据块。比如下面这个Node

Struct Node

{

Int value;
}

如何把这个Node放到LIST_ENTRY里?正确的做法是

 

Struct Node

{

Int value;

LIST_ENTRY linkField;
}

而不是什么类似linkField->Data = node之类的操作。

生成的list里保存的都是LIST_ENTRY,而想通过LIST_ENTRY访问到它所在的Node,则需要使用CONTAINING_RECORD宏。 比如:
Node psElement = (Node) CONTAINING_RECORD(psLink, Node, linkfield);

这里顺便要提一下,if或者else语句之后的操作最好用大括号括起来,因为操作到底是函数还是宏定义你是没办法确定的,万一是宏,那就会造成不必要的麻烦。

内核中操作字符串也有自己的一套机制,各函数说明如下:

操作

ANSI串函数

Unicode串函数

Length

strlen

wcslen

Concatenate

strcat, strncat

wcscat, wcsncat, RtlAppendUnicodeStringToString, RtlAppendUnicodeToString

Copy

strcpy, strncpy, RtlCopyString

wcscpy, wcsncpy, RtlCopyUnicodeString

Reverse

_strrev

_wcsrev

Compare

strcmp, strncmp, _stricmp, _strnicmp, RtlCompareString, RtlEqualString

wcscmp, wcsncmp, _wcsicmp, _wcsnicmp, RtlCompareUnicodeString, RtlEqualUnicodeString, RtlPrefixUnicodeString

Initialize

_strset, _strnset, RtlInitAnsiString, RtlInitString

_wcsnset, RtlInitUnicodeString

Search

strchr, strrchr, strspn, strstr

wcschr, wcsrchr, wcsspn, wcsstr

Upper/lowercase

_strlwr, _strupr, RtlUpperString

_wcslwr, _wcsupr, RtlUpcaseUnicodeString

Character

isdigit, islower, isprint, isspace, isupper, isxdigit, tolower, toupper, RtlUpperChar

towlower, towupper, RtlUpcaseUnicodeChar

Format

sprintf, vsprintf, _snprintf, _vsnprintf

swprintf, _snwprintf

String conversion

atoi, atol, _itoa

_itow, RtlIntegerToUnicodeString, RtlUnicodeStringToInteger

Type conversion

RtlAnsiStringToUnicodeSize, RtlAnsiStringToUnicodeString

RtlUnicodeStringToAnsiString

Memory release

RtlFreeAnsiString

RtlFreeUnicodeString

提醒一句,千万小心区分AnsiUnicode

内核中操作大内存块也有自己的一套,具体说明如下:

服务函数或宏

描述

memchr

blob中寻找一个字节

memcpy, RtlCopyBytes, RtlCopyMemory

复制字节,不允许重叠

memmove, RtlMoveMemory

复制字节,允许重叠

memset, RtlFillBytes, RtlFillMemory

用给定的值填充blob

memcmp, RtlCompareMemory, RtlEqualMemory

比较两个blob

memset, RtlZeroBytes, RtlZeroMemory

blob清零

 

posted @ 2007-07-05 14:50  gussing  阅读(3871)  评论(4编辑  收藏  举报