这篇文章是早前跟某同事探讨Windows SEH中__finally
实现时研究的内容, 根据某书介绍, 异常处理函数都是通过_EXCEPTION_REGISTRATION_RECORD
内的回调函数Handler
实现的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| typedef struct _EXCEPTION_REGISTRATION_RECORD { struct _EXCEPTION_REGISTRATION_RECORD *Next; PEXCEPTION_ROUTINE Handler; } EXCEPTION_REGISTRATION_RECORD, *PEXCEPTION_REGISTRATION_RECORD; __try { ... } __except( XXX ) { ... }
|
但是对于__finally
却没有介绍具体的实现方式. 通过调试器trace不到__finally
的调用路径.
限于当时搜商有限, 没有找到比较好的资料, 只能结合<<软件调试>>中介绍的内容自己进行摸索实验.
写了一段包含__try
, __finally
的简单代码进行反汇编, 发现VC对SEH做了些扩展, 往ExceptionList中添加的链表节点并不是书上描述的_EXCEPTION_REGISTRATION_RECORD
,而是_EH3_EXCEPTION_REGISTRATION
, 并且多向栈里压了8个字节. 同时异常处理函数也不是__except
或者__finally
里面的代码,而是统一的__except_handler3
(老的编译器可能是__except_handler2
, 还有那个某网站上贴的代码是__except_handler4
, 不同版本VC扩展的结构不太一样, 但基本原理都差不多), 真正的__finally
和__except
中的处理代码放在_EXCEPTION_REGISTRATION_RECORD
里的_SCOPETABLE_ENTRY
中:
1 2 3 4 5 6 7 8
| struct _EH3_EXCEPTION_REGISTRATION { _EH3_EXCEPTION_REGISTRATION * Next; void * ExceptionHandler; _SCOPETABLE_ENTRY * ScopeTable; DWORD TryLevel; };
|
1 2 3 4 5 6 7 8 9 10 11 12
| typedef struct _SCOPETABLE_ENTRY { DWORD EnclosingLevel; PVOID FilterFunc; PVOID HandlerFunc; } SCOPETABLE_ENTRY, *PSCOPETABLE_ENTRY;
|
下面整段 __try
, __finally
的反汇编:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| int func_in() { 009F13C0 push ebp 009F13C1 mov ebp,esp 009F13C3 push 0FFFFFFFFh 009F13C5 push offset ___rtc_tzz+108h (9F6B40h) 009F13CA push offset @ILT+115(__except_handler3) (9F1078h) 009F13CF mov eax,dword ptr fs:[00000000h] 009F13D5 push eax 009F13D6 mov dword ptr fs:[0],esp 009F13DD add esp,0FFFFFF38h 009F13E3 push ebx 009F13E4 push esi 009F13E5 push edi 009F13E6 lea edi,[ebp-0D8h] 009F13EC mov ecx,30h 009F13F1 mov eax,0CCCCCCCCh 009F13F6 rep stos dword ptr es:[edi] __try 009F13F8 mov dword ptr [ebp-4],0 { printf("fin try!\n"); 009F13FF mov esi,esp 009F1401 push offset string "fin try!\n" (9F574Ch) 009F1406 call dword ptr [__imp__printf (9F82C0h)] 009F140C add esp,4 009F140F cmp esi,esp 009F1411 call @ILT+320(__RTC_CheckEsp) (9F1145h) __leave; 009F1416 jmp func_in+62h (9F1422h) *(int*)0=0; 009F1418 mov dword ptr ds:[0],0 } __finally 009F1422 mov dword ptr [ebp-4],0FFFFFFFFh 009F1429 call $LN5 (9F1430h) 009F142E jmp $LN8 (9F1448h) { printf("fin finally!\n"); 009F1430 mov esi,esp 009F1432 push offset string "fin finally!\n" (9F573Ch) 009F1437 call dword ptr [__imp__printf (9F82C0h)] 009F143D add esp,4 009F1440 cmp esi,esp 009F1442 call @ILT+320(__RTC_CheckEsp) (9F1145h) $LN6: 009F1447 ret } return 0; 009F1448 xor eax,eax }
|
函数在进入__try
块前, 首先在栈上安装了自己的异常处理函数, 并把__finally
或者__except
的放入ScopeTable做为参数同样压栈, 通过调试器分析压入的ScopeTable地址0x9F6B40
处的内容:
0x9F6B40 + 8
也就是_SCOPETABLE_ENTRY::HandlerFunc
正好指向0x009f1430
, 对应__finally
里代码段的地址. 如果是__except
, 那这里对应的就是__except
中的代码段.
<<软件调试>>中有一部分对SEH的分析, 该书的确是本好书, 即使不做Windows开发拿来学习操作系统也是相当不错的. 并且作者十分负责, 出版之后发布了一个电子版的补编, 补全了很多没有写入书中的内容(包括该__finally
的实现).