结构化驱动开发。.doc

上传人:天*** 文档编号:955973 上传时间:2018-11-09 格式:DOC 页数:13 大小:75.50KB
下载 相关 举报
结构化驱动开发。.doc_第1页
第1页 / 共13页
结构化驱动开发。.doc_第2页
第2页 / 共13页
结构化驱动开发。.doc_第3页
第3页 / 共13页
结构化驱动开发。.doc_第4页
第4页 / 共13页
结构化驱动开发。.doc_第5页
第5页 / 共13页
点击查看更多>>
资源描述

1、结构化驱动开发开发驱动程序并不是一件容易的事情。更加不幸的是,将驱动程序加入你产品包里边有可能更加糟糕。而且作为 Windows 执行体的一部分的驱动可能干掉你的系统,而这种错误还非常的难以检测。再者系统当机有可能是有规律的出现,也可能是时不时的出现(一般地,你将会希望你的驱动不出现问题,因为她确实在做一些非常重要的事情。更加糟糕的事情是系统统当机有可能破坏你存储,而且当你会现这种情况的时候,已经晚了。因此当你开发驱动程序的时候,你必须得满足以下的条件:你的程序应该不会引起数据的破坏。这个是对于驱动程序设计师的一个最基本的要求,但是这也是最难以评估的。你的程序应该不会造成系统当机。这个要求的目

2、标是在一些不利的情况,当额外连接的设备(如磁盘或者网卡)没有正常工作的情况下,你的程序应该优雅的处理这种异常的情况。优雅的处理一个驱动程序的错误并不是一个十分明晰的定义:她可能意味你确实能够处理这种异常,也可能意味着您至少可将该驱动程序从内存当中卸载,从而允许系统的其他组件运行。对第二点进行扩展,驱动程序当中的代码不应该导到系统当中的发生。这听上去可能有点儿自相矛盾,因为从定义来看,臭虫就是因为不可预知才被称为臭虫,如果可以预知就不是臭虫了。既然不可预知,我如何可以知道这个臭虫是否会造成系统当机。然而,在大部分的情况下,驱动程序设计师其实还是可以为代码可能造成的错误来进行处理,来防止系统的崩溃

3、。驱动程序应该能够将运行情况报告给管理员。例如,一个错误发生之后,驱动程序应该能够向管理员提供造成错误的原因,以便管理员对系统作出相应的调整来解决这个问题,或者了解到这个驱动程序已经出现功能上边的错误或者即将出现错误。即算是你的驱动程序运行正常,你也应该提供报告给管理员,包括一些该驱动使用的方法及一些参数的规则。管理员有可能使用这些规则来完成对驱动的调整以及除错处理。这些要求的完成都是驱动程序设计师的责任。但是好消息是 Windows 操作系统为在你的驱动程序实现这些特性,提供了丰富的支持。异常派发支持(Exception Dispatching Support)一个异常是一个线程当中的代码产

4、生的。而且异常所导致的异常处理例程将会在产生异常的该线程当中执行。因为异常是一个由代码产生的同步事情,所以如果提供了一个同样的条件,异常可以重现。Windows 为异常提供了派发机制。1.为异常的线程建立一个陷阱帧。这个线程的陷阱帧所有已经被破坏的寄存器的值。比如在执行异常代码之后写入的寄存器的值。2.可选的为异常的线程建立一个陷阱帧。这个陷阱帧将包括所有没有被破坏的寄存器的值。所有的 Windows 异常处理程序都会为异常的线程建立一个这样的陷阱帧。3.建立一个异常的结构体。这个结构体包括了异常产生的原因,异常的种类,异常发生的地址以及其他相应的参数。异常的种类在目前的情况下只有EXCEPT

5、ION_NONCOUNTINUEABLE,表明这是一种非常严重的异常,发生这种异常之后系统将不能再继续正常的运行。一些异常有可能还会提供一些参数来表明该异常的一些特定的情况。这里只有EXCEPTION_ACCESS_VOLIDATION 才可以使用参数。一个参数来表明出现内存访问错误 的操作类型是读还是写,另外一个参数来表明出现错误访问的内存地址。4.将控制权交给位于系统内核的异常派发例程来进行处理。这个位于内核的异常派发例程叫作 KiDispatchException().异常处理有可能产生的结果(Possible Outcomes of Exception Processing)异常处理例

6、程将会判断异常发生在何种运行模式下。内核模式和用户模式的处理有些许的不同。在我们探索异常处理的内部的结构之前,先让我们看一些异常处理例程执行后,有可能出现的结果。每一个异常的情况都可由异常处理例程使用以下三种方法来进行处理:异常处理例程会修改将造成异常的条件,然后通过派发消息,让出现的异常的线程继续运行。我们可以考虑一种页错误的情况。异常处理例程将会从第二存储的设备当中将内存的数据取回来或者从网络当中将所请求的数据传储到内存当中,这样当发生异常再继续执行的时候,她就可取到相应页面的数据了。另外一种情况是一段代码段的代码试图分配一块内存。如果该内存分配失败,那么使用返回的内存地址将会产生访问权限

7、错误的异常。让我们看一下这个例子的代码:/分配 4k 的内存SomePtr = ExAllocatePool(PagedPool,4096);/一般情况下使用 ExAllocatePool 的例程能够成功的得到内存,当然也有分配失败的可能。这里我们为了方便,先假定这个内存的申请确实是失败了,而且 SomePtr 将会返回NULL。RtlZeroMemory(SomePtr,4096);/因此如果执行这样一句代码的时候,将会一个内存访问错误 。使用一个 NULL 指针来调用 RtlZeroMemory 将会造成一个内存访问错误。这个时候,异常处理例程会把 SomePtr 填充为一个预先分配的内存

8、块的地址。警告:当我们深入挖掘 RtlZeroMemory 的汇编代码后,我们可以知道即算是异常处理例程已经当 SomePtr 赋值为新的值的时候,也有可能起不到任何作用。我们假设在RtlZeroMemory 之后要进行的串移植指令,有可能 RtlZeroMemory 当中的汇编代码已经将SomePtr 的值存于一个寄存器当中,然后才会执行异常处理例程的分配动作,这样的情况下,虽然 SomePtr 已经被赋值,但实际上错仍然没有得到处理,并且如果异常处理程序设计不合理的话,还有可以能造成异常处理例程的循环调用。如果确实需要的话,异常处理例程可以返回一个常量到出现异常的线程当中来表示线程继续执行

9、。但是实际上这个值并没有什么重要的。异常处理程序将会判断该种异常是否为该异常处理程序可以处理的异常。如果不能处理的话,异常处理程序将会返回一个值来表明她并不能够处理该例程。举例来说,假设存在一个异常处理程序只能处理 EXCEPTION_ACCESS_VOLIDATION 这样一种异常,如果该异常处理程序接收到一种表示内存没有对齐的异常,那么她并不会处理该种异常,相反的她只会返回一个值来表明她并不能够处理该种异常。接下来,异常派发例程就可以将异常传发到其他的异常处理程序当中。异常处理程序将执行一系列的代码来改变异常出现的原因,然后告诉出现的异常的线程继续执行。在这种情况下,异常处理程序并不会在执

10、行后重新运行产生异常的代码,相反的她会使用从出现异常的代码的下一句继续开始执行。这种方法与之前处理异常的程序完全不同,她不是简单的改变产生异常的原因,然后重新执行产生异常的代码。在这种情况下,异常处理程序只会改变异常产生的原因同时期望其下一句代码执行。异常处理程序并不被要求存储在同一个过程或者函数当中。她可以出现在所有的调用结构当中。例设存在一种情况过程 Procedure_A 调用 Procedure_B,然后 Procedure_B 调用Procedure_C。如果在 Procedure_C 当中出现 EXCEPTOIN_ACCESS_VOLIDATION 异常,而且 Procedure_

11、C 和 Procedure_B 都是没有提供这个类型异常的异常处理程序。相反地,Procedure_A 提供相应的异常处理程序。在这种情况之下,系统会调用 Procedure_A 的异常处理程序,而且会保护 Procedure_A 和 Procedure_B 的堆栈帧。正如你所看到的,存在三种处理异常的方法。正如我们接下来将要讨论的一样,一部分的 Procedure_A 和 Procedure_B 的堆栈有可能会引起结束例程的执行。为了避免 Procedure_A 和 Procedure_C 之间的控制转移而造成的死锁等问题,系统将使一种叫做释放的方法来处理异常处理例程的调用。派核心异常(Di

12、spatch Kernel Exception)之前异常处理的三种方式的学习,为我们现在理解内核当中的 KiDispatchException 例程有莫大的好处。下表将是 KiDispatchExcpetion 执行具体的动作:1.首先异常派发例程将会判断内核当中是否存在调试器,如果存在调试器的话,就首先将异常派发到调试器。调试器可以决定她是否处理这种异常,如果处理该种异常则返回 TRUE,否则异常派发例程将会查找其他异常处理程序。需要指出的是有可能调试器会更改 CPU 的代码指示器或者其他的寄存器的值。在这种情况下,异常处理例程不一定会返回到产生异常的代码处然后继续线程代码的执行。如果调试器

13、返加 TURE 来表明调试器已经处理这种异常。那么异常处理例程将会把控制权交回产生异常的线程。2.如果并不存在调试器或者调试器返回 FALSE 来表明她并没有处理这种异常,那么异常处理例程将会试图调用其他的堆栈异常处理程序。基于堆栈的异常处理程序常常使用 RtlDispatchException()例程来调用。这个例程并没有导出给第三方软件开发商。RtlDispatchException()将会遍历整个基于堆栈的异常处理程序,只到其中一个异常处理程序返回了 TRUE,或者已经遍历完成整个异常处理程序链表。在 Windows NT 平台下边支持 SEH 的编绎器会使用这些 Rtl 例程来实现异常

14、处理机制。因而在一般情况,我们其实不必要直接与这些 Rtl 例程打交道。因为编绎器会对这些Rtl 例程进行封装,从而为我们的程序加入异常处理的功能。RTL 包也包括了一些其他的 Rtl 例程来为编绎器的设计师提供设计结束句柄的基础。3.如前所述,在第二步当中,异常处理程序有可以有存在也有可能不存在。如果只有一二个异常处理的程序,那么极有可能该异常将得不到处理。正如这节以后将会讨论的,异常派发例程允许你确定对何种异常进行处理,而对何种例程不进行处理,并且将异常派发到其他的异常处理程序去。如果从 RtlDispatchException 返回的是 FALSE,表明该异常并没有得到处理,这个时候异常

15、派发程序将试图将异常重新转发到调试器,这被称作为 二次尝试 ,就像同样的一个异常重复执行一样。如果存在一个调试器,调试器将会拥有最后一次机会来处理这个异常,同样如果调试器再一次返加 FALSE,那么系统将会调用 KeBugCheckEx 来停止系统的运行,然后显示蓝屏的界面。对异常处理的支持是由 WindowsNT 的内核提供支持的。因此并不是只能使用编绎来发完异常处理机制。你有可能听说过:除非使用编绎器提供的异常处理工具,你不可能使用异常处理机制。Windows 的编绎器支持既支持内核模式代码也支持用户模式代码。异常派发例程:用户模式异常对 RtlDispatchException 的调用只

16、有在内核模式下成立。在用户模式下有些许的不同。一人信号将会被传送到 CP U 的调试端口,如果 CPU 决定响应这个异常,那么KiDispatchExcpetion 将没有任何其他动作会被执行。考虑这种情况:CPU 处理该种异常失败,那么系统将不得不寻求其他的异常处理程序。这个时候如果在内核模式执行异常处理程序将会导到一个巨大的安全漏洞。因此,此时内核的 KiDispatchException 例程将会试图将其控制权交到用户模式下的异常派发例程当中。异常处理程式将会把陷阱帧,异常帧,以及异常记录结构体压入用户模式的堆栈当中。然后,KiDispatchException 将会修改异常记录结构以使

17、得当控制权从陷阱帧以及异常帧当中返回时,可以调用户空间的异常处理程式。在这里修改异常记录结构体是指修改其中的命令指针以使得其指向用户模式的异常处理程式。用户异常处理程式的使用和内核模式的基本相同。如若异常没有找到相应的异常处理程式,一般情况该异常线程将会被缺省的由系统提供的异常处理程式将线程结束。现在你已经知道一个异常是如何处理的,自然而然,你就会问了,我现在该如何使用异常处理机制啊。这正是本章下一节要解说的课题。结构化异常处理(Structured Exception Handling)Windows NT 的执行体严格的按照要求来使用 SEH 来保证任何情况下产生的异常都不会造成系统当机。

18、这对于构建一个稳定的系统是非常重要的。同时你也应该在你的驱动程序当中使用 SEH 来增加你的驱动的稳定性,从而带来更多的用户的笑容。技巧:事实你可以在驱动开发的过程不使用任何的 SEH 技术,但是开发文件系统驱动的时候,我不建议你这样做。因为与文件系统密切相关的缓存管理器以及虚拟内存管理器都使用了SEH 技术并且在与其交流的时候不可避免的会产生异常。文件系统驱动使用 SEH 就可以很好的处理这些异常。如果没有能够处理这些异常的话,系统将会使用 KeBugCheck 来完系统的当机。在我们讨论结构化异常处理结构是什么,以及她的好处之前先来看一下其不能够为我们提供的功能。一定得注意的是结构化异常处

19、理可不是糟糕的驱动设计的万能药。如果你不认真的设计驱动程序,极有可能结构化异常处理并不能够提供任何用处。同时结构化异常处理也不能够处理任何的异常。比如一个运行在 IROL_DISPATCH_LEVEL 的程式出现EXCEPTOIN_ACCESS_VOLIDATION 的时候,SEH 不能够提供任何处理的功能。此时,系统将不得不当机。最后,应该在整个驱动程序的开发的过程都使用 SEH,来获得相应容错性。因此如果只是驱动程序的一部分使用了 SEH,那么 SEH 的容错性功能也将只有一部分。结构化异常处理是一种异常处理机制,这种机制使得驱动程序来处理大部分的异常,而不必使用由系统定义的 KiDisp

20、atchException 所调用的 KeBugCheckEx().笔记:系统并没有为工作内核模式的驱动程式提供缺省的异常处理例程。因此,如若一个驱动程式没有提供异常处理例程,那么异常产生时,将会直接导致系统当机。如前所述,在 Windows 平台下边要使用 SEH 得编绎器的支持。在 Windows 平台下边一个支持 SEH 的编绎器是 C/C+的编绎器。如前所述,当发生一个异常的时候,Windows 的控制权将会被转移到陷阱处理例程当中。然后异常处理程式可能会处理一些异常或者大部分的情况下将该异常转发到异常派发程序 KiDispatchException 当中,然后 KiDispatchE

21、xception 将会寻找由设计师所提供的异常处理例程。在 C+程序当中这些都是对程序设计师透明的,当编绎器遇到 try-except 程序块的时候,她会自动地为程序生成相关的代码,而如果遇到 try-finally,她会自动地去掉堆栈调用链表。以下是两种最主要的结构化异常处理代码:try-except 结构如下:Try/被保护的代码Except(/任何异常过滤的函数)/这一段代码将只有在异常过滤函数返回 EXECUTE_EXCEPTION_HANDLER 才/会执行。这一段代码被称为异常处理代码。/当这一段执行完毕之后系统就会接着执行 try-except 结构体的下一句代码。这个结构提供了

22、被保护的代码,异常过滤结构,以及异常处理的代码。try-finally 结构如下:Try/被保护的代码Finally/被称为结束块的代码,用来进行清理。而且该段代码当一定会得到执行,无论保护代/出现什么情况Try-finally 结构包括两部分:一部分是被保护的代码,另一部分是结束代码。Try-except 结构try-except 结构允许在保护代码出现异常的情况下,将代码控制交给相应的异常处理程式(通过 RtlDispatchException 例程) 。这就要求 C/C+编绎器与 NT 系统内部进行合作来完成该种功能。一旦编绎器遇到 try-except 结构,她将会自动为这个结构块的代

23、码注册一个 Windows内核提供的相应的异常处理代码。当异常发生的时候,使得相应的异常处理程式能够得到执行。每一个由保护代码产生的异常一般情况都会直接被传送到该线程所处的异常处理帧里边的异常处理程式,除非已经被绑定了一个调试器。当然你也有可能并不希望处理一些特定的异常,这个时候你可以使用相应的异常过滤程式来过滤掉你不希望处理的异常。异常过滤程式可以是由多个复杂的语句所组成的。甚至于她可以是一个异常过滤的函数。异常过滤程式往往会返回以下三种情况当中的一种:EXCEPTION_EXECUTE_HANDLEREXCEPTION_CONTINUE_SEARCHEXCEPTION_CONTINUE_E

24、XECUTE当异常过滤程式返回 EXCEPTION_EXECUTE_HANDLER,相应的异常处理程式将会执行。当异常处理程式得到执行之后,整个线程的执行将会转回到异常处理程式之后的第一条语句。为理解这个问题,我们来看一下以下的代码:NTSTATUSMyProcedure_A(int *SomeVariable)Int *APtrNotBeenInitialized = NULL;Int AnotherInaneVariable = 0;NTSTATUS RC;TryMyProcedure_B(APtrNotBeenInitialized);AnotherInaneVariable = 5;/

25、如果上一个过程将会出现异常这一句代码将不会执行。Except(EXCEPTION_EXECUTE_HANDLER)RC = GetExceptionCode();DbgPrint(“encouter exception code %d“, RC);/执行流将会从这时开始,当异常处理程式运行完毕之后。AnotherIanaeVariable = 10;Return(RC);Int MyProcedure_B(char *IHopeThisPtrWasInitialized)Char ACharThatIWillReturn = A;*IHopeThisPtrWasInitilized = ACh

26、arThatIWillReturn;/如果这一句发生异常的话,下一句将得不到执行。ACharThatIWillReturn = B;Return 0;正如你在代码当中看到,过程 B 将会产生一个异常,当她试图将一个字符存储到一个没有初始化的指针所指向的地址的时候。因为过程 B 没有相应的异常处理程式,因此系统将会调用过程 A 的异常处理程式。这里我们为了方便将异常过滤程式设置非常的简单,她只是返回了一个 EXCEPTION_EXECUTE_HANDLER 的值来执行相应的异常处理程式。我们的异常处理程式相当的简单,只是使用 GetExceptionCode 来得相应的异常类型代码,然后调用一个

27、 DbgPrint 将其打印出来。有意思的是异常处理结构之后,整个线程的将会在 AnotherIanaeVariable=10 这一句这里得到恢复。而过程 B 当中产生异常的代码之后的代码都将会被跳过,同时在过程 A 当中的过程 B 之后的代码也会被无情地跳过。这个特性是由卸载堆栈异常处理链表来完成的。尽管在之前的代码当中,异常过滤程式的代码非常的简单,其实她也可以变得相当的复杂。甚至她还可以拥有一个异常过滤处理的函数,但是她得保证她将返回之前所列的三个值当中的一个。你也可以使用 GetExceptionCode 来得到相应的异常代码来作为异常过滤处理函数的参数,当然你也可以使用 GetExc

28、eptionInformation 来将异常发生时的上下文,异常的一些其他信息当成参数转递给异常过滤处理函数。GetException 例程可以在异常过滤程式当中或者异常处理程式当中调用,但是GetExceptionInformation 只可以在异常过滤程式当中被调用。一般情况下,你的异常过滤程式(或者你所有的异常过滤程式)都不会要求使用GetExceptionInformation 所得到的 EXCEPTION_POINTERS 结构来作为参数。当然,你也可以使用这种方法来修改异常结构体当中单个寄存器的值,但这种代码可能会造一些不稳定的因素,而且使整个程式变得难以维护。因此我非常之不推荐使

29、用这种方法。注意你不能够在异常过滤函数当中调用 GetExcpetionCode 或者GetExceptionInformation。请参见下列代码片断来理解异常过滤处理函数的用法:NTSTATUSMyProcedure_A(int *SomeVariable)Int *APtrNotBeenInitialized = NULL;Int AnotherInaneVariable = 0;NTSTATUS RC;TryMyProcedure_B(APtrNotBeenInitialized);AnotherInaneVariable = 5;/如果上一个过程将会出现异常这一句代码将不会执行。Ex

30、cept( MyExceptionFilter ( GetExceptionCode() , GetExceptionInformation ) )RC = GetExceptionCode();DbgPrint(“encouter exception code %d“, RC);/执行流将会从这时开始,当异常处理程式运行完毕之后。AnotherIanaeVariable = 10;Return(RC);Int MyProcedure_B(char *IHopeThisPtrWasInitialized)Char ACharThatIWillReturn = A;*IHopeThisPtrWa

31、sInitilized = ACharThatIWillReturn;/如果这一句发生异常的话,下一句将得不到执行。ACharThatIWillReturn = B;Return 0;Int MyExceptionFilter(Unsigned int ExceptionCode,PEXCPETION_POINTERS ExcpetionPointers,)NTSTATUS RC = EXCEPTION_CONTINUE_SEARCH ;/首先假设我们不能处理该异常Swtich(ExceptionCode)Case EXCEPTION_ACCESS_VOLIDATION:RC = EXCEPT

32、OIN_EXECUTE_HANDLER;Break;Default:Break;/接下来你还可以使用 EXCEPTION_POINTERS 来测试其他方面的值ASSERT(RC = EXCEPTION_EXECUTE_HANDLER | RC = EXCEPTION_CONTINUE_EXECUTE | RC = EXCEPTOIN_CONTINUE_SEARCH)Return (RC);异常处理程式可以嵌套,可以交叉调用。但是记住,异常处理程式并不能处理所有的异常,例如在一个 IRQL 高于 IRQL_DISPATCH_LEVEL 的程式当中出现访问页面异常的时候,会导致系统的蓝屏。因此你一

33、定得明白,结构化异常处理不能够处理所有的异常问题。Try-finally 结构try-finally 结构非常的简单,这个结构当中存在一个结束处理程式,无论被保护的模式是否调用了其了其他的函数,或者非正常直接跳出了整个函数块等等,只要被保护的代码已经离开了其保护块,那么结束异常处理程式将会执行。下面是一段非常简单的演示代码:NTSTATUS MyProcedure_A(int *SomeVairable;Int AnotherVariable;)NTSTATUS RC= NTSTATUS_SUCCESS;int *APtrThatWasNotInitialized = NULL;int Ano

34、therInsaneVariable = 0;int AnotherInsaneVariable2 = 0;TryIf (!AnotherInsaneVariable)AnotherInsaneVariable = 7;*SomeVariable = MyProcedure_B(APtrThatWasNotInitiliazed,AnotherInsaneVariable);/如果过程 B 出现异常 ,那么接下来的句子将不会执行.AnotherInsaneVariable2 = 5; Except(GetExceptionCode() = EXCEPTION_ACCESS_VOLIDATION

35、?EXCEPTION _EXECUTE_HANLDER:EXCEPTION_CONTINUE_SEARCH )ASSERT(AnotherInsaneVariable = 15);/因为过程 B 当中的结构块一定会得到执行,因此该变量的值一定是 15.RC = GetExceptionCode();DbgPrint ( “encouter exceptoin:%d“, RC);ASSERT(AnotherInsaneVariable = 15);/因为过程 B 的结束块一定会被调用,因此该变量的值一定是 15.AnotherInsaneVariable = 0;/表明相应的异常处理都已经结束了.Return (RC);Int MyProcedure_B(int *IWantAInitializedPtr, int AnotherInsaneVariable)Char ACharThatWillReturn = A;TryIf(AnotherInsaneVariable = 7)return (1) ;ACharThatWillReturn = B;FinallyAnotherInsaneVariable = 15;Return (0);

展开阅读全文
相关资源
相关搜索

当前位置:首页 > 重点行业资料库 > 1

Copyright © 2018-2021 Wenke99.com All rights reserved

工信部备案号浙ICP备20026746号-2  

公安局备案号:浙公网安备33038302330469号

本站为C2C交文档易平台,即用户上传的文档直接卖给下载用户,本站只是网络服务中间平台,所有原创文档下载所得归上传人所有,若您发现上传作品侵犯了您的权利,请立刻联系网站客服并提供证据,平台将在3个工作日内予以改正。