很早之前就看了Matt Pietrek的A Crash Course on the Depths of Win32 Structured Exception Handling这篇巨作,真是前无古人,后有仿者。
今天突发奇想研究了一下VS2005的SEH,又看到些新的东西,下面说明一下。
看一下文中引用的EXSUP.INC中的异常注册的定义:
struct _EXCEPTION_REGISTRATION{ struct _EXCEPTION_REGISTRATION *prev; void (*handler)(PEXCEPTION_RECORD, PEXCEPTION_REGISTRATION, PCONTEXT, PEXCEPTION_RECORD); struct scopetable_entry *scopetable; int trylevel; int _ebp; PEXCEPTION_POINTERS xpointers; };
ebp其实应该不是seh中的VC_EXCEPTION_REGISTRATION_RECORD的成员,因为在VC++中无论有否SEH都会用ebp去表示当前函数堆栈起始,参数和变量都是通过ebp访问的。
那这个ebp使用来干什么的呢?
编译器编译出来的函数代码中前两条语句基本上总是
push ebp
mov esp,ebp
而在返回的时候将ebp直接送给esp,pop先前栈上的ebp的
mov ebp, esp
pop ebp
不知道大家记得这两条x86指令:
enter 等价于 push ebp mov esp,ebp leave 等价于 mov ebp, esp pop ebp
这两条指令就是设计来完成这个工作的,从这可以看出使用ebp作为esp的快照是普遍采用的,这样更说明ebp可能不是VC_EXCEPTION_REGISTRATION_RECORD的成员,可能恰巧在那里了。
PS : 可以看下GCC,GCC也是这样使用ebp的。
另外一个恰巧就是xpointers,大家知道call指令的作用就是将eip下条指令压入堆栈(当然,CPU在解析完call指令后,eip其实应该已经指向下条指令了,只要将eip压入即可),并将调用地址赋值给eip,这样下条执行就从新函数开始了。
所以这个xpointers也是所说的恰巧在那里。
所以实际的注册结构应该为:
struct _EH3_EXCEPTION_REGISTRATION { _EH3_EXCEPTION_REGISTRATION * Next; void * ExceptionHandler; _SCOPETABLE_ENTRY * ScopeTable; DWORD TryLevel; };
但是如果你自己跟踪堆栈就会发现,VC在这个注册结构的上方(堆栈上方)还放置了两个成员。
一个存储了当时的esp,这个esp就是这个函数使用的最高堆栈地址(不包括函数调用),而如果这个函数调用其它函数,则这个地址刚好指向函数调用堆栈的起始。
另外一个成员在构造这个注册结构的时候没有初始化,而且如果这个函数没有发生过异常,这个成员都不会被赋值,只有在发生异常时,这个成员才被写入,而且其值是_EXCEPTION_POINTERS *。
这个如果算上ebp和那个返回地址的话,整个的注册结构应该是这样的:
struct _EH_EXCEPTION_REGISTRATION_RECORD { void * SavedESP; _EXCEPTION_POINTERS * ExceptionPointers; _EXCEPTION_REGISTRATION_RECORD SubRecord; INT_PTR ScopeTable; DWORD TryLevel; // DWORD _ebp; // PEXCEPTION_POINTERS xpointers; };
VC的SEH从VC6到VS2003就没什么变化,只是增加了一些判断是否有效的函数,但是到了2005问题就来了。ShowSEHFrames 这个函数在VS2005下会在打印ScopeTable是触发异常。这是因为VS2005将C++EH的版本更新到了第四版。
第四版的EH修改了ScopeTable的数据结构,但是这次升级可不是单纯改个结构。因为VS2005加强了代码的安全性,Crt的内部指针都被加密了。
但是经过努力,终于将VS2005的EH结构分析出来,在此贡献给大家:
typedef _EXCEPTION_DISPOSITION (*EXCEPTION_HANDLER)( PEXCEPTION_RECORD, _EXCEPTION_REGISTRATION_RECORD *, PCONTEXT, PEXCEPTION_RECORD ); struct _EH_SCOPETABLE_RECORD { DWORD EnclosingLevel; long (*FilterFunc)(void); union { void (*HandlerAddress)(void); void (*FinallyFunc)(void); } u; }; struct _EH4_SCOPETABLE_RECORD : _EH_SCOPETABLE_RECORD {}; struct _EH3_SCOPETABLE_RECORD : _EH_SCOPETABLE_RECORD {}; struct _EH3_SCOPETABLE { _EH3_SCOPETABLE_RECORD ScopeRecord[1]; }; struct _EH4_SCOPETABLE { DWORD GSCookieOffset; DWORD GSCookieXOROffset; DWORD EHCookieOffset; DWORD EHCookieXOROffset; _EH4_SCOPETABLE_RECORD ScopeRecord[1]; }; struct _EXCEPTION_REGISTRATION_RECORD { struct _EXCEPTION_REGISTRATION_RECORD *Next; EXCEPTION_HANDLER Handler; }; struct _EH_EXCEPTION_REGISTRATION_RECORD { void * SavedESP; _EXCEPTION_POINTERS * ExceptionPointers; _EXCEPTION_REGISTRATION_RECORD SubRecord; INT_PTR ScopeTable; DWORD TryLevel; // DWORD _ebp; // PEXCEPTION_POINTERS xpointers; }; struct _EH4_EXCEPTION_REGISTRATION_RECORD { void * SavedESP; _EXCEPTION_POINTERS * ExceptionPointers; _EXCEPTION_REGISTRATION_RECORD SubRecord; INT_PTR EncodedScopeTable; DWORD TryLevel; // DWORD _ebp; // PEXCEPTION_POINTERS xpointers; };
还有操作系统的Shell32.dll内部的EH还是版本3。所以,已经不能简单的将操作系统BaseProcessStart中的注册记录 对应的ShowSEHFrames 程序应该修改如下:
//---------------------------------------------------------------------------- // Prototypes //---------------------------------------------------------------------------- // _except_handler3 在VS2005中还存在,不过他已经失去原有的作用了,取而代之的是 // _except_handler4 extern "C" _EXCEPTION_DISPOSITION _except_handler3( PEXCEPTION_RECORD, _EXCEPTION_REGISTRATION_RECORD *, PCONTEXT, PEXCEPTION_RECORD ); // _except_handler4 是VC++运行库函数。我们想使用它的地址,但是我们需要定义它 // 的参数和返回值,因为微软没有头文件提供它的定义。 extern "C" _EXCEPTION_DISPOSITION _except_handler4( PEXCEPTION_RECORD, _EXCEPTION_REGISTRATION_RECORD *, PCONTEXT, PEXCEPTION_RECORD ); //---------------------------------------------------------------------------- // Code //---------------------------------------------------------------------------- // // Display the information in one exception frame, along with its scopetable // void ShowSEHFrame( _EH_EXCEPTION_REGISTRATION_RECORD * pVCExcRec ) { VOID *pScopeTable = NULL; _EH_SCOPETABLE_RECORD *pScopeTableEntry = NULL; if( pVCExcRec->SubRecord.Handler == _except_handler4 ) { // pVCExcRec 是 _EH4_EXCEPTION_REGISTRATION_RECORD* _EH4_SCOPETABLE *pEH4ScopeTable; pEH4ScopeTable = (_EH4_SCOPETABLE *)pVCExcRec->ScopeTable;
// EH4的ScopeTable使用cookie加密过了 ((DWORD_PTR&)pEH4ScopeTable) ^= __security_cookie;
// EH3和EH4的ScopeTable位置不同,但是结构相同 pScopeTableEntry = pEH4ScopeTable->ScopeRecord; pScopeTable = pEH4ScopeTable; } else { _EH3_SCOPETABLE *pEH3ScopeTable; pEH3ScopeTable = (_EH3_SCOPETABLE *)pVCExcRec->ScopeTable; // EH3和EH4的ScopeTable位置不同,但是结构相同
pScopeTableEntry = pEH3ScopeTable->ScopeRecord; pScopeTable = pEH3ScopeTable; }//END_IF printf( "Frame: %p Handler: %p Prev: %p Scopetable: %p/n", pVCExcRec, pVCExcRec->SubRecord.Handler, pVCExcRec->SubRecord.Next, pScopeTable ); for( DWORD dwIndex = 0; dwIndex <= pVCExcRec->TryLevel; ++dwIndex ) { printf( " scopetable[%u] PrevTryLevel: %08X " "filter: %p __except: %p/n", dwIndex, pScopeTableEntry->EnclosingLevel, pScopeTableEntry->FilterFunc, pScopeTableEntry->u.HandlerAddress ); pScopeTableEntry++; }//END_FOR printf( "/n" ); } // // Walk the linked list of frames, displaying each in turn // void WalkSEHFrames( void ) { _EXCEPTION_REGISTRATION_RECORD *pExcRec; // Print out the location of the __except_handler3 function printf( "_except_handler3 is at address: %p/n", _except_handler3 ); printf( "_except_handler4 is at address: %p/n", _except_handler4 ); printf( "/n" ); // Get a pointer to the head of the chain at FS:[0] __asm mov eax, FS:[0]; __asm mov [pExcRec], EAX; // Walk the linked list of frames. 0xFFFFFFFF indicates the end of list while( DWORD_PTR(-1) != (DWORD_PTR)pExcRec ) { _EH_EXCEPTION_REGISTRATION_RECORD *pVCEcxRec = CONTAINING_RECORD( pExcRec, _EH_EXCEPTION_REGISTRATION_RECORD, SubRecord ); ShowSEHFrame( pVCEcxRec ); pExcRec = pExcRec->Next; } }