首页 > 其他分享 >[转][翻译]深入理解Win32结构化异常处理(四)

[转][翻译]深入理解Win32结构化异常处理(四)

时间:2024-06-05 22:35:45浏览次数:24  
标签:结构化 异常 翻译 EXCEPTION Win32 DISPOSITION 寄存器 堆栈 Epilog

 

如果你已经走了这么远,不把整个过程讲完对你有点不公平。我已经讲了当异常发生时操作系统是如何调用用户定义的回调函数的。我也讲了这些回调的内部情况,以及编译器是如何使用它们来实现__try和__except的。我甚至还讲了当某个异常没有被处理时所发生的情况以及系统所做的扫尾工作。剩下的就只有异常回调过程最初是从哪里开始的这个问题了。好吧,让我们深入系统内部来看一下结构化异常处理的开始阶段吧。

  图十四是我为 KiUserExceptionDispatcher 函数和一些相关函数写的伪代码。这个函数在NTDLL.DLL中,它是异常处理执行的起点。为了绝对准确起见,我必须指出:刚才说的并不是绝对准确。例如在Intel平台上,一个异常导致CPU将控制权转到ring 0(0特权级,即内核模式)的一个处理程序上。这个处理程序由中断描述符表(Interrupt Descriptor Table,IDT)中的一个元素定义,它是专门用来处理相应异常的。我跳过所有的内核模式代码,假设当异常发生时CPU直接将控制权转到了 KiUserExceptionDispatcher 函数。

