原文链接http://www.openrce.org/articles/full_view/21
... Local variables Other saved registers Saved ebp Return address Function arguments ...
struct _SCOPETABLE_ENTRY { DWORD EnclosingLevel; void* FilterFunc; void* HandlerFunc; }
For more details on SEH implementation see[1]. To recover try blocks watch how the try level variable is updated. It's assigned a unique number per try block, and nesting is described by relationship between scopetable entries. E.g. if scopetable entry i has EnclosingLevel=j, then try block j encloses try block i. The function body is considered to have try level -1. See Appendix 1 for an example.
struct _EH4_SCOPETABLE { DWORD GSCookieOffset; DWORD GSCookieXOROffset; DWORD EHCookieOffset; DWORD EHCookieXOROffset; _EH4_SCOPETABLE_RECORD ScopeRecord[1]; }; struct _EH4_SCOPETABLE_RECORD { DWORD EnclosingLevel; long (*FilterFunc)(); union { void (*HandlerAddress)(); void (*FinallyFunc)(); }; };GSCookieOffset = -2 means that GS cookie is not used. EH cookie is always present. Offsets are ebp relative. Check is done the following way: (ebp+CookieXOROffset) ^ [ebp+CookieOffset] == _security_cookie Pointer to the scopetable in the stack is XORed with the _security_cookie too. Also, in SEH4 the outermost scope level is -2, not -1 as in SEH3.
(VC7+) mov eax, OFFSET __ehfuncinfo jmp ___CxxFrameHandler
__ehfuncinfo is a structure of type FuncInfo which fully describes all try/catch blocks and unwindable objects in the function.
struct FuncInfo { // compiler version. // 0x19930520: up to VC6, 0x19930521: VC7.x(2002-2003), 0x19930522: VC8 (2005) DWORD magicNumber; // number of entries in unwind table int maxState; // table of unwind destructors UnwindMapEntry* pUnwindMap; // number of try blocks in the function DWORD nTryBlocks; // mapping of catch blocks to try blocks TryBlockMapEntry* pTryBlockMap; // not used on x86 DWORD nIPMapEntries; // not used on x86 void* pIPtoStateMap; // VC7+ only, expected exceptions list (function "throw" specifier) ESTypeList* pESTypeList; // VC8+ only, bit 0 set if function was compiled with /EHs int EHFlags; };
struct UnwindMapEntry { int toState; // target state void (*action)(); // action to perform (unwind funclet address) };
Try block descriptor. Describes a try{} block with associated catches.
struct TryBlockMapEntry { int tryLow; int tryHigh; // this try {} covers states ranging from tryLow to tryHigh int catchHigh; // highest state inside catch handlers of this try int nCatches; // number of catch handlers HandlerType* pHandlerArray; //catch handlers table };
Catch block descriptor. Describes a single catch() of a try block.
struct HandlerType { // 0x01: const, 0x02: volatile, 0x08: reference DWORD adjectives; // RTTI descriptor of the exception type. 0=any (ellipsis) TypeDescriptor* pType; // ebp-based offset of the exception object in the function stack. // 0 = no object (catch by type) int dispCatchObj; // address of the catch handler code. // returns address where to continues execution (i.e. code after the try block) void* addressOfHandler; };
List of expected exceptions (implemented but not enabled in MSVC by default, use /d1ESrt to enable).
struct ESTypeList { // number of entries in the list int nCount; // list of exceptions; it seems only pType field in HandlerType is used HandlerType* pTypeArray; };
RTTI type descriptor. Describes a single C++ type. Used here to match the thrown exception type with catch type.
struct TypeDescriptor { // vtable of type_info class const void * pVFTable; // used to keep the demangled name returned by type_info::name() void* spare; // mangled type name, e.g. ".H" = "int", ".?AUA@@" = "struct A", ".?AVA@@" = "class A" char name[0]; };Unlike SEH, each try block doesn't have a single associated state value. The compiler changes the state value not only on entering/leaving a try block, but also for each constructed/destroyed object. That way it's possible to know which objects need unwinding when an exception happens. You can still recover try blocks boundaries by inspecting the associated state range and the addresses returned by catch handlers (see Appendix 2).
struct ThrowInfo { // 0x01: const, 0x02: volatile DWORD attributes; // exception destructor void (*pmfnUnwind)(); // forward compatibility handler int (*pForwardCompat)(); // list of types that can catch this exception. // i.e. the actual type and all its ancestors. CatchableTypeArray* pCatchableTypeArray; }; struct CatchableTypeArray { // number of entries in the following array int nCatchableTypes; CatchableType* arrayOfCatchableTypes[0]; };
struct CatchableType { // 0x01: simple type (can be copied by memmove), 0x02: can be caught by reference only, 0x04: has virtual bases DWORD properties; // see above TypeDescriptor* pType; // how to cast the thrown object to this type PMD thisDisplacement; // object size int sizeOrOffset; // copy constructor address void (*copyFunction)(); }; // Pointer-to-member descriptor. struct PMD { // member offset int mdisp; // offset of the vbtable (-1 if not a virtual base) int pdisp; // offset to the displacement value inside the vbtable int vdisp; };
Name | Type | EH Cookie | GS Cookie | Catch Handlers |
_SEH_prolog/_SEH_epilog | SEH3 | - | - | |
_SEH_prolog4/_SEH_epilog4 S | EH4 | + | - | |
_SEH_prolog4_GS/_SEH_epilog4_GS | SEH4 | + | + | |
_EH_prolog | C++ EH | - | - | +/- |
_EH_prolog3/_EH_epilog3 | C++ EH | + | - | - |
_EH_prolog3_catch/_EH_epilog3 | C++ EH | + | - | + |
_EH_prolog3_GS/_EH_epilog3_GS | C++ EH | + | + | - |
_EH_prolog3_catch_GS/_EH_epilog3_catch_GS | C++ EH | + | + | + |
... Saved edi Saved esi Saved ebx Next SEH frame Current SEH handler (__except_handler2) Pointer to the scopetable Try level Saved ebp (of this function) Exception pointers Local variables Saved ESP Local variables Callee EBP Return address Function arguments ...
func1 proc near _excCode = dword ptr -28h buf = byte ptr -24h _saved_esp = dword ptr -18h _exception_info = dword ptr -14h _next = dword ptr -10h _handler = dword ptr -0Ch _scopetable = dword ptr -8 _trylevel = dword ptr -4 str = dword ptr 8 push ebp mov ebp, esp push -1 push offset _func1_scopetable push offset _except_handler3 mov eax, large fs:0 push eax mov large fs:0, esp add esp, -18h push ebx push esi push edi ; --- end of prolog --- mov [ebp+_trylevel], 0 ;trylevel -1 -> 0: beginning of try block 0 mov [ebp+_trylevel], 1 ;trylevel 0 -> 1: beginning of try block 1 mov large dword ptr ds:123, 456 mov [ebp+_trylevel], 0 ;trylevel 1 -> 0: end of try block 1 jmp short _endoftry1 _func1_filter1: ; __except() filter of try block 1 mov ecx, [ebp+_exception_info] mov edx, [ecx+EXCEPTION_POINTERS.ExceptionRecord] mov eax, [edx+EXCEPTION_RECORD.ExceptionCode] mov [ebp+_excCode], eax mov ecx, [ebp+_excCode] xor eax, eax cmp ecx, EXCEPTION_ACCESS_VIOLATION setz al retn _func1_handler1: ; beginning of handler for try block 1 mov esp, [ebp+_saved_esp] push offset aAccessViolatio ; "Access violation" call _printf add esp, 4 mov [ebp+_trylevel], 0 ;trylevel 1 -> 0: end of try block 1 _endoftry1: mov edx, [ebp+str] push edx lea eax, [ebp+buf] push eax call _strcpy add esp, 8 mov [ebp+_trylevel], -1 ; trylevel 0 -> -1: end of try block 0 call _func1_handler0 ; execute __finally of try block 0 jmp short _endoftry0 _func1_handler0: ; __finally handler of try block 0 push offset aInFinally ; "in finally" call _puts add esp, 4 retn _endoftry0: ; --- epilog --- mov ecx, [ebp+_next] mov large fs:0, ecx pop edi pop esi pop ebx mov esp, ebp pop ebp retn func1 endp _func1_scopetable ;try block 0 dd -1 ;EnclosingLevel dd 0 ;FilterFunc dd offset _func1_handler0 ;HandlerFunc ;try block 1 dd 0 ;EnclosingLevel dd offset _func1_filter1 ;FilterFunc dd offset _func1_handler1 ;HandlerFunc
void func1 (char* str) { char buf[12]; __try // try block 0 { __try // try block 1 { *(int*)123=456; } __except(GetExceptCode() == EXCEPTION_ACCESS_VIOLATION) { printf("Access violation"); } strcpy(buf,str); } __finally { puts("in finally"); } }
func1 proc near _a1 = dword ptr -24h _exc = dword ptr -20h e = dword ptr -1Ch a2 = dword ptr -18h a1 = dword ptr -14h _saved_esp = dword ptr -10h _next = dword ptr -0Ch _handler = dword ptr -8 _state = dword ptr -4 push ebp mov ebp, esp push 0FFFFFFFFh push offset func1_ehhandler mov eax, large fs:0 push eax mov large fs:0, esp push ecx sub esp, 14h push ebx push esi push edi mov [ebp+_saved_esp], esp ; --- end of prolog --- lea ecx, [ebp+a1] call A::A(void) mov [ebp+_state], 0 ; state -1 -> 0: a1 constructed mov [ebp+a1], 1 ; a1.m1 = 1 mov byte ptr [ebp+_state], 1 ; state 0 -> 1: try { lea ecx, [ebp+a2] call A::A(void) mov [ebp+_a1], eax mov byte ptr [ebp+_state], 2 ; state 2: a2 constructed mov [ebp+a2], 2 ; a2.m1 = 2 mov eax, [ebp+a1] cmp eax, [ebp+a2] ; a1.m1 == a2.m1? jnz short loc_40109F mov [ebp+_exc], offset aAbc ; _exc = "abc" push offset __TI1?PAD ; char * lea ecx, [ebp+_exc] push ecx call _CxxThrowException ; throw "abc"; loc_40109F: mov byte ptr [ebp+_state], 1 ; state 2 -> 1: destruct a2 lea ecx, [ebp+a2] call A::~A(void) jmp short func1_try0end ; catch (char * e) func1_try0handler_pchar: mov edx, [ebp+e] push edx push offset aCaughtS ; "Caught %s\n" call ds:printf ; add esp, 8 mov eax, offset func1_try0end retn ; catch (...) func1_try0handler_ellipsis: push offset aCaught___ ; "Caught ...\n" call ds:printf add esp, 4 mov eax, offset func1_try0end retn func1_try0end: mov [ebp+_state], 0 ; state 1 -> 0: }//try push offset aAfterTry ; "after try\n" call ds:printf add esp, 4 mov [ebp+_state], -1 ; state 0 -> -1: destruct a1 lea ecx, [ebp+a1] call A::~A(void) ; --- epilog --- mov ecx, [ebp+_next] mov large fs:0, ecx pop edi pop esi pop ebx mov esp, ebp pop ebp retn func1 endp func1_ehhandler proc near mov eax, offset func1_funcinfo jmp __CxxFrameHandler func1_ehhandler endp func1_funcinfo dd 19930520h ; magicNumber dd 4 ; maxState dd offset func1_unwindmap ; pUnwindMap dd 1 ; nTryBlocks dd offset func1_trymap ; pTryBlockMap dd 0 ; nIPMapEntries dd 0 ; pIPtoStateMap dd 0 ; pESTypeList func1_unwindmap dd -1 dd offset func1_unwind_1tobase ; action dd 0 ; toState dd 0 ; action dd 1 ; toState dd offset func1_unwind_2to1 ; action dd 0 ; toState dd 0 ; action func1_trymap dd 1 ; tryLow dd 2 ; tryHigh dd 3 ; catchHigh dd 2 ; nCatches dd offset func1_tryhandlers_0 ; pHandlerArray dd 0 func1_tryhandlers_0 dd 0 ; adjectives dd offset char * `RTTI Type Descriptor' ; pType dd -1Ch ; dispCatchObj dd offset func1_try0handler_pchar ; addressOfHandler dd 0 ; adjectives dd 0 ; pType dd 0 ; dispCatchObj dd offset func1_try0handler_ellipsis ; addressOfHandler func1_unwind_1tobase proc near a1 = byte ptr -14h lea ecx, [ebp+a1] call A::~A(void) retn func1_unwind_1tobase endp func1_unwind_2to1 proc near a2 = byte ptr -18h lea ecx, [ebp+a2] call A::~A(void) retn func1_unwind_2to1 endp
Let's see what we can find out here. The maxState field in FuncInfo structure is 4 which means we have four entries in the unwind map, from 0 to 3. Examining the map, we see that the following actions are executed during unwinding:
void func1 () { A a1; a1.m1 = 1; try { A a2; a2.m1 = 2; if (a1.m1 == a1.m2) throw "abc"; } catch(char* e) { printf("Caught %s\n",e); } catch(...) { printf("Caught ...\n"); } printf("after try\n"); }