windows 提供的异常处理机制实际上只是一个简单的框架。我们通常所用的异常处理(比如 C++ 的 throw、try、catch)都是编译器在系统提供的异常处理机制上进行加工了的增强版本。这里先抛开增强版的不提,先说原始版本。
     原始版本的机制很简单:谁都可以触发异常,谁都可以处理异常(只要它能看得见)。但是不管是触发还是处理都得先注册。系统把这些注册信息保存在一个链表里,并且这个链表保存在线程的数据结构里。也就是说,异常所涉及的一些行为都是线程相关的。比如,线程 T1 触发的异常就只能由线程 T1 来处理,其他线程根本就不知道 T1 发生了什么事,更不会处理。等注册完毕后,线程就可以抛出或处理异常了,系统也可以做相应的管理工作了。
系统提供的管理工作简单来说包括(但不限于):找到触发异常的线程的异常处理链表(前面注册的那个),然后按照规则对该异常进行分发,根据分发后的处理结果再进行下一步的分发或者结束处理。
     系统管理所使用的数据结构:

  1. #define EXCEPTION_CHAIN_END ((struct _EXCEPTION_REGISTRATION_RECORD * POINTER_32)-1)
  2.  
  3. typedef enum _EXCEPTION_DISPOSITION {
  4. ExceptionContinueExecution,
  5. ExceptionContinueSearch,
  6. ExceptionNestedException,
  7. ExceptionCollidedUnwind
  8. } EXCEPTION_DISPOSITION;
  9.  
  10. typedef struct _EXCEPTION_RECORD {
  11. DWORD ExceptionCode;
  12. DWORD ExceptionFlags;
  13. struct _EXCEPTION_RECORD *ExceptionRecord;
  14. PVOID ExceptionAddress;
  15. DWORD NumberParameters;
  16. ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
  17. } EXCEPTION_RECORD;
  18.  
  19. typedef EXCEPTION_RECORD *PEXCEPTION_RECORD;
  20.  
  21. typedef
  22. EXCEPTION_DISPOSITION
  23. (*PEXCEPTION_ROUTINE) (
  24. IN struct _EXCEPTION_RECORD *ExceptionRecord,
  25. IN PVOID EstablisherFrame,
  26. IN OUT struct _CONTEXT *ContextRecord,
  27. IN OUT PVOID DispatcherContext
  28. );
  29.  
  30. typedef struct _EXCEPTION_REGISTRATION_RECORD {
  31. //指向下一个 EXCEPTION_REGISTRATION_RECORD,由此构成一个异常注册信息链表。
  32. //链表中的最后一个结点会将 Next 置为 EXCEPTION_CHAIN_END,表示链表到此结束。
  33. struct _EXCEPTION_REGISTRATION_RECORD *Next;
  34. PEXCEPTION_ROUTINE Handler; //指向异常处理函数
  35. } EXCEPTION_REGISTRATION_RECORD;
  36.  
  37. typedef EXCEPTION_REGISTRATION_RECORD *PEXCEPTION_REGISTRATION_RECORD;

当接收到异常后,系统找到当前线程的异常链表,从链表中的第一个结点开始遍历,找到一个 EXCEPTION_REGISTRATION_RECORD 就调用它的 Handler,并把该异常(类型为 EXCEPTION_RECORD 的参数)表示传递给该 Handler,Handler 处理并返回一个类型为 EXCEPTION_DISPOSITION 的枚举值。该返回值指示系统下一步该做什么:
  ExceptionContinueExecution 表示:已经处理了异常,回到异常触发点继续执行。
  ExceptionContinueSearch 表示:没有处理异常,继续遍历异常链表。
  ExceptionCollidedUnwind 表示在展开过程中再次触发异常。

ExceptionNestedException这里先不做解释

这样系统根据不同的返回值来继续遍历异常链表或者回到触发点继续执行。

内核模式异常处理:

首先,CPU 执行的指令触发了异常,CPU 改执行 IDT 中 KiTrap??,KiTrap?? 会调用 KiDispatchException。

该函数原型如下:  功能如名字一样,分发异常

  1. VOID KiDispatchException (
  2. IN PEXCEPTION_RECORD ExceptionRecord,
  3. IN PKEXCEPTION_FRAME ExceptionFrame,
  4. IN PKTRAP_FRAME TrapFrame,
  5. IN KPROCESSOR_MODE PreviousMode,
  6. IN BOOLEAN FirstChance );

Wrk中关于KiDispatchException的源代码在后面贴出,基本流程就是:

检查 ExceptionRecord->ExceptionCode,
如果是 STATUS_BREAKPOINT,那么将 CONTEXT::Eip 减一;
如果是 KI_EXCEPTION_ACCESS_VIOLATION,那么将检查是否是由 AtlThunk 触发(这个小环节没有深究),
如果是触发 NX(不可执行),那么将 ExceptionRecord->ExceptionInformation [0] 置为 0(貌似表示触发操作的类型,0表示读、1表示写),MSDN有详细解释,推荐阅读
如果 PreviousMode 是 KernelMode,
         那么如果 FirstChance 为 TRUE,那么将该异常传达给内核调试器,如果内核调试器没有处理,那么调用 RtlDispatchException 进行处理。
         如果 FirstChance 为 FALSE,那么再次将该异常传达给内核调试器,如果内核调试器没有处理,那么 BUGCHECK。
如果 PreviousMode 是 UserMode,
        那么,如果 FirstChance 为 TRUE,那么将该异常传达给内核调试器,如果内核调试器没有处理,那么将异常传达给应用层调试器。
                     如果仍然没有处理,那么将 KTRAP_FRAME 和 EXCEPTION_RECORD 拷贝到 UserMode 的栈中,并设置 KTRAP_FRAME::Eip 设置为                               ntdll!KiUserExceptionDispatcher,返回(将该异常交由应用层异常处理程序进行处理)。
         如果 FirstChance 为 FALSE,那么再次将异常传达给应用层调试器,如果仍然没有处理,那么调用 ZwTerminateProcess 结束进程,并 BUGCHECK。

  1. VOID
  2. KiDispatchException (
  3. IN PEXCEPTION_RECORD ExceptionRecord,
  4. IN PKEXCEPTION_FRAME ExceptionFrame,
  5. IN PKTRAP_FRAME TrapFrame,
  6. IN KPROCESSOR_MODE PreviousMode,
  7. IN BOOLEAN FirstChance
  8. )
  9.  
  10. /*++
  11.  
  12. Routine Description:
  13.  
  14. This function is called to dispatch an exception to the proper mode and
  15. to cause the exception dispatcher to be called. If the previous mode is
  16. kernel, then the exception dispatcher is called directly to process the
  17. exception. Otherwise the exception record, exception frame, and trap
  18. frame contents are copied to the user mode stack. The contents of the
  19. exception frame and trap are then modified such that when control is
  20. returned, execution will commense in user mode in a routine which will
  21. call the exception dispatcher.
  22.  
  23. Arguments:
  24.  
  25. ExceptionRecord - Supplies a pointer to an exception record.
  26.  
  27. ExceptionFrame - Supplies a pointer to an exception frame. For NT386,
  28. this should be NULL.
  29.  
  30. TrapFrame - Supplies a pointer to a trap frame.
  31.  
  32. PreviousMode - Supplies the previous processor mode.
  33.  
  34. FirstChance - Supplies a boolean value that specifies whether this is
  35. the first (TRUE) or second (FALSE) chance for the exception.
  36.  
  37. Return Value:
  38.  
  39. None.
  40.  
  41. --*/
  42.  
  43. {
  44.  
  45. CONTEXT ContextRecord;
  46. BOOLEAN DebugService;
  47. EXCEPTION_RECORD ExceptionRecord1;
  48. BOOLEAN ExceptionWasForwarded = FALSE;
  49. ULONG64 FaultingRsp;
  50. PMACHINE_FRAME MachineFrame;
  51. ULONG64 UserStack1;
  52. ULONG64 UserStack2;
  53.  
  54. //
  55. // Move machine state from trap and exception frames to a context frame
  56. // and increment the number of exceptions dispatched.
  57. //
  58.  
  59. KeGetCurrentPrcb()->KeExceptionDispatchCount += ;
  60. ContextRecord.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS | CONTEXT_SEGMENTS;
  61. //在当前栈中分配一个 CONTEXT,调用 KeContextFromKframes 初始化它
  62. KeContextFromKframes(TrapFrame, ExceptionFrame, &ContextRecord);
  63.  
  64. //
  65. // If the exception is a break point, then convert the break point to a
  66. // fault.
  67. //
  68.  
  69. if (ExceptionRecord->ExceptionCode == STATUS_BREAKPOINT) {
  70. ContextRecord.Rip -= ;
  71. }
  72.  
  73. //
  74. // If the exception is an internal general protect fault, invalid opcode,
  75. // or integer divide by zero, then attempt to resolve the problem without
  76. // actually raising an exception.
  77. //
  78.  
  79. if (KiPreprocessFault(ExceptionRecord,
  80. TrapFrame,
  81. &ContextRecord,
  82. PreviousMode) != FALSE) {
  83.  
  84. goto Handled1;
  85. }
  86.  
  87. //
  88. // Select the method of handling the exception based on the previous mode.
  89. //
  90.  
  91. if (PreviousMode == KernelMode) {
  92.  
  93. //
  94. // Previous mode was kernel.
  95. //
  96. // If the kernel debugger is active, then give the kernel debugger
  97. // the first chance to handle the exception. If the kernel debugger
  98. // handles the exception, then continue execution. Otherwise, attempt
  99. // to dispatch the exception to a frame based handler. If a frame
  100. // based handler handles the exception, then continue execution.
  101. //
  102. // If a frame based handler does not handle the exception, give the
  103. // kernel debugger a second chance, if it's present.
  104. //
  105. // If the exception is still unhandled call bugcheck.
  106. //
  107.  
  108. if (FirstChance != FALSE) {
  109. if ((KiDebugRoutine)(TrapFrame, //内核调试器处理 KdpTrap KdpStub
  110. ExceptionFrame,
  111. ExceptionRecord,
  112. &ContextRecord,
  113. PreviousMode,
  114. FALSE) != FALSE) {
  115.  
  116. goto Handled1;
  117. }
  118.  
  119. //
  120. // Kernel debugger didn't handle exception.
  121. //
  122. // If interrupts are disabled, then bugcheck.
  123. //
  124.  
  125. if (RtlDispatchException(ExceptionRecord, &ContextRecord) != FALSE) {
  126. goto Handled1;
  127. }
  128. }
  129.  
  130. //
  131. // This is the second chance to handle the exception.
  132. //
  133.  
  134. if ((KiDebugRoutine)(TrapFrame,
  135. ExceptionFrame,
  136. ExceptionRecord,
  137. &ContextRecord,
  138. PreviousMode,
  139. TRUE) != FALSE) {
  140.  
  141. goto Handled1;
  142. }
  143. //蓝屏
  144. KeBugCheckEx(KMODE_EXCEPTION_NOT_HANDLED,
  145. ExceptionRecord->ExceptionCode,
  146. (ULONG64)ExceptionRecord->ExceptionAddress,
  147. ExceptionRecord->ExceptionInformation[],
  148. ExceptionRecord->ExceptionInformation[]);
  149.  
  150. } else { //UserMode
  151.  
  152. //
  153. // Previous mode was user.
  154. //
  155. // If this is the first chance and the current process has a debugger
  156. // port, then send a message to the debugger port and wait for a reply.
  157. // If the debugger handles the exception, then continue execution. Else
  158. // transfer the exception information to the user stack, transition to
  159. // user mode, and attempt to dispatch the exception to a frame based
  160. // handler. If a frame based handler handles the exception, then continue
  161. // execution with the continue system service. Else execute the
  162. // NtRaiseException system service with FirstChance == FALSE, which
  163. // will call this routine a second time to process the exception.
  164. //
  165. // If this is the second chance and the current process has a debugger
  166. // port, then send a message to the debugger port and wait for a reply.
  167. // If the debugger handles the exception, then continue execution. Else
  168. // if the current process has a subsystem port, then send a message to
  169. // the subsystem port and wait for a reply. If the subsystem handles the
  170. // exception, then continue execution. Else terminate the process.
  171. //
  172. // If the current process is a wow64 process, an alignment fault has
  173. // occurred, and the AC bit is set in EFLAGS, then clear AC in EFLAGS
  174. // and continue execution. Otherwise, attempt to resolve the exception.
  175. //
  176.  
  177. if ((PsGetCurrentProcess()->Wow64Process != NULL) &&
  178. (ExceptionRecord->ExceptionCode == STATUS_DATATYPE_MISALIGNMENT) &&
  179. ((TrapFrame->EFlags & EFLAGS_AC_MASK) != )) {
  180.  
  181. TrapFrame->EFlags &= ~EFLAGS_AC_MASK;
  182. goto Handled2;
  183. }
  184.  
  185. //
  186. // If the exception happened while executing 32-bit code, then convert
  187. // the exception to a wow64 exception. These codes are translated later
  188. // by wow64.
  189. //
  190.  
  191. if ((ContextRecord.SegCs & 0xfff8) == KGDT64_R3_CMCODE) {
  192.  
  193. switch (ExceptionRecord->ExceptionCode) {
  194. case STATUS_BREAKPOINT:
  195. ExceptionRecord->ExceptionCode = STATUS_WX86_BREAKPOINT;
  196. break;
  197.  
  198. case STATUS_SINGLE_STEP:
  199. ExceptionRecord->ExceptionCode = STATUS_WX86_SINGLE_STEP;
  200. break;
  201. }
  202.  
  203. //
  204. // Clear the upper 32-bits of the stack address and 16-byte
  205. // align the stack address.
  206. //
  207.  
  208. FaultingRsp = (ContextRecord.Rsp & 0xfffffff0UI64);
  209.  
  210. } else {
  211. FaultingRsp = ContextRecord.Rsp;
  212. }
  213.  
  214. if (FirstChance == TRUE) {
  215.  
  216. //
  217. // This is the first chance to handle the exception.
  218. //
  219. // If the current processor is not being debugged and user mode
  220. // exceptions are not being ignored, or this is a debug service,
  221. // then attempt to handle the exception via the kernel debugger.
  222. //
  223.  
  224. DebugService = KdIsThisAKdTrap(ExceptionRecord,
  225. &ContextRecord,
  226. UserMode);
  227.  
  228. if (((PsGetCurrentProcess()->DebugPort == NULL) &&
  229. (KdIgnoreUmExceptions == FALSE)) ||
  230. (DebugService == TRUE)) {
  231.  
  232. //
  233. // Attempt to handle the exception with the kernel debugger.
  234. //
  235.  
  236. if ((KiDebugRoutine)(TrapFrame,
  237. ExceptionFrame,
  238. ExceptionRecord,
  239. &ContextRecord,
  240. PreviousMode,
  241. FALSE) != FALSE) {
  242.  
  243. goto Handled1;
  244. }
  245. }
  246.  
  247. if ((ExceptionWasForwarded == FALSE) &&
  248. (DbgkForwardException(ExceptionRecord, TRUE, FALSE))) {
  249.  
  250. goto Handled2;
  251. }
  252.  
  253. //
  254. // Clear the trace flag in the trap frame so a spurious trace
  255. // trap is guaranteed not to occur in the trampoline code.
  256. //
  257.  
  258. TrapFrame->EFlags &= ~EFLAGS_TF_MASK;
  259.  
  260. //
  261. // Transfer exception information to the user stack, transition
  262. // to user mode, and attempt to dispatch the exception to a frame
  263. // based handler.
  264. //
  265.  
  266. ExceptionRecord1.ExceptionCode = STATUS_ACCESS_VIOLATION;
  267.  
  268. repeat:
  269. try {
  270.  
  271. //
  272. // Compute address of aligned machine frame, compute address
  273. // of exception record, compute address of context record,
  274. // and probe user stack for writeability.
  275. //
  276.  
  277. MachineFrame =
  278. (PMACHINE_FRAME)((FaultingRsp - sizeof(MACHINE_FRAME)) & ~STACK_ROUND);
  279.  
  280. UserStack1 = (ULONG64)MachineFrame - EXCEPTION_RECORD_LENGTH;
  281. UserStack2 = UserStack1 - CONTEXT_LENGTH;
  282. ProbeForWriteSmallStructure((PVOID)UserStack2,
  283. sizeof(MACHINE_FRAME) + EXCEPTION_RECORD_LENGTH + CONTEXT_LENGTH,
  284. STACK_ALIGN);
  285.  
  286. //
  287. // Fill in machine frame information.
  288. //
  289.  
  290. MachineFrame->Rsp = FaultingRsp;
  291. MachineFrame->Rip = ContextRecord.Rip;
  292.  
  293. //
  294. // Copy exception record to the user stack.
  295. //
  296.  
  297. *(PEXCEPTION_RECORD)UserStack1 = *ExceptionRecord;
  298.  
  299. //
  300. // Copy context record to the user stack.
  301. //
  302.  
  303. *(PCONTEXT)UserStack2 = ContextRecord;
  304.  
  305. //
  306. // Set the address of the new stack pointer in the current
  307. // trap frame.
  308. //
  309.  
  310. TrapFrame->Rsp = UserStack2;
  311.  
  312. //
  313. // Set the user mode 64-bit code selector.
  314. //
  315.  
  316. TrapFrame->SegCs = KGDT64_R3_CODE | RPL_MASK;
  317.  
  318. //
  319. // Set the address of the exception routine that will call the
  320. // exception dispatcher and then return to the trap handler.
  321. // The trap handler will restore the exception and trap frame
  322. // context and continue execution in the routine that will
  323. // call the exception dispatcher.
  324. //
  325.  
  326. TrapFrame->Rip = (ULONG64)KeUserExceptionDispatcher;
  327. return;
  328.  
  329. } except (KiCopyInformation(&ExceptionRecord1,
  330. (GetExceptionInformation())->ExceptionRecord)) {
  331.  
  332. //
  333. // If the exception is a stack overflow, then attempt to
  334. // raise the stack overflow exception. Otherwise, the user's
  335. // stack is not accessible, or is misaligned, and second
  336. // chance processing is performed.
  337. //
  338.  
  339. if (ExceptionRecord1.ExceptionCode == STATUS_STACK_OVERFLOW) {
  340. ExceptionRecord1.ExceptionAddress = ExceptionRecord->ExceptionAddress;
  341. *ExceptionRecord = ExceptionRecord1;
  342.  
  343. goto repeat;
  344. }
  345. }
  346. }
  347.  
  348. //
  349. // This is the second chance to handle the exception.
  350. //
  351.  
  352. if (DbgkForwardException(ExceptionRecord, TRUE, TRUE)) {
  353. goto Handled2;
  354.  
  355. } else if (DbgkForwardException(ExceptionRecord, FALSE, TRUE)) {
  356. goto Handled2;
  357.  
  358. } else {
  359. ZwTerminateProcess(NtCurrentProcess(), ExceptionRecord->ExceptionCode);
  360. KeBugCheckEx(KMODE_EXCEPTION_NOT_HANDLED,
  361. ExceptionRecord->ExceptionCode,
  362. (ULONG64)ExceptionRecord->ExceptionAddress,
  363. ExceptionRecord->ExceptionInformation[],
  364. ExceptionRecord->ExceptionInformation[]);
  365. }
  366. }
  367.  
  368. //
  369. // Move machine state from context frame to trap and exception frames and
  370. // then return to continue execution with the restored state.
  371. //
  372.  
  373. Handled1:
  374. KeContextToKframes(TrapFrame,
  375. ExceptionFrame,
  376. &ContextRecord,
  377. ContextRecord.ContextFlags,
  378. PreviousMode);
  379.  
  380. //
  381. // Exception was handled by the debugger or the associated subsystem
  382. // and state was modified, if necessary, using the get state and set
  383. // state capabilities. Therefore the context frame does not need to
  384. // be transferred to the trap and exception frames.
  385. //
  386.  
  387. Handled2:
  388. return;
  389. }

