WINCE开发更安全可靠设备驱动的最佳实践
作者:LoongEmbedded(kandi)
时间:2011.10.04
类别:WINCE驱动开发
********************************LoongEmbedded********************************
微软总结了WINCE开发更安全可靠设备驱动的最佳实践,如下所示:
1. 根据需要使用异常处理结构(SEH)来处理异常,_try/_except模型就是我们熟知的C++的SHE处理模型,下面根据WAV_IOControl()来进一步学习这SHE处理机制。_except关键字后面跟着是一个各种类型的表达式,在一个函数中,可以有多个_try/_except语句,它们可以是一个平面的线性结构,可以是分层的嵌套结构,也可以是这两种结构的混合使用。
图1
上图的代码的执行流程如下:
1) 受监控的代码先被执行。
2) 如果受监控代码在执行过程中,没有出现异常,那么控制流将转入到_except定义的异常处理代码模块的后面执行,也就是直接返回FALSE。
3) 如果受监控代码出现执行异常,那么控制流将进入到_except后面的表达式中,也即首先计算这个表达式的指,然后再根据这个值来做出相应的处理。
下面来描述这个表达式中的相关知识。
1) GetExceptionCode()
GetExceptionCode()函数用于获取已经发生的异常的类型,该函数只能在筛选表达式(filter expression)或者try-except异常处理的异常处理阻塞(exception-handler block)中调用,它的返回值是下面的值:
EXCEPTION_ACCESS_VIOLATION
调用该函数的程序尝试访问一个它没有合适权限访问的虚拟地址产生的异常。
EXCEPTION_ARRAY_BOUNDS_EXCEEDED
调用该函数的程序尝试访问一个越界的数组元素,而且底层硬件支持越界检查的情况下引发的异常。
EXCEPTION_BREAKPOINT
触发断点时引发的异常。
EXCEPTION_DATATYPE_MISALIGNMENT
程序尝试读或者写未经对齐的数据时产生的异常。
EXCEPTION_FLT_DENORMAL_OPERAND
如果浮点数运算中的操作数的值太小,以致无法采用标准的浮点数表示时产生的异常。
EXCEPTION_FLT_DIVIDE_BY_ZERO
程序尝试对浮点数除法的除数是0进行运算时产生的异常。
EXCEPTION_FLT_INEXACT_RESULT
浮点运算的结果不能精确表示成小数时产生的异常。
EXCEPTION_FLT_INVALID_OPERATION
此异常表示不包含在这些列举中的浮点数异常。
EXCEPTION_FLT_OVERFLOW
浮点运算的指数超过所能表示的最大值引发的异常。
EXCEPTION_FLT_STACK_CHECK
浮点运算发生栈上溢或者下溢时引发的异常。
EXCEPTION_FLT_UNDERFLOW
浮点运算的指数超过所能表示的最小值引发的异常。
EXCEPTION_INT_DIVIDE_BY_ZERO
整数除法的除数是0时引发的异常。
EXCEPTION_INT_OVERFLOW
整数操作的结果溢出时引发的异常。
EXCEPTION_NONCONTINUABLE_EXCEPTION
发生一个不可继续执行的异常时,如果程序继续执行,则会引发此异常。
EXCEPTION_PRIV_INSTRUCTION
程序尝试去执行一条当前CPU模式不支持的指令时引发该异常。
EXCEPTION_SINGLE_STEP
单步调试时,每个陷阱追踪或单条指令执行时引发该异常。
要调用GetExceptionCode()函数,需要把excpt.h头文件包含进来,其中上面代码提到的STATUS_ACCESS_VIOLATION=EXCEPTION_ACCESS_VIOLATION,这在winbase.h中使用宏定义做了等同的定义。
2) Excpt.h对异常处理except()中的合法的值定义如下
#define EXCEPTION_EXECUTE_HANDLER 1
表示系统识别到此异常,并且转移控制到异常处理代码部分继续执行。
#define EXCEPTION_CONTINUE_SEARCH 0
当前的_except模块不是该异常所对应的正确的异常处理模块,系统将继续查找合适的异常处理模块来处理该异常。
#define EXCEPTION_CONTINUE_EXECUTION -1
系统停止查找异常处理并且返回到发生异常的控制流所在的地方,继续执行,但是,如果这个异常是一个不可继续执行的异常时,那么将引发一个EXCEPTION_NONCONTINUABLE_EXCEPTION
的异常。
异常分为系统异常和软件异常,上面列举的就是系统异常,而软件异常通过RaiseException()函数抛出。
3) RaiseException()
在调用的thread中可以通过调用该函数来引发一个异常,该函数的声明如下:
VOID WINAPI RaiseException(
DWORD dwExceptionCode,
DWORD dwExceptionFlags,
DWORD nNumberOfArguments,
CONST DWORD *lpArguments
);
下面描述参数及其功能
dwExceptionCode
一个在引发异常时应用程序定义的异常代码,筛选表达式(也即_except()中的表达式)和异常处理程序的异常处理阻塞可以调用GetExceptionCode()函数来获得异常代码。需要注意的一点是,系统会在显示信息之前清除dwExceptionCode参数的第28位,该位是一个系统保留的异常位,仅供系统自身使用。比如以一个异常代码0xFFFFFFFF调用RaiseException()函数的时候,系统会以异常代码0xEFFFFFFF显示。
dwExceptionFlags
异常标志,这可以赋值为零,表示一个可持续异常,或者使用EXCEPTION_NONCONTINUABLE标志来表示一个不可持续的异常。发生不可持续的异常后,任何企图继续执行的行为将导致触EXCEPTION_NONCONTINUABLE_EXCEPTION异常。
lpArguments
nNumberOfArguments
表示lpArguments参数数组中的参数个数,这个值不能超过EXCEPTION_MAXIMUM_PARAMETERS; 如果lpArguments为NULL ,则忽略此参数。
lpArguments
指向32位参数的数组的长指针,这个参数可以是NULL,这些参数可以包含任何应用程序所定义的数据,而这些数据需要传递给异常处理程序的筛选表达式。那么在筛选表达式可以调用GetExceptionInformation()函数来获取应用程序传递过来的数据。
一个进程可以通过调用RaiseException()函数并且使用异常处理结构来处理私有的,软件引起的或者应用程序定义的异常。下面列出在引发一个异常的时候,调度程序如何查找合适的异常处理程序来处理此异常的步骤:
⑴如果有调试器,系统将尝试通知进程的调试器。
⑵如果这一进程没有被调试,或者相关的调试器没有处理这个异常,系统将尝试通过查找引发异常的线程的堆栈帧的来定位一个基于帧的异常处理程序。系统首先查找当前的堆栈帧,然后继续查找先前的堆栈帧。
⑶如果没有找到基于帧的异常处理程序,或者基于帧的异常处理程序处理该异常,系统将会第二次尝试通知进程的调试器。
⑷如果该进程仍未被调试,或者相关的调试器不能处理该异常,系统根据异常的类型来提供默认的程序,对于大多数异常,默认的动作就是调用ExitProcess函数来退出进程。
2. 在IOCTL调用中检查对嵌套指针(nested pointers)的访问权限(access permissions)。
3. 在非流式接口的入口函数,比如GWES的键盘函数的入口函数中检查对嵌套指针的访问权限。
4. 使用ceddk.dll的函数来访问硬件,不要使用wdm.h中的宏定义,这不仅有利于标准化,更便于移植。
5. 检查任何函数调用的返回值,这样可以识别调用失败或者返回意料之外的结果,以便我们做更合理的处理。
6. 使用DEBUGCHK宏或者相关的宏来检查一些假设(assumptions),但要相应地处理发生的错误情况。
DEBUGCHK宏维护(assert)一个表达式,如果此表达式为FALSE,则会调用DebugBreak函数,DebugBreak函数使当前的进程引起一个断点异常,这样调用的线程可以通知调试器并且强迫它采取行动。但如果调用DebugBreak函数的线程没有附属的调试器,那么DebugBreak函数将被忽略,并且如果没有其他的异常或者调用DebugBreak函数时,这个线程将继续执行。DEBUGCHK宏的用法举例如下:
DEBUGCHK(dwCurrentNumberOfItems < dwMaxNumberOfItems);
如果dwCurrentNumberOfItems大于或者等于dwMaxNumberOfItems,那么对于debug版本的系统来说,将会输出下面的信息
MyProgram: DEBUGCHK failed in file C:\WINCE500\Programs\MyProgram\.\main.c at line 31
也即debug消息会把出错的文件名和行数打印出来。
7. 当一个内部线程访问一个外部提供的buffer的时候,使用CeAllocAsynchronousBuffer函数和SHE,这样可以把调用者的buffer排列(marshals)到内核的虚拟内存中。在WINCE6.0中,不需要改变线程的权限来访问外部提供的buffer。
8. 证实访问设备驱动并且被设备驱动信赖的任何应用程序(Verify that any applications that call into a device driver are trusted)。
9. 检查正在调用的应用程序的信任值,这个信任值通过调用CeGetCallerTrust函数来获得。接着,如果必要的话,Flags的值为DEVFLAGS_TRUSTEDCALLERONLY(0x00010000的)驱动有权强迫只被受信任的调用者的使用,也就是说带有此标志值的驱动只被它信任的应用程序打开,这是相对于WINCE5.0及之前版本的操作系统的概念,对于WINCE6.0之后的就有些不一样了。
10. 使用C/C++的代码分析(code analysis)来编程,这部分祥看帮助文档。