View Code
KiUserExceptionDispatcher( PEXCEPTION_RECORD pExcptRec, CONTEXT * pContext ) {     DWORD retValue;      // Note: If the exception is handled, RtlDispatchException() never returns     if ( RtlDispatchException( pExceptRec, pContext ) )         retValue = NtContinue( pContext, 0 );     else         retValue = NtRaiseException( pExceptRec, pContext, 0 );      EXCEPTION_RECORD excptRec2;      excptRec2.ExceptionCode = retValue;     excptRec2.ExceptionFlags = EXCEPTION_NONCONTINUABLE;     excptRec2.ExceptionRecord = pExcptRec;     excptRec2.NumberParameters = 0;      RtlRaiseException( &excptRec2 ); }  int RtlDispatchException( PEXCEPTION_RECORD pExcptRec, CONTEXT * pContext ) {     DWORD   stackUserBase;     DWORD   stackUserTop;     PEXCEPTION_REGISTRATION pRegistrationFrame;     DWORD hLog;      // Get stack boundaries from FS:[4] and FS:[8]     RtlpGetStackLimits( &stackUserBase, &stackUserTop );      pRegistrationFrame = RtlpGetRegistrationHead();      while ( -1 != pRegistrationFrame )     {         PVOID justPastRegistrationFrame = &pRegistrationFrame + 8;         if ( stackUserBase > justPastRegistrationFrame )         {             pExcptRec->ExceptionFlags |= EH_STACK_INVALID;             return DISPOSITION_DISMISS; // 0         }          if ( stackUsertop < justPastRegistrationFrame )         {             pExcptRec->ExceptionFlags |= EH_STACK_INVALID;             return DISPOSITION_DISMISS; // 0         }          if ( pRegistrationFrame & 3 )   // Make sure stack is DWORD aligned         {             pExcptRec->ExceptionFlags |= EH_STACK_INVALID;             return DISPOSITION_DISMISS; // 0         }          if ( someProcessFlag )         {             // Doesn't seem to do a whole heck of a lot.             hLog = RtlpLogExceptionHandler( pExcptRec, pContext, 0,                                             pRegistrationFrame, 0x10 );         }          DWORD retValue, dispatcherContext;          retValue= RtlpExecuteHandlerForException(pExcptRec, pRegistrationFrame,                                                  pContext, &dispatcherContext,                                                  pRegistrationFrame->handler );          // Doesn't seem to do a whole heck of a lot.         if ( someProcessFlag )             RtlpLogLastExceptionDisposition( hLog, retValue );          if ( 0 == pRegistrationFrame )         {             pExcptRec->ExceptionFlags &= ~EH_NESTED_CALL;   // Turn off flag         }          EXCEPTION_RECORD excptRec2;          DWORD yetAnotherValue = 0;          if ( DISPOSITION_DISMISS == retValue )         {             if ( pExcptRec->ExceptionFlags & EH_NONCONTINUABLE )             {                 excptRec2.ExceptionRecord = pExcptRec;                 excptRec2.ExceptionNumber = STATUS_NONCONTINUABLE_EXCEPTION;                 excptRec2.ExceptionFlags = EH_NONCONTINUABLE;                 excptRec2.NumberParameters = 0                 RtlRaiseException( &excptRec2 );             }             else                 return DISPOSITION_CONTINUE_SEARCH;         }         else if ( DISPOSITION_CONTINUE_SEARCH == retValue )         {         }         else if ( DISPOSITION_NESTED_EXCEPTION == retValue )         {             pExcptRec->ExceptionFlags |= EH_EXIT_UNWIND;             if ( dispatcherContext > yetAnotherValue )                 yetAnotherValue = dispatcherContext;         }         else    // DISPOSITION_COLLIDED_UNWIND         {             excptRec2.ExceptionRecord = pExcptRec;             excptRec2.ExceptionNumber = STATUS_INVALID_DISPOSITION;             excptRec2.ExceptionFlags = EH_NONCONTINUABLE;             excptRec2.NumberParameters = 0             RtlRaiseException( &excptRec2 );         }          pRegistrationFrame = pRegistrationFrame->prev;  // Go to previous frame     }      return DISPOSITION_DISMISS; }   _RtlpExecuteHandlerForException:    // Handles exception (first time through)     MOV     EDX,XXXXXXXX     JMP     ExecuteHandler   RtlpExecutehandlerForUnwind:        // Handles unwind (second time through)     MOV     EDX,XXXXXXXX    int ExecuteHandler( PEXCEPTION_RECORD pExcptRec                     PEXCEPTION_REGISTRATION pExcptReg                     CONTEXT * pContext                     PVOID pDispatcherContext,                     FARPROC handler ) // Really a ptr to an _except_handler()      // Set up an EXCEPTION_REGISTRATION, where EDX points to the     // appropriate handler code shown below     PUSH    EDX     PUSH    FS:[0]     MOV     FS:[0],ESP      // Invoke the exception callback function     EAX = handler( pExcptRec, pExcptReg, pContext, pDispatcherContext );      // Remove the minimal EXCEPTION_REGISTRATION frame      MOV     ESP,DWORD PTR FS:[00000000]     POP     DWORD PTR FS:[00000000]      return EAX; }  Exception handler used for _RtlpExecuteHandlerForException: {     // If unwind flag set, return DISPOSITION_CONTINUE_SEARCH, else     // assign pDispatcher context and return DISPOSITION_NESTED_EXCEPTION      return pExcptRec->ExceptionFlags & EXCEPTION_UNWIND_CONTEXT                 ? DISPOSITION_CONTINUE_SEARCH                  : *pDispatcherContext = pRegistrationFrame->scopetable,                   DISPOSITION_NESTED_EXCEPTION; }  Exception handler used for _RtlpExecuteHandlerForUnwind: {     // If unwind flag set, return DISPOSITION_CONTINUE_SEARCH, else     // assign pDispatcher context and return DISPOSITION_COLLIDED_UNWIND      return pExcptRec->ExceptionFlags & EXCEPTION_UNWIND_CONTEXT                 ? DISPOSITION_CONTINUE_SEARCH                  : *pDispatcherContext = pRegistrationFrame->scopetable,                   DISPOSITION_COLLIDED_UNWIND; }

KiUserExceptionDispatcher 的核心是对 RtlDispatchException 的调用。这拉开了搜索已注册的异常处理程序的序幕。如果某个处理程序处理这个异常并继续执行,那么对 RtlDispatchException 的调用就不会返回。如果它返回了,只有两种可能:或者调用了NtContinue以便让进程继续执行,或者产生了新的异常。如果是这样,那异常就不能再继续处理了,必须终止进程。

  现在把目光对准 RtlDispatchException 函数的代码,这就是我通篇提到的遍历异常帧的代码。这个函数获取一个指向EXCEPTION_REGISTRATION 结构链表的指针,然后遍历此链表以寻找一个异常处理程序。由于堆栈可能已经被破坏了,所以这个例程非常谨慎。在调用每个EXCEPTION_REGISTRATION结构中指定的异常处理程序之前,它确保这个结构是按DWORD对齐的,并且是在线程的堆栈之中,同时在堆栈中比前一个EXCEPTION_REGISTRATION结构高。

  RtlDispatchException并不直接调用EXCEPTION_REGISTRATION结构中指定的异常处理程序。相反,它调用 RtlpExecuteHandlerForException来完成这个工作。根据RtlpExecuteHandlerForException的执行情况,RtlDispatchException或者继续遍历异常帧,或者引发另一个异常。这第二次的异常表明异常处理程序内部出现了错误,这样就不能继续执行下去了。

  RtlpExecuteHandlerForException的代码与RtlpExecuteHandlerForUnwind的代码极其相似。你可能会回忆起来在前面讨论展开时我提到过它。这两个“函数”都只是简单地给EDX寄存器加载一个不同的值然后就调用ExecuteHandler函数。也就是说,RtlpExecuteHandlerForException和RtlpExecuteHandlerForUnwind都是 ExecuteHanlder这个公共函数的前端。

  ExecuteHandler查找EXCEPTION_REGISTRATION结构的handler域的值并调用它。令人奇怪的是,对异常处理回调函数的调用本身也被一个结构化异常处理程序封装着。在SEH自身中使用SEH看起来有点奇怪,但你思索一会儿就会理解其中的含义。如果在异常回调过程中引发了另外一个异常,操作系统需要知道这个情况。根据异常发生在最初的回调阶段还是展开回调阶段,ExecuteHandler或者返回DISPOSITION_NESTED_EXCEPTION,或者返回DISPOSITION_COLLIDED_UNWIND。这两者都是“红色警报!现在把一切都关掉!”类型的代码。

  如果你像我一样,那不仅理解所有与SEH有关的函数非常困难,而且记住它们之间的调用关系也非常困难。为了帮助我自己记忆,我画了一个调用关系图(图十五)。

  图十五 在SEH中是谁调用了谁

View Code
Figure 15   Who Calls Who in SEH  KiUserExceptionDispatcher()      RtlDispatchException()          RtlpExecuteHandlerForException()              ExecuteHandler() // Normally goes to __except_handler3  ---------  __except_handler3()      scopetable filter-expression()      __global_unwind2()          RtlUnwind()              RtlpExecuteHandlerForUnwind()      scopetable __except block()

现在要问:在调用ExecuteHandler之前设置EDX寄存器的值有什么用呢?这非常简单。如果ExecuteHandler在调用用户安装的异常处理程序的过程中出现了什么错误,它就把EDX指向的代码作为原始的异常处理程序。它把EDX寄存器的值压入堆栈作为原始的 EXCEPTION_REGISTRATION结构的handler域。这基本上与我在MYSEH和MYSEH2中对原始的结构化异常处理的使用情况一样。

  结论

  结构化异常处理是Win32一个非常好的特性。多亏有了像Visual C++之类的编译器的支持层对它的封装,一般的程序员才能付出比较小的学习代价就能利用SEH所提供的便利。但是在操作系统层面上,事情远比Win32文档说的复杂。

  不幸的是,由于人人都认为系统层面的SEH是一个非常困难的问题,因此至今这方面的资料都不多。在本文中,我已经向你指出了系统层面的SEH就是围绕着简单的回调在打转。如果你理解了回调的本质,在此基础上分层理解,系统层面的结构化异常处理也不是那么难掌握。

附录:关于 “prolog 和 epilog ”

  在 Visual C++ 文档中,微软对 prolog 和 epilog  的解释是:“保护现场和恢复现场” 此附录摘自微软 MSDN 库,详细信息参见:

http://msdn.microsoft.com/en-us/library/tawsa7cb(VS.80).aspx(英文)

http://msdn.microsoft.com/zh-cn/library/tawsa7cb(VS.80).aspx(中文) 每个分配堆栈空间、调用其他函数、保存非易失寄存器或使用异常处理的函数必须具有 Prolog,Prolog 的地址限制在与各自的函数表项关联的展开数据中予以说明(请参见异常处理 (x64))。Prolog 将执行以下操作:必要时将参数寄存器保存在其内部地址中;将非易失寄存器推入堆栈;为局部变量和临时变量分配堆栈的固定部分;(可选)建立帧指针。关联的展开数据必须描述 Prolog 的操作,必须提供撤消 Prolog 代码的影响所需的信息。

  如果堆栈中的固定分配超过一页(即大于 4096 字节),则该堆栈分配的范围可能超过一个虚拟内存页,因此在实际分配之前必须检查分配情况。为此,提供了一个特殊的例程,该例程可从 Prolog 调用,并且不会损坏任何参数寄存器。

  保存非易失寄存器的首选方法是:在进行固定堆栈分配之前将这些寄存器移入堆栈。如果在保存非易失寄存器之前执行了固定堆栈分配,则很可能需要 32 位位移以便对保存的寄存器区域进行寻址(据说寄存器的压栈操作与移动操作一样快,并且在可预见的未来一段时间内都应该是这样,尽管压栈操作之间存在隐含的相关性)。可按任何顺序保存非易失寄存器。但是,在 Prolog 中第一次使用非易失寄存器时必须对其进行保存。

  典型的 Prolog 代码可以为:

View Code
mov [RSP + 8], RCX push R15 push R14 push R13 sub RSP, fixed-allocation-size lea R13, 128[RSP

此 Prolog 执行以下操作:将参数寄存器 RCX 存储在其标识位置;保存非易失寄存器 R13、R14、R15;分配堆栈帧的固定部分;建立帧指针,该指针将 128 字节地址指向固定分配区域。使用偏移量以后,便可以通过单字节偏移量对多个固定分配区域进行寻址。

  如果固定分配大小大于或等于一页内存,则在修改 RSP 之前必须调用 helper 函数。此 __chkstk helper 函数负责探测待分配的堆栈范围,以确保对堆栈进行正确的扩展。在这种情况下,前面的 Prolog 示例应变为:

mov [RSP + 8], RCX   push R15   push R14   push R13   mov RAX, fixed-allocation-size   call __chkstk   sub RSP, RAX   lea R13, 128[RSP]   ..

 

  .除了 R10、R11 和条件代码以外,此 __chkstk helper 函数不会修改任何寄存器。特别是,此函数将返回未更改的 RAX,并且不会修改所有非易失寄存器和参数传递寄存器。

  Epilog 代码位于函数的每个出口。通常只有一个 Prolog,但可以有多个 Epilog。Epilog 代码执行以下操作:必要时将堆栈修整为其固定分配大小;释放固定堆栈分配;从堆栈中弹出非易失寄存器的保存值以还原这些寄存器;返回。

  对于展开代码,Epilog 代码必须遵守一组严格的规则,以便通过异常和中断进行可靠的展开。这样可以减少所需的展开数据量,因为描述每个 Epilog 不需要额外数据。通过向前扫描整个代码流以标识 Epilog,展开代码可以确定 Epilog 正在执行。

  如果函数中没有使用任何帧指针,则 Epilog 必须首先释放堆栈的固定部分,弹出非易失寄存器,然后将控制返回调用函数。例如,

双击代码全选
1 2 3 4 5 6 7 8 9 add RSP, fixed-allocation-size   pop R13   pop R14   pop R15   ret

 

  如果函数中使用了帧指针,则在执行 Epilog 之前必须将堆栈修整为其固定分配。这在技术上不属于 Epilog。例如,下面的 Epilog 可用于撤消前面使用的 Prolog:

双击代码全选
1 2 3 4 5 6 7 8 9 10 11 12 13 lea RSP, -128[R13]   ; epilogue proper starts here   add RSP, fixed-allocation-size   pop R13   pop R14   pop R15   ret

 

  在实际应用中,使用帧指针时,没有必要分两个步骤调整 RSP,因此应改用以下 Epilog:

双击代码全选
1 2 3 4 5 6 7 8 9 lea RSP, fixed-allocation-size – 128[R13]   pop R13   pop R14   pop R15   ret

 

  以上是 Epilog 的唯一合法形式。它必须由 add RSP,constant 或 lea RSP,constant[FPReg] 组成,后跟一系列零或多个 8 字节寄存器 pop、一个 return 或一个 jmp。(Epilog 中只允许 jmp 语句的子集。仅限于具有 ModRM 内存引用的 jmp 类,其中 ModRM mod 字段值为 00。在 ModRM mod 字段值为 01 或 10 的 Epilog 中禁止使用 jmp。有关允许使用的 ModRM 引用的更多信息,请参见“AMD x86-64 Architecture Programmer’s Manual Volume 3: General Purpose and System Instructions”(AMD x86-64 结构程序员手册第 3 卷:通用指令和系统指令)中的表 A-15。)不能出现其他代码。特别是,不能在 Epilog 内进行调度,包括加载返回值。

  请注意,未使用帧指针时,Epilog 必须使用 add RSP,constant 释放堆栈的固定部分,而不能使用 lea RSP,constant[RSP]。由于此限制,在搜索 Epilog 时展开代码具有较少的识别模式。

  通过遵守这些规则,展开代码便可以确定某个 Epilog 当前正在执行,并可以模拟该 Epilog 其余部分的执行,从而允许重新创建调用函数的上下文


---------------------
作者:salomon
来源:CNBLOGS
原文:https://www.cnblogs.com/salomon/archive/2012/06/20/2556363.html
版权声明:本文为作者原创文章,转载请附上博文链接!
内容解析By:CSDN,CNBLOG博客文章一键转载插件

标签:结构化,异常,翻译,EXCEPTION,Win32,DISPOSITION,寄存器,堆栈,Epilog
From: https://www.cnblogs.com/xiaomingerniu/p/18234036

相关文章

  • OCPP1.6 v2 之 ocpp-1.6 edition 2 (中文翻译)
    开放充电点协议1.6第二版最终版,2017年9月28日目录1.范围.................................................................................................42.术语和约定.............................................................................................
  • RFC 6455-websocket协议 -- 中文翻译
    英文文档的在源地址:RFC6455-TheWebSocketProtocol中文翻译如下:(未格式化整理)///互联网工程任务组(IETF)请求评论:6455作者:I.Fette公司:Google,Inc.类别:标准轨道ISSN:2070-1721A.Melnikov公司:IsodeLtd.2011年12月WebSocket协议摘要WebSocket协议允许在受......
  • ARP协议:网络世界的地址翻译官
    一.引言  在当今快速发展的汽车行业中,车载以太网正逐步成为推动汽车智能化、网联化浪潮的核心技术之一。作为传统以太网技术在汽车领域的创新应用,车载以太网不仅继承了以太网的开放性、成熟性和互操作性,还针对车辆特有的环境和需求进行了优化与定制,为车载内部的复杂数据传......
  • Navi日语社App一款支持日文OCR文字识别提取的应用,功能丰富,支持日语翻译、语音翻译、日
    如果你正在寻找一款简单好用、功能丰富的日文OCR识别软件,那么推荐你试试《Navi日语社》App,在安卓和苹果手机上,很多应用都支持免费的日语翻译功能,但是支持日文OCR文字识别的软件并不多,针对这一痛点,准橙翻译开发上线了《Navi日语社》App,一款支持日文OCR识别提取文字的移动软件,识......
  • 老挝语翻译通App中国人出门在外都在用的老挝语翻译工具,支持老挝文OCR识别、文字转语音
    老挝语翻译通App,一款更加符合中国人用语习惯的翻译工具,在国内外都能正常使用的翻译器。当大家选择去东南亚国家旅游、GAP的时候,老挝这个国家是值得一去的,可以让大家感受到另一番风情。但是,在去之前,需要做一些准备:衣食住行都要提前规划和准备好,而贯穿整个旅途的另一个容易被......
  • QNX-9—QNX官网文档翻译—中断-3—Writing an Interrupt Handler
    翻译:QNXSoftwareDevelopmentPlatform-->Programming-->Programmer'sGuidehttps://www.qnx.com/developers/docs/7.1/index.html#com.qnx.doc.neutrino.prog/topic/inthandler.html前言:及时处理硬件事件的关键是硬件产生中断。中断只是处理器正在执行的操作的暂停或中断......
  • 关于结构化分析方法(状态图、ER图)
    课堂作业1.尝试建模电梯的状态图(选)2.学校规定:一个学生可选修多门课,一门课有若干学生选修;一个教师可讲授多门课,一门课只有一个教师讲授;一个学生选修一门课,仅有一个成绩。学生的属性有学号、学生姓名;教师的属性有教师编号,教师姓名;课程的属性有课程号、课程名。要求:根据上述语义......
  • 翻译《The Old New Thing》- What a drag: Dragging a Uniform Resource Locator (URL
    Whatadrag:DraggingaUniformResourceLocator(URL)andtext-TheOldNewThinghttps://devblogs.microsoft.com/oldnewthing/20080313-00/?p=23123RaymondChen 2008年03月13日 麻烦的拖拽:拖拽统一资源定位符(URL)和文本简要        这篇文章主要讲......
  • 翻译《The Old New Thing》- What a drag: Dragging a Uniform Resource Locator (URL
    Whatadrag:DraggingaUniformResourceLocator(URL)-TheOldNewThing(microsoft.com)https://devblogs.microsoft.com/oldnewthing/20080312-00/?p=23133RaymondChen 2008年03月12日麻烦的拖拽:拖拽统一资源定位符(URL)简要本文介绍了如何在Windows程序中实......
  • 深度学习论文翻译解析(二十二):Uniformed Students Student-Teacher Anomaly Detection W
    论文标题:UniformedStudentsStudent-TeacherAnomalyDetectionWithDiscriminativeLatentEmbbeddings论文作者: PaulBergmann MichaelFauser DavidSattlegger CarstenSteger论文地址:https://openaccess.thecvf.com/content_CVPR_2020/papers/Bergmann_Uninformed......