KiDispatchException

我们主要关注KernelMode的异常处理情况,重点是RtlDispatchException的操作:

函数流程大致如下:

遍历当前线程的异常链表,挨个调用 RtlpExecuteHandlerForException,RtlpExecuteHandlerForException 会调用异常处理函数。再根据返回值做出不同的处理:

对于 ExceptionContinueExecution,结束遍历,返回。(对于标记为‘EXCEPTION_NONCONTINUABLE’的异常,会调用 RtlRaiseException。)
对于 ExceptionContinueSearch,继续遍历下一个结点;
对于 ExceptionNestedException,则从指定的新异常继续遍历;
只有正确处理 ExceptionContinueExecution 才会返回 TRUE,其他情况都返回 FALSE。

  1. BOOLEAN
  2. RtlDispatchException (
  3. IN PEXCEPTION_RECORD ExceptionRecord,
  4. IN PCONTEXT ContextRecord
  5. )
  6.  
  7. /*++
  8.  
  9. Routine Description:
  10.  
  11. This function attempts to dispatch an exception to a call frame based
  12. handler by searching backwards through the stack based call frames. The
  13. search begins with the frame specified in the context record and continues
  14. backward until either a handler is found that handles the exception, the
  15. stack is found to be invalid (i.e., out of limits or unaligned), or the end
  16. of the call hierarchy is reached.
  17.  
  18. Arguments:
  19.  
  20. ExceptionRecord - Supplies a pointer to an exception record.
  21.  
  22. ContextRecord - Supplies a pointer to a context record.
  23.  
  24. Return Value:
  25.  
  26. If the exception is handled by one of the frame based handlers, then
  27. a value of TRUE is returned. Otherwise a value of FALSE is returned.
  28.  
  29. --*/
  30.  
  31. {
  32.  
  33. BOOLEAN Completion = FALSE;
  34. DISPATCHER_CONTEXT DispatcherContext;
  35. EXCEPTION_DISPOSITION Disposition;
  36. PEXCEPTION_REGISTRATION_RECORD RegistrationPointer;
  37. PEXCEPTION_REGISTRATION_RECORD NestedRegistration;
  38. ULONG HighAddress;
  39. ULONG HighLimit;
  40. ULONG LowLimit;
  41. EXCEPTION_RECORD ExceptionRecord1;
  42. ULONG Index;
  43.  
  44. //
  45. // Get current stack limits.
  46. //
  47.  
  48. RtlpGetStackLimits(&LowLimit, &HighLimit);
  49.  
  50. //
  51. // Start with the frame specified by the context record and search
  52. // backwards through the call frame hierarchy attempting to find an
  53. // exception handler that will handler the exception.
  54. //
  55.  
  56. RegistrationPointer = RtlpGetRegistrationHead();
  57. NestedRegistration = ;
  58.  
  59. while (RegistrationPointer != EXCEPTION_CHAIN_END) {
  60.  
  61. //
  62. // If the call frame is not within the specified stack limits or the
  63. // call frame is unaligned, then set the stack invalid flag in the
  64. // exception record and return FALSE. Else check to determine if the
  65. // frame has an exception handler.
  66. //
  67.  
  68. HighAddress = (ULONG)RegistrationPointer +
  69. sizeof(EXCEPTION_REGISTRATION_RECORD);
  70.  
  71. if ( ((ULONG)RegistrationPointer < LowLimit) ||
  72. (HighAddress > HighLimit) ||
  73. (((ULONG)RegistrationPointer & )
  74. ) {
  75.  
  76. //
  77. // Allow for the possibility that the problem occured on the
  78. // DPC stack.
  79. //
  80.  
  81. ULONG TestAddress = (ULONG)RegistrationPointer;
  82.  
  83. ) &&
  84. KeGetCurrentIrql() >= DISPATCH_LEVEL) {
  85.  
  86. PKPRCB Prcb = KeGetCurrentPrcb();
  87. ULONG DpcStack = (ULONG)Prcb->DpcStack;
  88.  
  89. if ((Prcb->DpcRoutineActive) &&
  90. (HighAddress <= DpcStack) &&
  91. (TestAddress >= DpcStack - KERNEL_STACK_SIZE)) {
  92.  
  93. //
  94. // This error occured on the DPC stack, switch
  95. // stack limits to the DPC stack and restart
  96. // the loop.
  97. //
  98.  
  99. HighLimit = DpcStack;
  100. LowLimit = DpcStack - KERNEL_STACK_SIZE;
  101. continue;
  102. }
  103. }
  104.  
  105. ExceptionRecord->ExceptionFlags |= EXCEPTION_STACK_INVALID;
  106. goto DispatchExit;
  107. }
  108.  
  109. // See if the handler is reasonable
  110.  
  111. if (!RtlIsValidHandler(RegistrationPointer->Handler)) {
  112. ExceptionRecord->ExceptionFlags |= EXCEPTION_STACK_INVALID;
  113. goto DispatchExit;
  114. }
  115.  
  116. //
  117. // The handler must be executed by calling another routine
  118. // that is written in assembler. This is required because
  119. // up level addressing of the handler information is required
  120. // when a nested exception is encountered.
  121. //
  122.  
  123. if (NtGlobalFlag & FLG_ENABLE_EXCEPTION_LOGGING) {
  124. Index = RtlpLogExceptionHandler(
  125. ExceptionRecord,
  126. ContextRecord,
  127. ,
  128. (PULONG)RegistrationPointer,
  129. * sizeof(ULONG));
  130. // can't use sizeof(EXCEPTION_REGISTRATION_RECORD
  131. // because we need the 2 dwords above it.
  132. }
  133.  
  134. Disposition = RtlpExecuteHandlerForException(
  135. ExceptionRecord,
  136. (PVOID)RegistrationPointer,
  137. ContextRecord,
  138. (PVOID)&DispatcherContext,
  139. (PEXCEPTION_ROUTINE)RegistrationPointer->Handler);
  140.  
  141. if (NtGlobalFlag & FLG_ENABLE_EXCEPTION_LOGGING) {
  142. RtlpLogLastExceptionDisposition(Index, Disposition);
  143. }
  144.  
  145. //
  146. // If the current scan is within a nested context and the frame
  147. // just examined is the end of the context region, then clear
  148. // the nested context frame and the nested exception in the
  149. // exception flags.
  150. //
  151.  
  152. if (NestedRegistration == RegistrationPointer) {
  153. ExceptionRecord->ExceptionFlags &= (~EXCEPTION_NESTED_CALL);
  154. NestedRegistration = ;
  155. }
  156.  
  157. //
  158. // Case on the handler disposition.
  159. //
  160.  
  161. switch (Disposition) {
  162.  
  163. //
  164. // The disposition is to continue execution. If the
  165. // exception is not continuable, then raise the exception
  166. // STATUS_NONCONTINUABLE_EXCEPTION. Otherwise return
  167. // TRUE.
  168. //
  169.  
  170. case ExceptionContinueExecution :
  171. if ((ExceptionRecord->ExceptionFlags &
  172. EXCEPTION_NONCONTINUABLE) != ) {
  173. ExceptionRecord1.ExceptionCode =
  174. STATUS_NONCONTINUABLE_EXCEPTION;
  175. ExceptionRecord1.ExceptionFlags = EXCEPTION_NONCONTINUABLE;
  176. ExceptionRecord1.ExceptionRecord = ExceptionRecord;
  177. ExceptionRecord1.NumberParameters = ;
  178. RtlRaiseException(&ExceptionRecord1);
  179.  
  180. } else {
  181. Completion = TRUE;
  182. goto DispatchExit;
  183. }
  184.  
  185. //
  186. // The disposition is to continue the search. If the frame isn't
  187. // suspect/corrupt, get next frame address and continue the search
  188. //
  189.  
  190. case ExceptionContinueSearch :
  191. if (ExceptionRecord->ExceptionFlags & EXCEPTION_STACK_INVALID)
  192. goto DispatchExit;
  193.  
  194. break;
  195.  
  196. //
  197. // The disposition is nested exception. Set the nested
  198. // context frame to the establisher frame address and set
  199. // nested exception in the exception flags.
  200. //
  201.  
  202. case ExceptionNestedException :
  203. ExceptionRecord->ExceptionFlags |= EXCEPTION_NESTED_CALL;
  204. if (DispatcherContext.RegistrationPointer > NestedRegistration) {
  205. NestedRegistration = DispatcherContext.RegistrationPointer;
  206. }
  207.  
  208. break;
  209.  
  210. //
  211. // All other disposition values are invalid. Raise
  212. // invalid disposition exception.
  213. //
  214.  
  215. default :
  216. ExceptionRecord1.ExceptionCode = STATUS_INVALID_DISPOSITION;
  217. ExceptionRecord1.ExceptionFlags = EXCEPTION_NONCONTINUABLE;
  218. ExceptionRecord1.ExceptionRecord = ExceptionRecord;
  219. ExceptionRecord1.NumberParameters = ;
  220. RtlRaiseException(&ExceptionRecord1);
  221. break;
  222. }
  223.  
  224. //
  225. // If chain goes in wrong direction or loops, report an
  226. // invalid exception stack, otherwise go on to the next one.
  227. //
  228.  
  229. RegistrationPointer = RegistrationPointer->Next;
  230. }
  231.  
  232. //
  233. // Call vectored continue handlers.
  234. //
  235.  
  236. DispatchExit:
  237.  
  238. return Completion;
  239. }

