图十三 UnHandledExceptionFilter 函数的伪代码

UnhandledExceptionFilter( STRUCT _EXCEPTION_POINTERS *pExceptionPtrs ) { PEXCEPTION_RECORD pExcptRec; DWORD currentESP; DWORD retValue; DWORD DEBUGPORT; DWORD dwTemp2; DWORD dwUseJustInTimeDebugger; CHAR szDbgCmdFmt[256]; // 从AeDebug这个注册表键值返回的字符串 CHAR szDbgCmdLine[256]; // 实际的调试器命令行参数(已填入进程ID和事件ID) STARTUPINFO startupinfo; PROCESS_INFORMATION pi; HARDERR_STRUCT harderr; // ??? BOOL fAeDebugAuto; TIB * pTib; // 线程信息块 pExcptRec = pExceptionPtrs->ExceptionRecord; if ( (pExcptRec->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) && (pExcptRec->ExceptionInformation[0]) ) { retValue=BasepCheckForReadOnlyResource(pExcptRec->ExceptionInformation[1]); if ( EXCEPTION_CONTINUE_EXECUTION == retValue ) return EXCEPTION_CONTINUE_EXECUTION; } // 查看这个进程是否运行于调试器下 retValue = NtQueryInformationProcess(GetCurrentProcess(), ProcessDebugPort, &debugPort, sizeof(debugPort), 0 ); if ( (retValue >= 0) && debugPort ) // 通知调试器 return EXCEPTION_CONTINUE_SEARCH; // 用户调用SetUnhandledExceptionFilter了吗? // 如果调用了,那现在就调用他安装的异常处理程序 if ( _BasepCurrentTopLevelFilter ) { retValue = _BasepCurrentTopLevelFilter( pExceptionPtrs ); if ( EXCEPTION_EXECUTE_HANDLER == retValue ) return EXCEPTION_EXECUTE_HANDLER; if ( EXCEPTION_CONTINUE_EXECUTION == retValue ) return EXCEPTION_CONTINUE_EXECUTION; // 只有返回值为EXCEPTION_CONTINUE_SEARCH时才会继续执行下去 } // 调用过SetErrorMode(SEM_NOGPFAULTERRORBOX)吗? { harderr.elem0 = pExcptRec->ExceptionCode; harderr.elem1 = pExcptRec->ExceptionAddress; if ( EXCEPTION_IN_PAGE_ERROR == pExcptRec->ExceptionCode ) harderr.elem2 = pExcptRec->ExceptionInformation[2]; else harderr.elem2 = pExcptRec->ExceptionInformation[0]; dwTemp2 = 1; fAeDebugAuto = FALSE; harderr.elem3 = pExcptRec->ExceptionInformation[1]; pTib = FS:[18h]; DWORD someVal = pTib->pProcess->0xC; if ( pTib->threadID != someVal ) { __try { char szDbgCmdFmt[256]; retValue = GetProfileStringA( "AeDebug", "Debugger", 0, szDbgCmdFmt, sizeof(szDbgCmdFmt)-1 ); if ( retValue ) dwTemp2 = 2; char szAuto[8]; retValue = GetProfileStringA( "AeDebug", "Auto", "0", szAuto, sizeof(szAuto)-1 ); if ( retValue ) if ( 0 == strcmp( szAuto, "1" ) ) if ( 2 == dwTemp2 ) fAeDebugAuto = TRUE; } __except( EXCEPTION_EXECUTE_HANDLER ) { ESP = currentESP; dwTemp2 = 1; fAeDebugAuto = FALSE; } } if ( FALSE == fAeDebugAuto ) { retValue=NtRaiseHardError(STATUS_UNHANDLED_EXCEPTION | 0x10000000, 4, 0, &harderr,_BasepAlreadyHadHardError ? 1 : dwTemp2, &dwUseJustInTimeDebugger ); } else { dwUseJustInTimeDebugger = 3; retValue = 0; } if (retValue >= 0 && (dwUseJustInTimeDebugger == 3) && (!_BasepAlreadyHadHardError)&&(!_BaseRunningInServerProcess)) { _BasepAlreadyHadHardError = 1; SECURITY_ATTRIBUTES secAttr = { sizeof(secAttr), 0, TRUE }; HANDLE hEvent = CreateEventA( &secAttr, TRUE, 0, 0 ); memset( &startupinfo, 0, sizeof(startupinfo) ); sprintf(szDbgCmdLine, szDbgCmdFmt, GetCurrentProcessId(), hEvent); startupinfo.cb = sizeof(startupinfo); startupinfo.lpDesktop = "Winsta0/Default" CsrIdentifyAlertableThread(); // ??? retValue = CreateProcessA( 0, // 应用程序名称 szDbgCmdLine, // 命令行 0, 0, // 进程和线程安全属性 1, // bInheritHandles 0, 0, // 创建标志、环境 0, // 当前目录 &statupinfo, // STARTUPINFO &pi); // PROCESS_INFORMATION if ( retValue && hEvent ) { NtWaitForSingleObject( hEvent, 1, 0 ); return EXCEPTION_CONTINUE_SEARCH; } } if ( _BasepAlreadyHadHardError ) NtTerminateProcess(GetCurrentProcess(), pExcptRec->ExceptionCode); } return EXCEPTION_EXECUTE_HANDLER; } LPTOP_LEVEL_EXCEPTION_FILTER SetUnhandledExceptionFilter( LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter ) { // _BasepCurrentTopLevelFilter是KERNEL32.DLL中的一个全局变量 LPTOP_LEVEL_EXCEPTION_FILTER previous= _BasepCurrentTopLevelFilter; // 设置为新值 _BasepCurrentTopLevelFilter = lpTopLevelExceptionFilter; return previous; // 返回以前的值 }

  UnhandledExceptionFilter接下来的任务是确定进程是否运行于Win32调试器下。也就是进程的创建标志中是否带有标志DEBUG_PROCESS或DEBUG_ONLY_THIS_PROCESS。它使用NtQueryInformationProcess函数来确定进程是否正在被调试,我在本月的Under the Hood专栏中讲解了这个函数。如果正在被调试,UnhandledExceptionFilter就返回 EXCEPTION_CONTINUE_SEARCH,这告诉系统去唤醒调试器并告诉它在被调试程序(debuggee)中产生了一个异常。 
UnhandledExceptionFilter接下来调用用户安装的未处理异常过滤器(如果存在的话)。通常情况下,用户并没有安装回调函数,但是用户可以调用 SetUnhandledExceptionFilter这个API来安装。上面我也提供了这个API的伪代码。这个函数只是简单地用用户安装的回调函数的地址来替换一个全局变量,并返回替换前的值。 
有了初步的准备之后,UnhandledExceptionFilter就开始做它的主要工作:用一个时髦的应用程序错误对话框来通知你犯了低级的编程错误。有两种方法可以避免出现这个对话框。第一种方法是调用SetErrorMode函数并指定SEM_NOGPFAULTERRORBOX标志。另一种方法是将AeDebug子键下的Auto的值设为1。此时UnhandledExceptionFilter跳过应用程序错误对话框直接启动AeDebug 子键下的Debugger的值所指定的调试器。如果你熟悉“即时调试(Just In Time Debugging,JIT)”的话,这就是操作系统支持它的地方。接下来我会详细讲。 
大多数情况下,上面的两个条件都为假。这样UnhandledExceptionFilter就调用NTDLL.DLL中的 NtRaiseHardError函数。正是这个函数产生了应用程序错误对话框。这个对话框等待你单击“确定”按钮来终止进程,或者单击“取消”按钮来调试它。(单击“取消”按钮而不是“确定”按钮来加载调试器好像有点颠倒了,可能这只是我个人的感觉吧。) 
如果你单击“确定”,UnhandledExceptionFilter就返回EXCEPTION_EXECUTE_HANDLER。调用UnhandledExceptionFilter 的进程通常通过终止自身来作为响应(正像你在BaseProcessStart的伪代码中看到的那样)。这就产生了一个有趣的问题——大多数人都认为是系统终止了产生未处理异常的进程,而实际上更准确的说法应该是,系统进行了一些设置使得产生未处理异常的进程将自身终止掉了。 
UnhandledExceptionFilter执行时真正有意思的部分是当你单击应用程序错误对话框中的“取消”按钮,此时系统将调试器附加(attach)到出错进程上。这段代码首先调用 CreateEvent来创建一个事件内核对象,调试器成功附加到出错进程之后会将此事件对象变成有信号状态。这个事件句柄以及出错进程的ID都被传到 sprintf函数,由它将其格式化成一个命令行,用来启动调试器。一切就绪之后,UnhandledExceptionFilter就调用 CreateProcess来启动调试器。如果CreateProcess成功,它就调用NtWaitForSingleObject来等待前面创建的那个事件对象。此时这个调用被阻塞,直到调试器进程将此事件变成有信号状态,以表明它已经成功附加到出错进程上。UnhandledExceptionFilter函数中还有一些其它的代码,我在这里只讲重要的。

进入地狱

  如果你已经走了这么远,不把整个过程讲完对你有点不公平。我已经讲了当异常发生时操作系统是如何调用用户定义的回调函数的。我也讲了这些回调的内部情况,以及编译器是如何使用它们来实现__try和__except的。我甚至还讲了当某个异常没有被处理时所发生的情况以及系统所做的扫尾工作。剩下的就只有异常回调过程最初是从哪里开始的这个问题了。好吧,让我们深入系统内部来看一下结构化异常处理的开始阶段吧。 
图十四是我为 KiUserExceptionDispatcher 函数和一些相关函数写的伪代码。这个函数在NTDLL.DLL中,它是异常处理执行的起点。为了绝对准确起见,我必须指出:刚才说的并不是绝对准确。例如在Intel平台上,一个异常导致CPU将控制权转到ring 0(0特权级,即内核模式)的一个处理程序上。这个处理程序由中断描述符表(Interrupt Descriptor Table,IDT)中的一个元素定义,它是专门用来处理相应异常的。我跳过所有的内核模式代码,假设当异常发生时CPU直接将控制权转到了 KiUserExceptionDispatcher 函数。

图十四 KiUserExceptionDispatcher 的伪代码:

KiUserExceptionDispatcher( PEXCEPTION_RECORD pExcptRec, CONTEXT * pContext ) { DWORD retValue; // 注意:如果异常被处理,那么 RtlDispatchException 函数就不会返回 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; // 从FS:[4]和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 ) // 确保堆栈按DWORD对齐 { pExcptRec->ExceptionFlags |= EH_STACK_INVALID; return DISPOSITION_DISMISS; // 0 } if ( someProcessFlag ) { hLog = RtlpLogExceptionHandler( pExcptRec, pContext, 0, pRegistrationFrame, 0x10 ); } DWORD retValue, dispatcherContext; retValue= RtlpExecuteHandlerForException(pExcptRec, pRegistrationFrame, pContext, &dispatcherContext, pRegistrationFrame->handler ); if ( someProcessFlag ) RtlpLogLastExceptionDisposition( hLog, retValue ); if ( 0 == pRegistrationFrame ) { pExcptRec->ExceptionFlags &= ~EH_NESTED_CALL; // 关闭标志 } 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; // 转到前一个帧 } return DISPOSITION_DISMISS; } _RtlpExecuteHandlerForException: // 处理异常(第一次) MOV EDX,XXXXXXXX JMP ExecuteHandler RtlpExecutehandlerForUnwind: // 处理展开(第二次) MOV EDX,XXXXXXXX int ExecuteHandler( PEXCEPTION_RECORD pExcptRec, PEXCEPTION_REGISTRATION pExcptReg, CONTEXT * pContext, PVOID pDispatcherContext, FARPROC handler ) // 实际上是指向_except_handler()的指针 { // 安装一个EXCEPTION_REGISTRATION帧,EDX指向相应的handler代码 PUSH EDX PUSH FS:[0] MOV FS:[0],ESP // 调用异常处理回调函数 EAX = handler( pExcptRec, pExcptReg, pContext, pDispatcherContext ); // 移除EXCEPTION_REGISTRATION帧 MOV ESP,DWORD PTR FS:[00000000] POP DWORD PTR FS:[00000000] return EAX; } _RtlpExecuteHandlerForException使用的异常处理程序: { // 如果设置了展开标志,返回DISPOSITION_CONTINUE_SEARCH // 否则,给pDispatcherContext赋值并返回DISPOSITION_NESTED_EXCEPTION return pExcptRec->ExceptionFlags & EXCEPTION_UNWIND_CONTEXT ? DISPOSITION_CONTINUE_SEARC : ( *pDispatcherContext = pRegistrationFrame->scopetable, DISPOSITION_NESTED_EXCEPTION ); } _RtlpExecuteHandlerForUnwind使用的异常处理程序: { // 如果设置了展开标志,返回DISPOSITION_CONTINUE_SEARCH // 否则,给pDispatcherContext赋值并返回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中是谁调用了谁

KiUserExceptionDispatcher() RtlDispatchException() RtlpExecuteHandlerForException() ExecuteHandler() // 通常到 __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 代码可以为:

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 必须首先释放堆栈的固定部分,弹出非易失寄存器,然后将控制返回调用函数。例如,

add RSP, fixed-allocation-size pop R13 pop R14 pop R15 ret

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

lea RSP, -128[R13] ; epilogue proper starts here add RSP, fixed-allocation-size pop R13 pop R14 pop R15 ret

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

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 其余部分的执行,从而允许重新创建调用函数的上下文。

转载于:https://blog.51cto.com/masefee/814028

Win32 结构化异常处理(SEH)探秘【下篇】相关推荐

  1. 深入探索Win32结构化异常处理

    原文:http://blog.csdn.net/diamont/article/details/4259590 Matt Pietrek 著 董岩 译 在Win32操作系统提供的所有功能中,使用最广泛 ...

  2. 深入解析结构化异常处理(SEH) - by Matt Pietrek

    目录 1.浅析SEH 2.移向更深处 3.编译器层面的SEH 4.扩展的异常处理帧 5.ShowSEHFrames程序 6.展开 7.未处理异常 8.进入地狱 9.结论 ​尽管以前写过一篇SEH相关的 ...

  3. windows核心编程学习笔记(八)结构化异常处理(Structured Exception Handling)

    首先要要知道,结构化异常处理(SEH)和C++提供的异常处理不相同. 一.Termination HandlersTermination Handlers使用很简单.在想使用SEH处理的地方使用 __ ...

  4. 异常处理第三讲,SEH(结构化异常处理),异常展开问题

    异常处理第三讲,SEH(结构化异常处理),异常展开问题 作者:IBinary 出处:http://www.cnblogs.com/iBinary/ 版权所有,欢迎保留原文链接进行转载:) 不知道昨天有 ...

  5. Windows系统程序设计之结构化异常处理

    标 题: [原创]Windows系统程序设计之结构化异常处理 作 者: 北极星2003 时 间: 2006-09-20,20:21:28 链 接: http://bbs.pediy.com/showt ...

  6. Windows异常世界历险记(五)——VC6中结构化异常处理机制的反汇编分析(下)

    在本系列的上一篇文章Windows异常世界历险记(四)--VC6中结构化异常处理机制的反汇编分析(中)中,给出了针对VC6的异常处理机制进行逆向后得到的伪码.在本文中,我们仍然以之前写的小程序为例,通 ...

  7. 什么是VB.NET的结构化异常处理

    深谈VB.NET结构化异常处理执行标准 对于VB.NET语言来说,在实际编程中有一个非常重要的操作值得我们去注意,那就关于异常方面的处理.今天我们就可以通过VB.NET结构化异常处理的相关介绍,来仔细 ...

  8. Windows内存管理(3)--检查内存可用性,结构化异常处理 和 ASSERT

    1.      检查内存可用性 在驱动程序开发中,对内存的操作要格外小心.如果某段内存是只读的,而驱动程序试图去写操作,会导致系统的崩溃. DDK提供了两个函数,帮助程序员在不知道某段内存是否可读写的 ...

  9. SEH(结构化异常处理)

    文章目录 内容回顾: 总结: 程序代码 实现截图 具体流程 内容回顾: 当用户异常产生后,内核函数KiDispatchException并不是像处理内核异常那样在0环直接处理,而是修正3环EIP为Ki ...

最新文章

  1. MongoDB操作(.net)
  2. Devexpress的Datalayout控件
  3. JQuery源码解析-整体架构的设计
  4. LintCode 183. 木材加工(二分查找)
  5. Lukas-Kanade光流法
  6. pil模块python_python PIL模块的基本使用
  7. android封装aidl接口,Android远端接口AIDL及服务回调用法
  8. LeetCode002-两数相加(Add Two Numbers)-Rust语言实现
  9. W10系统配置java环境变量
  10. javascript手册安卓版_JavaScript 完全手册(2018版)
  11. Java基础系列--static关键字
  12. ASP.NET AJAX 1.0 Beta 2 发布
  13. 链接数据库超级简单的工具类C3P0谁用谁知道
  14. pygame教程笔记
  15. 输入一个三位数,再反向输出
  16. 用户画像pdf_【金猿产品展】易观方舟:智能用户数据中台
  17. Python实现视频转代码舞
  18. Android图像适配
  19. python进阶_基础篇4(数据类型和列表)
  20. 什么是哈希?哈希的模拟实现

热门文章

  1. Vue.js 第二天: 列表渲染
  2. 【quickhybrid】组件(自定义)API的实现
  3. Android备份和添加短信
  4. 移动端触屏click点击事件延迟问题,以及tap的解决方案
  5. Javascript 将时间戳显示为“多久前”
  6. [CLR via C#]25. 线程基础
  7. [笔记]在ubuntu下使用conky
  8. ubuntu12.0.4不能正常关机和重启的解决方法
  9. squid+iptalbes实现透明代理配置记录
  10. JS+CSS打造一款漂亮绿色相册代码