RtlDispatchException 源代码

  1. EXCEPTION_DISPOSITION
  2. RtlpExecuteHandlerForException (
  3. IN PEXCEPTION_RECORD ExceptionRecord,
  4. IN PVOID EstablisherFrame,
  5. IN OUT PCONTEXT ContextRecord,
  6. IN OUT PVOID DispatcherContext,
  7. IN PEXCEPTION_ROUTINE ExceptionRoutine
  8. );

RtlpExecuteHandlerForException

然后了解异常链表在线程中的位置

  1. kd> dt _ETHREAD
  2. ntdll!_ETHREAD
  3. +0x000 Tcb : _KTHREAD
  4.  
  5. kd> dt _KTHREAD
  6. ntdll!_KTHREAD
  7. ......
  8. +0x074 Teb : Ptr32 Void
  9.  
  10. kd> dt _TEB
  11. ntdll!_TEB
  12. +0x000 NtTib : _NT_TIB
  13.  
  14. kd> dt _NT_TIB
  15. ntdll!_NT_TIB
  16. +0x000 ExceptionList : Ptr32 _EXCEPTION_REGISTRATION_RECORD //异常处理链表
  17. +0x004 StackBase : Ptr32 Void
  18. +0x008 StackLimit : Ptr32 Void
  19. +0x00c SubSystemTib : Ptr32 Void
  20. +0x010 FiberData : Ptr32 Void
  21. +0x010 Version : Uint4B
  22. +0x014 ArbitraryUserPointer : Ptr32 Void
  23. +0x018 Self : Ptr32 _NT_TIB

系统根据FS寄存器来寻找异常处理链表,在应用层,FS 寄存器“指向”当前执行线程的 _TEB 结构体。

在内核层,FS 寄存器“指向”另一个跟 CPU 相关的结构体:_KPCR,来看看它的结构,

  1. kd> dt _kpcr
  2. nt!_KPCR
  3. +0x000 NtTib : _NT_TIB
  4. +0x000 Used_ExceptionList : Ptr32 _EXCEPTION_REGISTRATION_RECORD
  5. ......省略

与 _TEB 一样,它的第一个域成员也是 _NT_TIB,只不过此时是 nt!_NT_TIB,而在应用层是 ntdll!_NT_TIB,但它们的结构是一样的。
这样,不论在应用层还是在内核层,系统都可以使用 FS:[0] 找到异常链表。

总结一下异常处理调用流程
硬件异常:
CPU 检测到异常 -> KiTrap?? -> KiDispatchException -> RtlDispatchException -> RtlpExecuteHandlerForException
软件异常:
RtlRaiseException -> RtlDispatchException -> RtlpExecuteHandlerForException

接下来看看RtlRaiseException 函数,大致流程就是:

首先调用 RtlDispatchException 分发异常,如果 RtlDispatchException 成功分发,有处理函数处理了这个异常,那么结束本函数。
 如果没有成功分发,那么调用 ZwRaiseException 再次触发该异常,这次传入的异常的 FirstChance 被置为 FALSE。

  1. DECLSPEC_NOINLINE
  2. VOID
  3. RtlRaiseException (
  4. IN PEXCEPTION_RECORD ExceptionRecord
  5. )
  6.  
  7. /*++
  8.  
  9. Routine Description:
  10.  
  11. This function raises a software exception by building a context record
  12. and calling the raise exception system service.
  13.  
  14. Arguments:
  15.  
  16. ExceptionRecord - Supplies a pointer to an exception record.
  17.  
  18. Return Value:
  19.  
  20. None.
  21.  
  22. --*/
  23.  
  24. {
  25.  
  26. CONTEXT ContextRecord;
  27. ULONG64 ControlPc;
  28. ULONG64 EstablisherFrame;
  29. PRUNTIME_FUNCTION FunctionEntry;
  30. PVOID HandlerData;
  31. ULONG64 ImageBase;
  32. NTSTATUS Status = STATUS_INVALID_DISPOSITION;
  33.  
  34. //
  35. // Capture the current context, unwind to the caller of this routine, set
  36. // the exception address, and call the appropriate exception dispatcher.
  37. //
  38.  
  39. RtlCaptureContext(&ContextRecord);
  40. ControlPc = ContextRecord.Rip;
  41. FunctionEntry = RtlLookupFunctionEntry(ControlPc, &ImageBase, NULL);
  42. if (FunctionEntry != NULL) {
  43. RtlVirtualUnwind(UNW_FLAG_NHANDLER,
  44. ImageBase,
  45. ControlPc,
  46. FunctionEntry,
  47. &ContextRecord,
  48. &HandlerData,
  49. &EstablisherFrame,
  50. NULL);
  51.  
  52. ExceptionRecord->ExceptionAddress = (PVOID)ContextRecord.Rip;
  53.  
  54. if (RtlDispatchException(ExceptionRecord, &ContextRecord) != FALSE) {
  55. return;
  56.  
  57. }
  58.  
  59. Status = ZwRaiseException(ExceptionRecord, &ContextRecord, FALSE);
  60. }
  61.  
  62. //
  63. // There should never be a return from either exception dispatch or the
  64. // system service unless there is a problem with the argument list itself.
  65. // Raise another exception specifying the status value returned.
  66. //
  67.  
  68. RtlRaiseStatus(Status);
  69. return;
  70. }

RtlRaiseException

到这里,系统提供的 SEH 机制,大致完毕,我们可以回顾一下:
1. 系统的SEH的实现较简单,代码量不大,而且 wrk 基本上有所有关键函数的实现代码。
2. 系统的SEH的功能过于简单,实际过程中很难直接使用。整个异常处理过程无非就是遍历异常链表,挨个调用异常注册信息的处理函数,
    如果其中有某个处理函数处理了该异常(返回值为 ExceptionContinueExecution),
    那么就从异常触发点(如果是断点异常,则要回退一个字节的指令(int 3 指令本身))重新执行。
    否则不管是整个链表中没有找到合适的处理函数(返回值为 ExceptionContinueSearch),
    或者遍历过程中出现问题(返回值为 ExceptionNestedException),系统都会简单粗暴的 BUGCHECK。

那么问题来了:
线程运行过程中会调用很多个函数,每个函数都有可能注册异常处理,
它们提供的异常处理函数既可能处理该函数自身触发的异常,又可能需要处理其子孙函数触发的异常。
前者还好说,自己出了问题,多少还有可能自己修复。
而后者就很头疼了,它无法了解所有其调用的子孙函数内部的实现,要想修复子孙函数触发的异常,太困难了。
而一旦没有正确处理,或者没人处理,系统就崩掉,这个后果太严重。
于是实际上现实程序设计中,基本上没有直接使用系统的SEH机制,而是使用编译器提供的增强版本。

下面就学习编译器提供的增强版本。

增强版中的结构体

  1. typedef struct _EXCEPTION_REGISTRATION PEXCEPTION_REGISTRATION;
  2. struct _EXCEPTION_REGISTRATION{
  3. PEXCEPTION_POINTERS xpointers;
  4. struct _EXCEPTION_REGISTRATION *prev;
  5. void (*handler)(PEXCEPTION_RECORD, PEXCEPTION_REGISTRATION, PCONTEXT, PEXCEPTION_RECORD);
  6. struct scopetable_entry *scopetable; //类型为 scopetable_entry 的数组
  7. int trylevel; //数组下标,用来索引 scopetable 中的数组成员。
  8. int _ebp; //包含该 _EXCEPTION_REGISTRATION 结构体的函数的栈帧指针。
  9. //对于没有 FPO 优化过的函数,一开头通常有个 push ebp 的操作,_ebp 的值就是被压入的 ebp 的值
  10. };

也就是说它沿用了系统SEH的注册信息结构,只是在域成员名称上做了些改动,把 Next 改名为 prev,把 Handler 改为 handler。

除此之外,在原始版本基础上增加了4个域成员(scopetable、trylevel、_ebp、xpointers),用来支持它的增强功能。

  1. scopetable_entry
  2. +0x000 previousTryLevel : Uint4B
  3. +0x004 lpfnFilter : Ptr32 int
  4. +0x008 lpfnHandler : Ptr32 int

按照原始版本的设计,每一对“触发异常-处理异常”都会有一个注册信息即 EXCEPTION_REGISTRATION_RECORD。
也就是说,如果按照原始的设计,每一个 __try/__except(__finally) 都应该对应一个 EXCEPTION_REGISTRATION。但是实际的 MSC(微软编译器,我用的VS2010)实现不是这样的。
真正的实现是:
每个使用 __try/__except(__finally) 的函数,不管其内部嵌套或反复使用多少 __try/__except(__finally),都只注册一遍,
即只将一个 EXCEPTION_REGISTRATION 挂入当前线程的异常链表中
(对于递归函数,每一次调用都会创建一个 EXCEPTION_REGISTRATION,并挂入线程的异常链表中,这是另外一回事)。

那如何处理函数内部出现的多个 __try/__except(__finally) 呢?
这多个 __except 代码块的功能可能大不相同,而注册信息 EXCEPTION_REGISTRATION 中只能提供一个处理函数 handler,怎么办?

MSC 的做法是,MSC 提供一个处理函数,即 EXCEPTION_REGISTRATION::handler 被设置为 MSC 的某个函数,而不是我们自己提供的 __except 代码块。
我们自己提供的多个 __except 块被存储在 EXCEPTION_REGISTRATION::scopetable 数组中。
我们看看上面的 scopetable_entry 定义,由于我没有找到它的定义代码,所以就贴了 windbg 中 dt 输出结果。
其中 scopetable_entry::lpfnHandler 就是程序猿提供的 __except 异常处理块代码。
而 lpfnFilter 就是 __except 的过滤块代码。对于 __finally 代码块,其 lpfnFilter 被置为 NULL,lpfnHandler 就是其包含的代码块。

下面,我们用一小段简单的伪代码来学习

  1. VOID SimpleSEH()
  2. {
  3. __try
  4. {
  5. }
  6. __except(ExceptionFilter_0(...))
  7. {
  8. ExceptCodeBlock_0;
  9. }
  10.  
  11. __try
  12. {
  13. __try
  14. {              //假设触发异常
  15. }
  16. __except(ExceptionFilter_1(...))
  17. {
  18. ExceptCodeBlock_1;
  19. }
  20. }
  21. __except(ExceptionFilter_2(...))
  22. {
  23. ExceptCodeBlock_2;
  24. }
  25. }

编译时,编译器会为 SimpleSeh 分配一个 EXCEPTION_REGISTRATION 和一个拥有3个成员的 scopetable 数组,并将 EXCEPTION_REGISTRATION::scopetable 指向该数组(请留意:EXCEPTION_REGISTRATION::scopetable 只是一个指针,不是数组)。然后按照 __try 关键字出现的顺序,将对应的__except/__finally 都存入该数组,步骤如下:

scopetable[0].lpfnFilter = ExceptionFilter_0;
  scopetable[0].lpfnHandler = ExceptCodeBlock_0;

scopetable[1].lpfnFilter = ExceptionFilter_1;
  scopetable[1].lpfnHandler = ExceptCodeBlock_1;

scopetable[2].lpfnFilter = ExceptionFilter_2;
  scopetable[2].lpfnHandler = ExceptCodeBlock_2;

我们假象当前开始执行 SimpleSEH 函数,在行16和17行之间触发了异常。
根据之前的流程:RtlRaiseException -> RtlDispatchException -> RtlpExecuteHandlerForException。
RtlpExecuteHandlerForException 会调用注册信息中的处理函数,即 EXCEPTION_REGISTRATION::handler。
该函数是由 MSC 提供的,内部会依次调用 scopetable 中的 lpfnHandler。
那咱们来模拟执行一下,在16和17行之前触发异常,那应该先从 scopetable[2] 的 ExceptionFilter_2 开始执行,
假设该函数返回 EXCEPTION_CONTINUE_SEARCH,那接下来应该是 scopetable[1];
假设 ExceptionFilter_1 也返回 EXCEPTION_CONTINUE_SEARCH;
那么接下来是不是就应该轮到 scopetable[0] 了?不是。
再看看上面的伪代码,行16和行17之间的代码并没处于第一个 __try/__except 的范围中,该异常轮不到 scopetable[0] 来处理。
那怎么办?
SimpleSEH 执行的过程中怎么知道到 scopetable[1] 就应该停止?

MSC 是通过 scopetable_entry::previousTryLevel 来解决这个问题的。上面数组的设置,完整的形式其实是这样:

  1. scopetable[].previousTryLevel = TRYLEVEL_NONE;
  2. scopetable[].lpfnFilter = ExceptionFilter_0;
  3. scopetable[].lpfnHandler = ExceptCodeBlock_0;
  4.  
  5. scopetable[].previousTryLevel = TRYLEVEL_NONE;
  6. scopetable[].lpfnFilter = ExceptionFilter_1;
  7. scopetable[].lpfnHandler = ExceptCodeBlock_1;
  8.  
  9. scopetable[].previousTryLevel = ;
  10. scopetable[].lpfnFilter = ExceptionFilter_2;
  11. scopetable[].lpfnHandler = ExceptCodeBlock_2;

scopetable_entry::previousTryLevel 包含的意思是“下一个该轮到数组下标为 previousTryLevel 的单元了”。

当 scopetable_entry::previousTryLevel 等于 TRYLEVEL_NONE(-1) 时,就会停止遍历 scopetable。

  1. TRYLEVEL_NONE equ -
  2. TRYLEVEL_INVALID equ -

咱再来模拟执行一遍,当14和15行之间触发异常时,首先遍历到 scopetable[2],处理完后,找到 scopetable[2].previousTryLevel,发现其值为1,那么遍历到 scopetable[1],处理完后,找到 scopetable[1].previousTryLevel,发现其值为 TRYLEVEL_NONE,于是停止遍历。
 好像挺圆满的。

再假设下,如果行4和行5之间触发了同样的异常,执行流程应该如何。
首先,执行 scopetable[2],然后在 scopetable[1],然后……(省略若干同上字)。
停!这次的异常是在第一个 __try/__except 中触发的,轮不到 scopetable[2] 来处理,怎么办?

这个时候就轮到 EXCEPTION_REGISTRATION::trylevel 出场了。EXCEPTION_REGISTRATION::trylevel 的作用就是标识从那个数组单元开始遍历。
与 scopetable_entry::previousTryLevel 不同,EXCEPTION_REGISTRATION::trylevel 是动态变化的,也就是说,这个值在 SimpleSeh 执行过程中是会经常改变的。
比如:
执行到行4和行5之间,该值就会被修改为0;
执行到第12行,该值被修改为1;
执行到14行,该值为2。
这样,当异常触发时候,MSC 就能正确的遍历 scopetable 了。

到目前位置,已经熟悉了增强版的概要流程。下面结合真实代码来分析。代码分为三块:SEH 创建代码、MSC 提供的 handler 函数,以及展开函数。

先把后面分析要用的宏和结构体列出来:

  1. #define EXCEPTION_NONCONTINUABLE 0x1 // Noncontinuable exception
  2. #define EXCEPTION_UNWINDING 0x2 // Unwind is in progress
  3. #define EXCEPTION_EXIT_UNWIND 0x4 // Exit unwind is in progress
  4. #define EXCEPTION_STACK_INVALID 0x8 // Stack out of limits or unaligned
  5. #define EXCEPTION_NESTED_CALL 0x10 // Nested exception handler call
  6. #define EXCEPTION_TARGET_UNWIND 0x20 // Target unwind in progress
  7. #define EXCEPTION_COLLIDED_UNWIND 0x40 // Collided exception handler call
  8.  
  9. #define EXCEPTION_UNWIND (EXCEPTION_UNWINDING | EXCEPTION_EXIT_UNWIND |
  10. EXCEPTION_TARGET_UNWIND | EXCEPTION_COLLIDED_UNWIND)
  11.  
  12. nt!_EXCEPTION_RECORD
  13. +0x000 ExceptionCode : Int4B
  14. +0x004 ExceptionFlags : Uint4B
  15. +0x008 ExceptionRecord : Ptr32 _EXCEPTION_RECORD
  16. +0x00c ExceptionAddress : Ptr32 Void
  17. +0x010 NumberParameters : Uint4B
  18. +] Uint4B
  19.  
  20. typedef enum _EXCEPTION_DISPOSITION {
  21. ExceptionContinueExecution,
  22. ExceptionContinueSearch,
  23. ExceptionNestedException,
  24. ExceptionCollidedUnwind
  25. } EXCEPTION_DISPOSITION;
  26.  
  27. // scopetable_entry::lpfnFilter 的返回值,也就是 __except 过滤块的返回值
  28. #define EXCEPTION_EXECUTE_HANDLER 1
  29. #define EXCEPTION_CONTINUE_SEARCH 0
  30. #define EXCEPTION_CONTINUE_EXECUTION -1

一、SEH 创建代码

  1. #include <ntifs.h>
  2. #include <devioctl.h>
  3.  
  4. VOID TestSeh();
  5. LONG Filter_0();
  6. LONG Filter_2();
  7.  
  8. NTSTATUS
  9. DriverEntry(IN PDRIVER_OBJECT pDriverObj, IN PUNICODE_STRING pRegistryString)
  10. {
  11. TestSeh();
  12. return STATUS_SUCCESS;
  13. }
  14.  
  15. VOID TestSeh()
  16. {
  17.  
  18. ULONG ulVal = ;
  19.  
  20. __try // 第一个 __try 域
  21. {
  22. ulVal = 0x11111111; // 最后一位为1表示“在 __try 代码块中”
  23. }
  24. __except(Filter_0())
  25. {
  26. ulVal = 0x11111110; // 最后一位为0表示“在 __except/__finally 代码块中”
  27. }
  28.  
  29. __try // 第二个 __try 域
  30. {
  31. ulVal = 0x22222222;
  32.  
  33. __try // 第三个 __try 域
  34. {
  35. ulVal = 0x33333333;
  36.  
  37. *((ULONG*)NULL) = ulVal; // 触发异常
  38. }
  39. __finally
  40. {
  41. ulVal = 0x33333330;
  42. }
  43. }
  44. __except(Filter_2())
  45. {
  46. ulVal = 0x22222220;
  47. }
  48.  
  49. return;
  50.  
  51. }
  52.  
  53. LONG Filter_0()
  54. {
  55. return EXCEPTION_EXECUTE_HANDLER;
  56. }
  57.  
  58. LONG Filter_2()
  59. {
  60. return EXCEPTION_EXECUTE_HANDLER;
  61.  
  62. }

将生成的文件用Ida反汇编查看,TestSeh() 函数如下

  1. . ; _DWORD __stdcall TestSeh()
  2. . _TestSeh@0 proc near ; CODE XREF: DriverEntry(x,x)+5p
  3. .
  4. . ulVal = dword ptr -1Ch
  5. . ms_exc = CPPEH_RECORD ptr -18h
  6. .
  7. . mov edi, edi
  8. . push ebp
  9. . mov ebp, esp
  10. . push 0FFFFFFFEh
  11. . push offset scopetable ; ExceptionRegister->scopetable
  12. .text:0001103C push offset __except_handler4 ; ExceptionRegistration-->handler 系统自己的
  13. . ; prev
  14. . push eax
  15. . add esp, 0FFFFFFF4h
  16. .text:0001104B push ebx
  17. .text:0001104C push esi
  18. .text:0001104D push edi
  19. .text:0001104E mov eax, ___security_cookie
  20. . xor [ebp+ms_exc.registration.ScopeTable], eax ; scopetable进行加密
  21. . xor eax, ebp ; security_cookie进行异或加密
  22. . push eax
  23. . lea eax, [ebp+ms_exc.registration]
  24. ., eax ; registration挂入线程异常链表
  25. . mov [ebp+ms_exc.old_esp], esp
  26. .
  27. . ; 进入第一个__try域,TryLevel=0
  28. . mov [ebp+ulVal], 11111111h
  29. .text:0001107A mov [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh ; 离开第一个__try域,TryLevel=TRYLEVEL_NONE (-2)
  30. . jmp short loc_1109A
  31. . ; ---------------------------------------------------------------------------
  32. .
  33. . $LN7: ; DATA XREF: .rdata:scopetableo
  34. . call _Filter_0@0 ; Exception filter 0 for function 11030
  35. .
  36. . $LN9:
  37. . retn
  38. . ; ---------------------------------------------------------------------------
  39. .
  40. . $LN8: ; DATA XREF: .rdata:scopetableo
  41. . mov esp, [ebp+ms_exc.old_esp] ; Exception handler 0 for function 11030
  42. .text:0001108C mov [ebp+ulVal], 11111110h
  43. . mov [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh
  44. .text:0001109A
  45. .text:0001109A loc_1109A: ; CODE XREF: TestSeh()+51j
  46. . ; 第二个__try域,TryLevel=1
  47. .text:000110A1 mov [ebp+ulVal], 22222222h
  48. . ; 第三个__try域,TryLevel=2
  49. .text:000110AF mov [ebp+ulVal], 33333333h
  50. .text:000110B6 mov eax, [ebp+ulVal]
  51. ., eax ; *((ULONG*)NULL) = ulVal; 触发异常
  52. . ; 离开第三个__try域,TryLevel=1
  53. .text:000110C5 call $LN15 ; Finally handler 2 for function 11030
  54. .text:000110CA ; ---------------------------------------------------------------------------
  55. .text:000110CA
  56. .text:000110CA loc_110CA: ; CODE XREF: TestSeh():$LN16j
  57. .text:000110CA jmp short $LN18
  58. .text:000110CC ; ---------------------------------------------------------------------------
  59. .text:000110CC
  60. .text:000110CC $LN15: ; CODE XREF: TestSeh()+95j
  61. .text:000110CC ; DATA XREF: .rdata:scopetableo
  62. .text:000110CC mov [ebp+ulVal], 33333330h ; Finally handler 2 for function 11030
  63. .text:000110D3
  64. .text:000110D3 $LN16:
  65. .text:000110D3 retn
  66. .text:000110D4 ; ---------------------------------------------------------------------------
  67. .text:000110D4
  68. .text:000110D4 $LN18: ; CODE XREF: TestSeh():loc_110CAj
  69. .text:000110D4 mov [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh
  70. .text:000110DB jmp short loc_110F4
  71. .text:000110DD ; ---------------------------------------------------------------------------
  72. .text:000110DD
  73. .text:000110DD $LN11: ; DATA XREF: .rdata:scopetableo
  74. .text:000110DD call _Filter_0@0 ; Exception filter 1 for function 11030
  75. .text:000110E2
  76. .text:000110E2 $LN13:
  77. .text:000110E2 retn
  78. .text:000110E3 ; ---------------------------------------------------------------------------
  79. .text:000110E3
  80. .text:000110E3 $LN12: ; DATA XREF: .rdata:scopetableo
  81. .text:000110E3 mov esp, [ebp+ms_exc.old_esp] ; Exception handler 1 for function 11030
  82. .text:000110E6 mov [ebp+ulVal], 22222220h ; 第二个__except处理
  83. .text:000110ED mov [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh
  84. .text:000110F4
  85. .text:000110F4 loc_110F4: ; CODE XREF: TestSeh()+ABj
  86. .text:000110F4 mov ecx, [ebp+ms_exc.registration.Next] ; 恢复旧的EXCEPTION_REGISTRATION,从链表中摘除ExceptionRegistration
  87. ., ecx
  88. .text:000110FE pop ecx
  89. .text:000110FF pop edi
  90. . pop esi
  91. . pop ebx
  92. . mov esp, ebp
  93. . pop ebp
  94. . retn
  95. . _TestSeh@0 endp

用WinDbg来看看 scopetable 的内容:

  1. kd> uf SEHx86!TestSeh
  2. SEHx86!TestSeh []:
  3. 91de8030 8bff mov edi,edi
  4. 91de8032 push ebp
  5. 91de8033 8bec mov ebp,esp
  6. 91de8035 6afe push 0FFFFFFFEh
  7. 91de8037 68c890de91 push offset SEHx86!__safe_se_handler_table+0x8 (91de90c8)
  8. 91de803c 683081de91 push offset SEHx86!_except_handler4 (91de8130)
  9. 91de8041 64a100000000 mov eax,dword ptr fs:[00000000h]
  10. 91de8047 push eax
  11. 91de8048 83c4f4 add esp,0FFFFFFF4h
  12. 91de804b push ebx
  13. 91de804c push esi
  14. 91de804d push edi
  15. 91de804e a100a0de91 mov eax,dword ptr [SEHx86!__security_cookie (91dea000)]
  16. 91de8053 3145f8 ],eax
  17. 91de8056 33c5 xor eax,ebp
  18. 91de8058 push eax
  19. 91de8059 8d45f0 lea eax,[ebp-10h]
  20. 91de805c 64a300000000 mov dword ptr fs:[00000000h],eax
  21. 91de8062 8965e8 mov dword ptr [ebp-18h],esp
  22. 91de8065 c745e400000000
  23. 91de806c c745fc00000000 ],
  24. 91de8073 c745e411111111 mov dword ptr [ebp-1Ch],11111111h
  25. 91de807a c745fcfeffffff ],0FFFFFFFEh
  26. 91de8081 eb17 jmp SEHx86!TestSeh+0x6a (91de809a)
  27.  
  28. SEHx86!TestSeh+0x6a []:
  29. 91de809a c745fc01000000 ],
  30. 91de80a1 c745e422222222 mov dword ptr [ebp-1Ch],22222222h
  31. 91de80a8 c745fc02000000 ],
  32. 91de80af c745e433333333 mov dword ptr [ebp-1Ch],33333333h
  33. 91de80b6 8b45e4 mov eax,dword ptr [ebp-1Ch]
  34. 91de80b9 a300000000 mov dword ptr ds:[00000000h],eax
  35. 91de80be c745fc01000000 ],
  36. 91de80c5 e802000000 call SEHx86!TestSeh+0x9c (91de80cc)
  37. 91de80ca eb08 jmp SEHx86!TestSeh+0xa4 (91de80d4)
  38.  
  39. SEHx86!TestSeh+0xa4 []:
  40. 91de80d4 c745fcfeffffff ],0FFFFFFFEh
  41. 91de80db eb17 jmp SEHx86!TestSeh+0xc4 (91de80f4)
  42.  
  43. SEHx86!TestSeh+0xc4 []:
  44. 91de80f4 8b4df0 mov ecx,dword ptr [ebp-10h]
  45. 91de80f7 6489],ecx
  46. 91de80fe pop ecx
  47. 91de80ff 5f pop edi
  48. 91de8100 5e pop esi
  49. 91de8101 5b pop ebx
  50. 91de8102 8be5 mov esp,ebp
  51. 91de8104 5d pop ebp
  52. 91de8105 c3 ret

uf Windbg

kd> dd 91de90c8
91de90c8 【fffffffe 00000000 ffffffd4 00000000】   >>>16个字节的空间
91de90d8 【fffffffe 91de8083 91de8089】【fffffffe   >>>3个socpetable_entry结构 
91de90e8 91de80dd 91de80e3】【00000001 00000000
91de90f8 91de80cc】 00000000 00000000 00000000
91de9108 00000000 00000000 00000000 00000000
91de9118 00000000 00000000 00000000 00000000
91de9128 00000000 00000000 00000000 00000000
91de9138 00000000 00000000 00000000 00000000

接下来再看系统自己的handler函数

  1. ; _EXCEPTION_DISPOSITION __cdecl _except_handler4(_EXCEPTION_RECORD *ExceptionRecord, _EXCEPTION_REGISTRATION_RECORD *EstablisherFrame, _CONTEXT *ContextRecord, void *DispatcherContext)
  2. . __except_handler4 proc near ; DATA XREF: TestSeh()+Co
  3. . ; .rdata:___safe_se_handler_tableo
  4. .
  5. . ExceptionPointers= _EXCEPTION_POINTERS ptr -14h
  6. . ScopeTableRecord= dword ptr -0Ch
  7. . Disposition = dword ptr -
  8. . Revalidate = byte ptr -
  9. . ExceptionRecord = dword ptr
  10. . EstablisherFrame= dword ptr 0Ch
  11. . ContextRecord = dword ptr 10h
  12. . DispatcherContext= dword ptr 14h
  13. .
  14. . mov edi, edi ; 这里的_EXCEPTION_REGISTRATION_RECORD是编译器增强版,不是wrk中的定义
  15. . push ebp
  16. . mov ebp, esp
  17. . sub esp, 14h
  18. . push ebx
  19. . mov ebx, [ebp+EstablisherFrame]
  20. .text:0001113C push esi
  21. .] ; scopetable
  22. . xor esi, ___security_cookie ; 解密scopetable
  23. . push edi
  24. . mov eax, [esi]
  25. . ; BOOLEAN 用来表示是否执行过任何 scopetable_entry::lpfnFilter
  26. . ; 函数的返回值,初始化为EXCEPTION_EXECUTE_HANDLER(0)
  27. . lea edi, [ebx+10h] ; ebxExceptionRigstration,+10h即为_ebp
  28. . cmp eax, 0FFFFFFFEh
  29. .text:0001115A jz short loc_11169
  30. .] ; 检验scopetable(1)
  31. .text:0001115F add ecx, edi
  32. . xor ecx, [eax+edi] ; cookie
  33. . call @__security_check_cookie@4 ; __security_check_cookie(x)
  34. .
  35. . loc_11169: ; CODE XREF: __except_handler4+2Aj
  36. . mov ecx, [esi+0Ch] ; 检验scopetable(2)
  37. .]
  38. .text:0001116F add ecx, edi
  39. . xor ecx, [eax+edi] ; cookie
  40. . call @__security_check_cookie@4 ; __security_check_cookie(x)
  41. . mov eax, [ebp+ExceptionRecord]
  42. .], 66h ; wrk中的定义EXCEPTION_UNWIND equ 00066H
  43. .text:0001117C ; ExceptionRecord->ExceptionFlags & EXCEPTION_UNWIND
  44. .text:0001117C ; 判断是异常处理过程还是展开过程
  45. . jnz $LN31 ; 展开
  46. . mov ecx, [ebp+ContextRecord] ; 异常处理过程
  47. . lea edx, [ebp+ExceptionPointers]
  48. .], edx ; ebx是第二个参数ExceptionRigstrationRecord
  49. .text:0001118C ; [ebx-4]就是前文提到过的xpointers
  50. .text:0001118F mov ebx, [ebx+0Ch] ; ebx = TryLevel
  51. . mov [ebp+ExceptionPointers.ExceptionRecord], eax ; 将参数拷贝到自己的函数栈
  52. . mov [ebp+ExceptionPointers.ContextRecord], ecx
  53. . cmp ebx, 0FFFFFFFEh
  54. .text:0001119B jz short loc_111FC
  55. .]
  56. .text:000111A0
  57. .text:000111A0 loc_111A0: ; CODE XREF: __except_handler4+A0j
  58. .] ; eax=ebx*3
  59. .+14h] ; 这里的eax*4加上上面的ebx*3,相当于是*12,
  60. .text:000111A3 ; 为了跳过trylevelscopetable_entry(大小为12个字节)
  61. .text:000111A3 ; 然后+14h,为了过上文提到过的10h的一个坑,
  62. .text:000111A3 ; ecx = scopetable[i].lpfnFilter
  63. .+10h] ; eax = &scopetable[i]
  64. .text:000111AB mov [ebp+ScopeTableRecord], eax ; ScopeTableRecord存放的就是当前异常
  65. .text:000111AB ; ScopeTableEntry
  66. .text:000111AE mov eax, [eax] ; eax = scopetable[i].previousTryLevel
  67. .text:000111B0 mov [ebp+ExceptionRecord], eax
  68. .text:000111B3 test ecx, ecx
  69. .text:000111B5 jz short loc_111CB ; ecx =0,即lpfnHandlerNULL,则跳转
  70. .text:000111B7 mov edx, edi ; edi = _ebp
  71. .text:000111B9 call @_EH4_CallFilterFunc@8 ; 在函数里面 call ecx,即调用lpfnFilter
  72. . ; 表示是否执行过 lpfnFilter
  73. .text:000111C2 test eax, eax ; 检验lpfnHandler函数的返回值
  74. .text:000111C4 jl short loc_11206 ; 如果是 EXCEPTION_CONTINUE_EXECUTION (-1) 就跳
  75. .text:000111C6 jg short loc_1120F ; 如果是 EXCEPTION_EXECUTE_HANDLER (1) 就跳
  76. .text:000111C8 mov eax, [ebp+ExceptionRecord] ; eax = scopetable[i].previousTryLevel
  77. .text:000111CB
  78. .text:000111CB loc_111CB: ; CODE XREF: __except_handler4+85j
  79. .text:000111CB mov ebx, eax
  80. .text:000111CD cmp eax, 0FFFFFFFEh ; cmp scopetable[i].previousTryLevel, TRYLEVEL_INVALID
  81. .text:000111D0 jnz short loc_111A0 ; 不为TRYLEVEL_INVALID(-2),跳转,寻找下一个
  82. .
  83. .text:000111D6 jz short loc_111FC ; 没有执行过 lpfnFilter,无需进行安全检查
  84. .text:000111D8
  85. .text:000111D8 loc_111D8: ; CODE XREF: __except_handler4+DDj
  86. .text:000111D8 ; __except_handler4+14Fj
  87. .text:000111D8 mov eax, [esi]
  88. .text:000111DA cmp eax, 0FFFFFFFEh ; 根据 scopetable 空间的第一个DWORD
  89. .text:000111DA ; 判断是否需要做进一步的安全检查
  90. .text:000111DD jz short loc_111EC
  91. .] ; 检验scopetable完整性(1)
  92. .text:000111E2 add ecx, edi
  93. .text:000111E4 xor ecx, [eax+edi] ; cookie
  94. .text:000111E7 call @__security_check_cookie@4 ; __security_check_cookie(x)
  95. .text:000111EC
  96. .text:000111EC loc_111EC: ; CODE XREF: __except_handler4+ADj
  97. .text:000111EC mov ecx, [esi+0Ch] ; 检验scopetable完整性(2)
  98. .]
  99. .text:000111F2 add ecx, edi
  100. .text:000111F4 xor ecx, [edx+edi] ; cookie
  101. .text:000111F7 call @__security_check_cookie@4 ; __security_check_cookie(x)
  102. .text:000111FC
  103. .text:000111FC loc_111FC: ; CODE XREF: __except_handler4+6Bj
  104. .text:000111FC ; __except_handler4+A6j ...
  105. .text:000111FC mov eax, [ebp+Disposition] ; 函数返回EXCEPTION_EXCUTE_HANDLER (1)
  106. .text:000111FF pop edi
  107. . pop esi
  108. . pop ebx
  109. . mov esp, ebp
  110. . pop ebp
  111. . retn
  112. . ; ---------------------------------------------------------------------------
  113. .
  114. . loc_11206: ; CODE XREF: __except_handler4+94j
  115. . ; 函数返回EXECEPTION_CONTINUE_SEARCH (0)
  116. .text:0001120D jmp short loc_111D8
  117. .text:0001120F ; ---------------------------------------------------------------------------
  118. .text:0001120F
  119. .text:0001120F loc_1120F: ; CODE XREF: __except_handler4+96j
  120. .text:0001120F mov ecx, [ebp+EstablisherFrame] ; 全局展开操作
  121. . call @_EH4_GlobalUnwind@4 ; _EH4_GlobalUnwind(x)
  122. . mov eax, [ebp+EstablisherFrame]
  123. .text:0001121A cmp [eax+0Ch], ebx
  124. .text:0001121D jz short loc_11231
  125. .text:0001121F push offset ___security_cookie
  126. . push edi
  127. . mov edx, ebx
  128. . mov ecx, eax
  129. . call @_EH4_LocalUnwind@16 ; _EH4_LocalUnwind(x,x,x,x)
  130. .text:0001122E mov eax, [ebp+EstablisherFrame]
  131. .
  132. . loc_11231: ; CODE XREF: __except_handler4+EDj
  133. . mov ecx, [ebp+ExceptionRecord]
  134. . mov [eax+0Ch], ecx
  135. . mov eax, [esi]
  136. . cmp eax, 0FFFFFFFEh
  137. .text:0001123C jz short loc_1124B
  138. .]
  139. . add ecx, edi
  140. . xor ecx, [eax+edi] ; cookie
  141. . call @__security_check_cookie@4 ; __security_check_cookie(x)
  142. .text:0001124B
  143. .text:0001124B loc_1124B: ; CODE XREF: __except_handler4+10Cj
  144. .text:0001124B mov ecx, [esi+0Ch]
  145. .]
  146. . add ecx, edi
  147. . xor ecx, [edx+edi] ; cookie
  148. . call @__security_check_cookie@4 ; __security_check_cookie(x)
  149. .text:0001125B mov eax, [ebp+ScopeTableRecord]
  150. .]
  151. . mov edx, edi
  152. . call @_EH4_TransferToHandler@8 ; _EH4_TransferToHandler(x,x)
  153. .
  154. . $LN31: ; CODE XREF: __except_handler4+50j
  155. . mov edx, 0FFFFFFFEh
  156. .text:0001126D cmp [ebx+0Ch], edx
  157. . jz short loc_111FC
  158. . push offset ___security_cookie
  159. . push edi
  160. . mov ecx, ebx
  161. .text:0001127A call @_EH4_LocalUnwind@16 ; _EH4_LocalUnwind(x,x,x,x)
  162. .text:0001127F jmp loc_111D8
  163. .text:0001127F __except_handler4 endp

在函数的最后有展开操作,先学习一下展开的概念

我们假设一系列使用 SEH 的函数调用流程: 
  func1 -> func2 -> func3,然后在 func3 执行的过程中触发了异常。

看看分发异常流程 RtlRaiseException -> RtlDispatchException -> RtlpExecuteHandlerForException
  RtlDispatchException 会遍历异常链表,对每个 EXCEPTION_REGISTRATION 都调用 RtlpExecuteHandlerForException。
  RtlpExecuteHandlerForException 会调用 EXCEPTION_REGISTRATION::handler,也就是 PassThrough!_except_handler4。如咱们上面分析,该函数内部遍历 EXCEPTION_REGISTRATION::scopetable,如果遇到有 scopetable_entry::lpfnFilter 返回 EXCEPTION_EXECUTE_HANDLER,那么 scopetable_entry::lpfnHandler 就会被调用,来处理该异常。
  因为 lpfnHandler 不会返回到 PassThrough!_except_handler4,于是执行完 lpfnHandler 后,就会从 lpfnHandler 之后的代码继续执行下去。也就是说,假设 func3 中触发了一个异常,该异常被 func1 中的 __except 处理块处理了,那 __except 处理块执行完毕后,就从其后的指令继续执行下去,即异常处理完毕后,接着执行的就是 func1 的代码。不会再回到 func2 或者 func3,这样就有个问题,func2 和 func3 中占用的资源怎么办?这些资源比如申请的内存是不会自动释放的,岂不是会有资源泄漏问题?

这就需要用到“展开”了。
  所谓“展开”就是进行清理,这里的清理主要包含动态分配的资源的清理,栈空间是由 func1 的“mov esp,ebp” 这类操作顺手清理

那这个展开工作由谁来完成呢?由 func1 来完成肯定不合适,毕竟 func2 和 func3 有没有申请资源、申请了哪些资源,func1 无从得知。于是这个展开工作还得要交给 func2 和 func3 自己来完成。

展开分为两种:“全局展开”和“局部展开”。
  全局展开是指针对异常链表中的某一段,局部展开针对指定 EXCEPTION_REGISTRATION。用上面的例子来讲,局部展开就是针对 func3 或 func2 (某一个函数)内部进行清理,全局展开就是 func2 和 func3 的局部清理的总和。再归纳一下,局部展开是指具体某一函数内部的清理,而全局展开是指,从异常触发点(func3)到异常处理点(func1)之间所有函数(包含异常触发点 func3)的局部清理的总和。

下面来看看wrk中RtlUwind的源代码:

  1. VOID
  2. RtlUnwind (
  3. IN PVOID TargetFrame OPTIONAL,
  4. IN PVOID TargetIp OPTIONAL,
  5. IN PEXCEPTION_RECORD ExceptionRecord OPTIONAL,
  6. IN PVOID ReturnValue
  7. )
  8.  
  9. /*++
  10.  
  11. Routine Description:
  12.  
  13. This function initiates an unwind of procedure call frames. The machine
  14. state at the time of the call to unwind is captured in a context record
  15. and the unwinding flag is set in the exception flags of the exception
  16. record. If the TargetFrame parameter is not specified, then the exit unwind
  17. flag is also set in the exception flags of the exception record. A backward
  18. walk through the procedure call frames is then performed to find the target
  19. of the unwind operation.
  20.  
  21. N.B. The captured context passed to unwinding handlers will not be
  22. a completely accurate context set for the 386. This is because
  23. there isn't a standard stack frame in which registers are stored.
  24.  
  25. Only the integer registers are affected. The segment and
  26. control registers (ebp, esp) will have correct values for
  27. the flat 32 bit environment.
  28.  
  29. N.B. If you change the number of arguments, make sure you change the
  30. adjustment of ESP after the call to RtlpCaptureContext (for
  31. STDCALL calling convention)
  32.  
  33. Arguments:
  34.  
  35. TargetFrame - Supplies an optional pointer to the call frame that is the
  36. target of the unwind. If this parameter is not specified, then an exit
  37. unwind is performed.
  38.  
  39. TargetIp - Supplies an optional instruction address that specifies the
  40. continuation address of the unwind. This address is ignored if the
  41. target frame parameter is not specified.
  42.  
  43. ExceptionRecord - Supplies an optional pointer to an exception record.
  44.  
  45. ReturnValue - Supplies a value that is to be placed in the integer
  46. function return register just before continuing execution.
  47.  
  48. Return Value:
  49.  
  50. None.
  51.  
  52. --*/
  53.  
  54. {
  55. PCONTEXT ContextRecord;
  56. CONTEXT ContextRecord1;
  57. DISPATCHER_CONTEXT DispatcherContext;
  58. EXCEPTION_DISPOSITION Disposition;
  59. PEXCEPTION_REGISTRATION_RECORD RegistrationPointer;
  60. PEXCEPTION_REGISTRATION_RECORD PriorPointer;
  61. ULONG HighAddress;
  62. ULONG HighLimit;
  63. ULONG LowLimit;
  64. EXCEPTION_RECORD ExceptionRecord1;
  65. EXCEPTION_RECORD ExceptionRecord2;
  66.  
  67. //
  68. // Get current stack limits.
  69. //
  70.  
  71. RtlpGetStackLimits(&LowLimit, &HighLimit);
  72.  
  73. //
  74. // If an exception record is not specified, then build a local exception
  75. // record for use in calling exception handlers during the unwind operation.
  76. //
  77.  
  78. if (ARGUMENT_PRESENT(ExceptionRecord) == FALSE) {
  79. ExceptionRecord = &ExceptionRecord1;
  80. ExceptionRecord1.ExceptionCode = STATUS_UNWIND;
  81. ExceptionRecord1.ExceptionFlags = ;
  82. ExceptionRecord1.ExceptionRecord = NULL;
  83. ExceptionRecord1.ExceptionAddress = _ReturnAddress(); //返回地址
  84. ExceptionRecord1.NumberParameters = ;
  85. }
  86.  
  87. //
  88. // If the target frame of the unwind is specified, then set EXCEPTION_UNWINDING
  89. // flag in the exception flags. Otherwise set both EXCEPTION_EXIT_UNWIND and
  90. // EXCEPTION_UNWINDING flags in the exception flags.
  91. //
  92.  
  93. if (ARGUMENT_PRESENT(TargetFrame) == TRUE) {
  94. ExceptionRecord->ExceptionFlags |= EXCEPTION_UNWINDING;
  95. } else {
  96. ExceptionRecord->ExceptionFlags |= (EXCEPTION_UNWINDING |
  97. EXCEPTION_EXIT_UNWIND);
  98. }
  99.  
  100. //
  101. // Capture the context.
  102. //
  103.  
  104. ContextRecord = &ContextRecord1;
  105. ContextRecord1.ContextFlags = CONTEXT_INTEGER | CONTEXT_CONTROL | CONTEXT_SEGMENTS;
  106. RtlpCaptureContext(ContextRecord);
  107.  
  108. //
  109. // Adjust captured context to pop our arguments off the stack
  110. //
  111. ContextRecord->Esp += sizeof(TargetFrame) +
  112. sizeof(TargetIp) +
  113. sizeof(ExceptionRecord) +
  114. sizeof(ReturnValue);
  115. ContextRecord->Eax = (ULONG)ReturnValue;
  116.  
  117. //
  118. // Scan backward through the call frame hierarchy, calling exception
  119. // handlers as they are encountered, until the target frame of the unwind
  120. // is reached.
  121. //
  122.  
  123. RegistrationPointer = RtlpGetRegistrationHead();//异常链表头
  124. while (RegistrationPointer != EXCEPTION_CHAIN_END) {
  125.  
  126. //
  127. // If this is the target of the unwind, then continue execution
  128. // by calling the continue system service.
  129. //
  130. //说明展开完毕
  131. if ((ULONG)RegistrationPointer == (ULONG)TargetFrame) {
  132. ZwContinue(ContextRecord, FALSE);
  133.  
  134. //
  135. // If the target frame is lower in the stack than the current frame,
  136. // then raise STATUS_INVALID_UNWIND exception.
  137. //
  138.  
  139. } else if ( (ARGUMENT_PRESENT(TargetFrame) == TRUE) &&
  140. ((ULONG)TargetFrame < (ULONG)RegistrationPointer) ) {
  141. //超出了异常链表的查找范围
  142. ExceptionRecord2.ExceptionCode = STATUS_INVALID_UNWIND_TARGET;
  143. ExceptionRecord2.ExceptionFlags = EXCEPTION_NONCONTINUABLE;
  144. ExceptionRecord2.ExceptionRecord = ExceptionRecord;
  145. ExceptionRecord2.NumberParameters = ;
  146. RtlRaiseException(&ExceptionRecord2);
  147. }
  148.  
  149. //
  150. // If the call frame is not within the specified stack limits or the
  151. // call frame is unaligned, then raise the exception STATUS_BAD_STACK.
  152. // Else restore the state from the specified frame to the context
  153. // record.
  154. //
  155.  
  156. HighAddress = (ULONG)RegistrationPointer +
  157. sizeof(EXCEPTION_REGISTRATION_RECORD);
  158. //低于线程栈底或者高于线程栈顶,进行错误处理
  159. if ( ((ULONG)RegistrationPointer < LowLimit) ||
  160. (HighAddress > HighLimit) ||
  161. (((ULONG)RegistrationPointer & )
  162. ) {
  163.  
  164. //
  165. // Allow for the possibility that the problem occured on the
  166. // DPC stack.
  167. //
  168.  
  169. ULONG TestAddress = (ULONG)RegistrationPointer;
  170. //& 0x3 检查是否4字节对齐 ,IRQL级别,如果当前正在执行dpc操作,restart
  171. ) &&
  172. KeGetCurrentIrql() >= DISPATCH_LEVEL) {
  173.  
  174. PKPRCB Prcb = KeGetCurrentPrcb();
  175. ULONG DpcStack = (ULONG)Prcb->DpcStack;
  176.  
  177. if ((Prcb->DpcRoutineActive) &&
  178. (HighAddress <= DpcStack) &&
  179. (TestAddress >= DpcStack - KERNEL_STACK_SIZE)) {
  180.  
  181. //
  182. // This error occured on the DPC stack, switch
  183. // stack limits to the DPC stack and restart
  184. // the loop.
  185. //
  186.  
  187. HighLimit = DpcStack;
  188. LowLimit = DpcStack - KERNEL_STACK_SIZE;
  189. continue;
  190. }
  191. }
  192.  
  193. ExceptionRecord2.ExceptionCode = STATUS_BAD_STACK;
  194. ExceptionRecord2.ExceptionFlags = EXCEPTION_NONCONTINUABLE;
  195. ExceptionRecord2.ExceptionRecord = ExceptionRecord;
  196. ExceptionRecord2.NumberParameters = ;
  197. RtlRaiseException(&ExceptionRecord2);
  198. } else { //一般情况的展开
  199.  
  200. //
  201. // The handler must be executed by calling another routine
  202. // that is written in assembler. This is required because
  203. // up level addressing of the handler information is required
  204. // when a collided unwind is encountered.
  205. //
  206. //在内部调用Handler处理函数
  207. Disposition = RtlpExecuteHandlerForUnwind(
  208. ExceptionRecord,
  209. (PVOID)RegistrationPointer,
  210. ContextRecord,
  211. (PVOID)&DispatcherContext,
  212. RegistrationPointer->Handler);
  213.  
  214. //
  215. // Case on the handler disposition.
  216. //
  217. //检查Handler的返回值
  218. switch (Disposition) {
  219.  
  220. //
  221. // The disposition is to continue the search. Get next
  222. // frame address and continue the search.
  223. //
  224.  
  225. case ExceptionContinueSearch :
  226. break;
  227.  
  228. //
  229. // The disposition is colided unwind. Maximize the target
  230. // of the unwind and change the context record pointer.
  231. //
  232.  
  233. case ExceptionCollidedUnwind :
  234.  
  235. //
  236. // Pick up the registration pointer that was active at
  237. // the time of the unwind, and simply continue.
  238. //
  239.  
  240. RegistrationPointer = DispatcherContext.RegistrationPointer;
  241. break;
  242.  
  243. //
  244. // All other disposition values are invalid. Raise
  245. // invalid disposition exception.
  246. //
  247.  
  248. default :
  249. ExceptionRecord2.ExceptionCode = STATUS_INVALID_DISPOSITION;
  250. ExceptionRecord2.ExceptionFlags = EXCEPTION_NONCONTINUABLE;
  251. ExceptionRecord2.ExceptionRecord = ExceptionRecord;
  252. ExceptionRecord2.NumberParameters = ;
  253. RtlRaiseException(&ExceptionRecord2);
  254. break;
  255. }
  256.  
  257. //
  258. // Step to next registration record
  259. //
  260.  
  261. PriorPointer = RegistrationPointer;
  262. RegistrationPointer = RegistrationPointer->Next;
  263.  
  264. //
  265. // Unlink the unwind handler, since it's been called.
  266. //
  267.  
  268. RtlpUnlinkHandler(PriorPointer);
  269.  
  270. //
  271. // If chain goes in wrong direction or loops, raise an
  272. // exception.
  273. //
  274.  
  275. }
  276. }
  277.  
  278. if (TargetFrame == EXCEPTION_CHAIN_END) {
  279.  
  280. //
  281. // Caller simply wants to unwind all exception records.
  282. // This differs from an exit_unwind in that no "exit" is desired.
  283. // Do a normal continue, since we've effectively found the
  284. // "target" the caller wanted.
  285. //
  286.  
  287. ZwContinue(ContextRecord, FALSE);
  288.  
  289. } else {
  290.  
  291. //
  292. // Either (1) a real exit unwind was performed, or (2) the
  293. // specified TargetFrame is not present in the exception handler
  294. // list. In either case, give debugger and subsystem a chance
  295. // to see the unwind.
  296. //
  297.  
  298. ZwRaiseException(ExceptionRecord, ContextRecord, FALSE);
  299.  
  300. }
  301. return;
  302. }

wrk RtlUnwind

代码不长,主要功能也不复杂:从异常链表头开始遍历,一直遍历到指定 EXCEPTION_REGISTRATION_RECORD,对每个遍历到的 EXCEPTION_REGISTRATION_RECORD,执行 RtlpExecuteHandlerForUnwind 进行局部展开。

汇编代码如下,就不写注释了,直接看wrk源码更清晰

  1. .text:00475C9B ; int __stdcall RtlUnwind(int, int, PEXCEPTION_RECORD ExceptionRecord, int)
  2. .text:00475C9B public _RtlUnwind@16
  3. .text:00475C9B _RtlUnwind@16 proc near ; CODE XREF: __global_unwind2+13p
  4. .text:00475C9B ; _EH4_GlobalUnwind(x)+10p
  5. .text:00475C9B
  6. .text:00475C9B var_384= dword ptr -384h
  7. .text:00475C9B var_380= dword ptr -380h
  8. .text:00475C9B var_37C= dword ptr -37Ch
  9. .text:00475C9B var_378= EXCEPTION_RECORD ptr -378h
  10. .text:00475C9B var_328= dword ptr -328h
  11. .text:00475C9B var_324= dword ptr -324h
  12. .text:00475C9B var_320= dword ptr -320h
  13. .text:00475C9B var_31C= dword ptr -31Ch
  14. .text:00475C9B var_318= dword ptr -318h
  15. .text:00475C9B Context= CONTEXT ptr -2D8h
  16. .
  17. .
  18. .text:00475C9B ExceptionRecord= dword ptr 10h
  19. .text:00475C9B arg_C= dword ptr 14h
  20. .text:00475C9B
  21. .text:00475C9B 8B FF mov edi, edi
  22. . push ebp
  23. .text:00475C9E 8B EC mov ebp, esp
  24. . E4 F8 and esp, 0FFFFFFF8h
  25. . EC sub esp, 384h
  26. . 2A mov eax, ___security_cookie
  27. . C4 xor eax, esp
  28. . +mov [esp+384h+var_4], eax
  29. . push ebx
  30. . push esi
  31. . push edi
  32. . mov edi, [ebp+ExceptionRecord]
  33. . lea eax, [esp+390h+var_380]
  34. . push eax
  35. . lea esi, [esp+394h+var_37C]
  36. . 3D call _RtlpGetStackLimits@8 ; RtlpGetStackLimits(x,x)
  37. . C0 test al, al
  38. . 0A jnz short loc_475CD9
  39. . C0 push 0C0000028h ; Status
  40. .text:00475CD4 E8 9B D0 FB FF call _RtlRaiseStatus@4 ; RtlRaiseStatus(x)
  41. .text:00475CD9 ; ---------------------------------------------------------------------------
  42. .text:00475CD9
  43. .text:00475CD9 loc_475CD9: ; CODE XREF: RtlUnwind(x,x,x,x)+32j
  44. . F6 xor esi, esi
  45. .text:00475CDB 3B FE cmp edi, esi
  46. . 1F jnz short loc_475CFE
  47. . ]
  48. . lea edi, [esp+390h+var_328]
  49. . +mov [esp+390h+var_328], 0C0000027h
  50. . 6C mov [esp+390h+var_324], esi
  51. . mov [esp+390h+var_320], esi
  52. . mov [esp+390h+var_31C], eax
  53. . mov [esp+390h+var_318], esi
  54. .text:00475CFE
  55. .text:00475CFE loc_475CFE: ; CODE XREF: RtlUnwind(x,x,x,x)+42j
  56. . cmp [ebp+arg_0], esi
  57. . jz short loc_475D09
  58. . 4F ],
  59. . jmp short loc_475D0D
  60. .text:00475D09 ; ---------------------------------------------------------------------------
  61. .text:00475D09
  62. .text:00475D09 loc_475D09: ; CODE XREF: RtlUnwind(x,x,x,x)+66j
  63. . 4F ],
  64. .text:00475D0D
  65. .text:00475D0D loc_475D0D: ; CODE XREF: RtlUnwind(x,x,x,x)+6Cj
  66. . B8 +lea eax, [esp+390h+Context]
  67. . push eax
  68. . BC +mov [esp+394h+Context.ContextFlags], 10007h
  69. . FE FF call _RtlpCaptureContext@4 ; RtlpCaptureContext(x)
  70. . mov eax, [ebp+arg_C]
  71. . 7C +add [esp+390h+Context._Esp], 10h
  72. . +mov [esp+390h+Context._Eax], eax
  73. . FE FF call _RtlpGetRegistrationHead@0 ; RtlpGetRegistrationHead()
  74. .text:00475D3C 8B D8 mov ebx, eax
  75. . FB FF cmp ebx, 0FFFFFFFFh
  76. . DC jz loc_475E23
  77. . F6 xor esi, esi
  78. . inc esi
  79. .text:00475D4A
  80. .text:00475D4A loc_475D4A: ; CODE XREF: RtlUnwind(x,x,x,x)+180j
  81. . cmp ebx, [ebp+arg_0]
  82. . jnz short loc_475D60
  83. . ; TestAlert
  84. . BC +lea eax, [esp+394h+Context]
  85. . push eax ; Context
  86. .text:00475D59 E8 2A D5 FB FF call _ZwContinue@8 ; ZwContinue(x,x)
  87. .text:00475D5E EB 2A jmp short loc_475D8A
  88. .text:00475D60 ; ---------------------------------------------------------------------------
  89. .text:00475D60
  90. .text:00475D60 loc_475D60: ; CODE XREF: RtlUnwind(x,x,x,x)+B2j
  91. . 7D
  92. . jz short loc_475D8A
  93. . 5D cmp [ebp+arg_0], ebx
  94. . 1F jnb short loc_475D8A
  95. .
  96. . lea eax, [esp+390h+var_378]
  97. . push eax ; ExceptionRecord
  98. . 1C +mov [esp+394h+var_378.ExceptionCode], 0C0000029h
  99. . mov [esp+394h+var_378.ExceptionFlags], esi
  100. . 7C mov [esp+394h+var_378.ExceptionRecord], edi
  101. . CF FB FF call _RtlRaiseException@4 ; RtlRaiseException(x)
  102. .text:00475D8A ; ---------------------------------------------------------------------------
  103. .text:00475D8A
  104. .text:00475D8A loc_475D8A: ; CODE XREF: RtlUnwind(x,x,x,x)+C3j
  105. .text:00475D8A ; RtlUnwind(x,x,x,x)+C9j
  106. .text:00475D8A ; RtlUnwind(x,x,x,x)+CEj
  107. . cmp ebx, [esp+390h+var_37C]
  108. . jb short loc_475DF9
  109. . ]
  110. . cmp eax, [esp+390h+var_380]
  111. . ja short loc_475DF9
  112. .
  113. . 5B jnz short loc_475DF9
  114. . ]
  115. .text:00475DA1 E8 ED A4 FF FF call _RtlIsValidHandler@8 ; RtlIsValidHandler(x,x)
  116. . C0 test al, al
  117. . 4F jz short loc_475DF9
  118. . ]
  119. . lea eax, [esp+394h+var_384]
  120. . push eax
  121. . C0 +lea eax, [esp+398h+Context]
  122. . push eax
  123. . push ebx
  124. . push edi
  125. .text:00475DBC E8 A7 FF FD FF call _RtlpExecuteHandlerForUnwind@20 ; RtlpExecuteHandlerForUnwind(x,x,x,x,x)
  126. . dec eax
  127. . jz short loc_475DED
  128. . dec eax
  129. . dec eax
  130. . jz short loc_475DE9
  131. .
  132. . lea eax, [esp+390h+var_378]
  133. . push eax ; ExceptionRecord
  134. . 1C +mov [esp+394h+var_378.ExceptionCode], 0C0000026h
  135. . mov [esp+394h+var_378.ExceptionFlags], esi
  136. . 7C mov [esp+394h+var_378.ExceptionRecord], edi
  137. . CF FB FF call _RtlRaiseException@4 ; RtlRaiseException(x)
  138. .text:00475DE2 ; ---------------------------------------------------------------------------
  139. .text:00475DE7 EB db 0EBh ;
  140. . db
  141. .text:00475DE9 ; ---------------------------------------------------------------------------
  142. .text:00475DE9
  143. .text:00475DE9 loc_475DE9: ; CODE XREF: RtlUnwind(x,x,x,x)+12Bj
  144. . 0C mov ebx, [esp+390h+var_384]
  145. .text:00475DED
  146. .text:00475DED loc_475DED: ; CODE XREF: RtlUnwind(x,x,x,x)+127j
  147. .text:00475DED 8B C3 mov eax, ebx
  148. .text:00475DEF 8B 1B mov ebx, [ebx]
  149. . push eax
  150. . FE FF call _RtlpUnlinkHandler@4 ; RtlpUnlinkHandler(x)
  151. .text:00475DF7 EB 1F jmp short loc_475E18
  152. .text:00475DF9 ; ---------------------------------------------------------------------------
  153. .text:00475DF9
  154. .text:00475DF9 loc_475DF9: ; CODE XREF: RtlUnwind(x,x,x,x)+F3j
  155. .text:00475DF9 ; RtlUnwind(x,x,x,x)+FCj
  156. .text:00475DF9 ; RtlUnwind(x,x,x,x)+101j
  157. .text:00475DF9 ; RtlUnwind(x,x,x,x)+10Dj
  158. .
  159. . lea eax, [esp+390h+var_378]
  160. . push eax ; ExceptionRecord
  161. . 1C +mov [esp+394h+var_378.ExceptionCode], 0C0000028h
  162. . mov [esp+394h+var_378.ExceptionFlags], esi
  163. . 7C mov [esp+394h+var_378.ExceptionRecord], edi
  164. . CF FB FF call _RtlRaiseException@4 ; RtlRaiseException(x)
  165. .text:00475E18 ; ---------------------------------------------------------------------------
  166. .text:00475E18
  167. .text:00475E18 loc_475E18: ; CODE XREF: RtlUnwind(x,x,x,x)+15Cj
  168. . FB FF cmp ebx, 0FFFFFFFFh
  169. . FF FF FF jnz loc_475D4A
  170. . F6 xor esi, esi
  171. .text:00475E23
  172. .text:00475E23 loc_475E23: ; CODE XREF: RtlUnwind(x,x,x,x)+A6j
  173. . 7D FF cmp [ebp+arg_0], 0FFFFFFFFh
  174. . push esi ; SearchFrames
  175. . BC +lea eax, [esp+394h+Context]
  176. . push eax ; Context
  177. . jnz short loc_475E39
  178. . D4 FB FF call _ZwContinue@8 ; ZwContinue(x,x)
  179. . jmp short loc_475E3F
  180. .text:00475E39 ; ---------------------------------------------------------------------------
  181. .text:00475E39
  182. .text:00475E39 loc_475E39: ; CODE XREF: RtlUnwind(x,x,x,x)+195j
  183. . push edi ; ExceptionRecord
  184. .text:00475E3A E8 C5 E4 FB FF call _ZwRaiseException@12 ; ZwRaiseException(x,x,x)
  185. .text:00475E3F
  186. .text:00475E3F loc_475E3F: ; CODE XREF: RtlUnwind(x,x,x,x)+19Cj
  187. . 8C +mov ecx, [esp+390h+var_4]
  188. .text:00475E46 5F pop edi
  189. .text:00475E47 5E pop esi
  190. .text:00475E48 5B pop ebx
  191. . CC xor ecx, esp
  192. . FE FF call @__security_check_cookie@4 ; __security_check_cookie(x)
  193. .text:00475E50 8B E5 mov esp, ebp
  194. .text:00475E52 5D pop ebp
  195. . retn 10h
  196. .text:00475E53 _RtlUnwind@16 endp

asm RtlUnwind

再看_except_handler4中的局部展开

  1. .text:000113C8 ; int __fastcall _EH4_LocalUnwind(_EXCEPTION_REGISTRATION_RECORD *EstablisherFrame, int TryLevel, int FrameEBP, int *CookiePointer)
  2. .text:000113C8 @_EH4_LocalUnwind@16 proc near ; CODE XREF: __except_handler4+F9p
  3. .text:000113C8 ; __except_handler4+14Ap
  4. .text:000113C8
  5. .
  6. .
  7. .text:000113C8
  8. .text:000113C8 push ebp
  9. .+FrameEBP] ; 切换ebp准备局部展开
  10. .text:000113CD push edx ; TryLevel
  11. .text:000113CE push ecx ; EstablisherFrame
  12. .text:000113CF push [esp+0Ch+CookiePointer] ; CookiePointer
  13. .text:000113D3 call __local_unwind4
  14. .text:000113D8 add esp, 0Ch
  15. .text:000113DB pop ebp
  16. .
  17. .text:000113DC @_EH4_LocalUnwind@16 endp
  1. text:0001128C ; int __cdecl _local_unwind4(int *CookiePointer, _EXCEPTION_REGISTRATION_RECORD *EstablisherFrame, int TryLevel)
  2. .text:0001128C __local_unwind4 proc near ; CODE XREF: _unwind_handler4+2Dp
  3. .text:0001128C ; _seh_longjmp_unwind4(x)+10p ...
  4. .text:0001128C
  5. .text:0001128C var_20 = dword ptr -20h
  6. .
  7. .
  8. .text:0001128C TryLevel = dword ptr 0Ch
  9. .text:0001128C
  10. .text:0001128C push ebx
  11. .text:0001128D push esi
  12. .text:0001128E push edi
  13. .text:0001128F mov edx, [esp+0Ch+CookiePointer]
  14. . mov eax, [esp+0Ch+EstablisherFrame]
  15. . mov ecx, [esp+0Ch+TryLevel]
  16. .text:0001129B push ebp
  17. .text:0001129C push edx
  18. .text:0001129D push eax
  19. .text:0001129E push ecx
  20. .text:0001129F push ecx
  21. .text:000112A0 push offset _unwind_handler4 ; Handler,局部展开发生异常时调用
  22. .
  23. .text:000112AC mov eax, ___security_cookie
  24. .text:000112B1 xor eax, esp
  25. .text:000112B3 mov [esp+28h+var_20], eax
  26. ., esp ; 安装新的SEH
  27. .text:000112BE
  28. .text:000112BE _lu_top: ; CODE XREF: __local_unwind4+64j
  29. .text:000112BE ; __local_unwind4+80j
  30. .text:000112BE mov eax, [esp+28h+EstablisherFrame]
  31. .] ; 获取ScopeTable
  32. .text:000112C5 mov ecx, [esp+28h+CookiePointer]
  33. .text:000112C9 xor ebx, [ecx] ; 解密scopetable
  34. .text:000112CB mov esi, [eax+0Ch] ; 获取TryLevel
  35. .text:000112CE cmp esi, 0FFFFFFFEh ; 判断是否遍历完毕
  36. .text:000112D1 jz short _lu_done
  37. .text:000112D3 mov edx, [esp+28h+TryLevel]
  38. .text:000112D7 cmp edx, 0FFFFFFFEh
  39. .text:000112DA jz short loc_112E0
  40. .text:000112DC cmp esi, edx ; 判断当前__try是否在EXCEPTION_EXECUTE_HANDLER__try语句里层
  41. .text:000112DE jbe short _lu_done
  42. .text:000112E0
  43. .text:000112E0 loc_112E0: ; CODE XREF: __local_unwind4+4Ej
  44. .]
  45. .+10h] ; _except_handler4中作用一样,不过这里是+0x10,取scopetable
  46. .text:000112E7 mov ecx, [ebx]
  47. .text:000112E9 mov [eax+0Ch], ecx ; 使当前异常帧指向上一个__try语句,也就是移除当前异常帧指向的
  48. .],
  49. .text:000112F0 jnz short _lu_top
  50. .text:000112F2 push 101h
  51. .]
  52. .text:000112FA call __NLG_Notify
  53. .
  54. . ]
  55. . call __NLG_Call ; 进入__finally块处理
  56. .text:0001130C jmp short _lu_top ; 遍历上一个__try/__except(或__finally)
  57. .text:0001130E ; ---------------------------------------------------------------------------
  58. .text:0001130E
  59. .text:0001130E _lu_done: ; CODE XREF: __local_unwind4+45j
  60. .text:0001130E ; __local_unwind4+52j
  61. .
  62. . add esp, 18h
  63. . pop edi
  64. . pop esi
  65. .text:0001131A pop ebx
  66. .text:0001131B retn
  67. .text:0001131B __local_unwind4 endp

看到了有人写出了C语言代码,直接就抄过来了

  1. /**
  2. *
  3. * 操作系统原始的SEH异常帧结构
  4. * struct _EXCEPTION_REGISTRATION_RECORD{
  5. * struct _EXCEPTION_REGISTRATION_RECORD *Next;
  6. * _except_handler Handler;
  7. * }
  8. *
  9. * SEH异常处理函数原型
  10. * EXCEPTION_DISPOSITION (__cdecl *PEXCEPTION_ROUTINE)(
  11. * struct _EXCEPTION_RECORD *_ExceptionRecord,
  12. * void * _EstablisherFrame,
  13. * struct _CONTEXT *_ContextRecord,
  14. * void * _DispatcherContext
  15. * );
  16. *
  17. * C/C++编译器扩展SEH的异常帧结构:
  18. * [ebp-18] ESP
  19. * [ebp-14] PEXCEPTION_POINTERS xpointers;
  20. * struct _EXCEPTION_REGISTRATION{
  21. * [ebp-10] struct _EXCEPTION_REGISTRATION *Prev;
  22. * [ebp-0C] PEXCEPTION_ROUTINE Handler;
  23. * [ebp-08] struct _EH4_SCOPETABLE *ScopeTable;
  24. * [ebp-04] int TryLevel;
  25. * [ebp-00] int _Ebp;
  26. * };
  27. *
  28. * C/C++运行库使用的SCOPE TABLE结构
  29. * struct _EH4_SCOPETABLE {
  30. * DWORD GSCookieOffset;
  31. * DWORD GSCookieXOROffset;
  32. * DWORD EHCookieOffset;
  33. * DWORD EHCookieXOROffset;
  34. * struct _EH4_SCOPETABLE_RECORD ScopeRecord;
  35. * };
  36. *
  37. * C/C++运行库使用的SCOPE TABLE RECORD结构
  38. * struct _EH4_SCOPETABLE_RECORD {
  39. * DWORD EnclosingLevel; //上一层__try块
  40. * PVOID FilterFunc; //过滤表达式
  41. * union
  42. * {
  43. * PVOID HandlerAddress; //__except块代码
  44. * PVOID FinallyFunc; //__finally块代码
  45. * };
  46. * };
  47. *
  48. * 参数说明:
  49. * CookiePointer - 安全码所在地址,用于解密异常帧的ScopeTable.
  50. * EstablisherFrame - 当前异常帧结构
  51. * TryLevel - __try/__except(EXCEPTION_EXECUTE_HANDLER)所在的__try
  52. *
  53. **/
  54. int __cdecl _local_unwind4(int *CookiePointer,
  55. _EXCEPTION_REGISTRATION_RECORD *EstablisherFrame,
  56. int TryLevel
  57. )
  58. {
  59. //安装SEH,_local_unwind4局部展开发生异常时调用
  60. __asm push _unwind_handler4
  61. __asm push dword ptr fs:[]
  62. __asm mov fs:[],esp
  63.  
  64. //解密ScopeTable
  65. struct _EH4_SCOPETABLE * pScopeTable = EstablisherFrame->ScopeTable ^ (*CookiePointer);
  66. struct _EH4_SCOPETABLE_RECORD * pScopeRecord = &pScopeTable->ScopeRecord;
  67.  
  68. //当前异常帧不存在__try块,退出!
  69. while(EstablisherFrame->TryLevel != 0xFFFFFFFE)
  70. {
  71. //越里层的__try其TryLevel值越高,捕获异常的__try通常在引发异常__try的外层
  72. //这里判断当前异常帧指向的__try的TryLevel值是否正常.
  73. if(EstablisherFrame->TryLevel!=0xFFFFFFFE && EstablisherFrame->TryLevel <= TryLevel) break;
  74.  
  75. //移除当前__try/__except(__finally)信息,使其指向上一层__try块
  76. EstablisherFrame->TryLevel = pScopeRecord->EnclosingLevel;
  77.  
  78. //__except过滤表达式存在,也就是__finally块不存在,向上一层__try/__except(__finally)遍历
  79. if(pScopeRecord->FilterFunc) continue;
  80.  
  81. //作用未知!
  82. __NLG_Notify();
  83.  
  84. //进入__finally块处理
  85. pScopeRecord->FinallyFunc();
  86. }
  87.  
  88. //恢复SEH
  89. __asm pop dword ptr fs:[]
  90. }

到这里概要流程就讲完了。在处理异常和展开过程中多处涉及到遍历操作,咱们来总结一下这些遍历操作。
  1. 在异常处理过程中,每个被"卷入是非"的异常都至少会遍历异常链表两次(如果发生嵌套异常,比如在展开过程中
      EXCEPTION_REGISTRATION_RECORD::Handler 又触发异常,则会遍历更多次。不过这也可以算作是一个新异常了。看如何理解。)。
      一次是在 RtlDispatchException 中,遍历的目的是找到愿意处理该异常的 _EXCEPTION_REGISTRATION_RECORD。
      另一次是在展开过程中、RtlUnwind 函数内,遍历的目录是为了对每个遍历到的 EXCEPTION_REGISTRATION_RECORD 进行局部展开。
  2. 同样的,每个被"卷入是非"的异常的 scopetable 也会被遍历至少两次,
      一次是在 modulename!_except_handler? 中,遍历目的也是找到愿意处理该异常的 scopetable_entry。
      另一次是在展开过程中、_local_unwind4 函数内,遍历的目的是找到所有指定范围内的 scopetable_entry::lpfnFilter 为 NULL 的 scopetable_entry,调用它们的 lpfnHandler (即 __finally 处理块)。

转载(https://www.bbsmax.com/A/RnJW7l2rJq/