// 操作系统定义的基本异常帧 struct EXCEPTION_REGISTRATION {
EXCEPTION_REGISTRATION* prev; FARPROC handler; };
// Visual C++ 扩展异常帧指向的数据结构 struct scopetable_entry {
DWORD previousTryLevel; FARPROC lpfnFilter; FARPROC lpfnHandler; };
// Visual C++ 使用的扩展异常帧
struct VC_EXCEPTION_REGISTRATION : EXCEPTION_REGISTRATION {
scopetable_entry * scopetable; int trylevel; int _ebp; };
//---------------------------------------------------------------- // 原型声明
//---------------------------------------------------------------- // __except_handler3 是Visual C++运行时库函数,我们想打印出它的地址 // 但是它的原型并没有出现在任何头文件中,所以我们需要自己声明它。 extern \ EXCEPTION_REGISTRATION *, PCONTEXT,
PEXCEPTION_RECORD);
//------------------------------------------------------------- // 代码
//------------------------------------------------------------- //
// 显示一个异常帧及其相应的scopetable的信息 //
void ShowSEHFrame( VC_EXCEPTION_REGISTRATION * pVCExcRec ) {
printf( \ pVCExcRec, pVCExcRec->handler, pVCExcRec->prev, pVCExcRec->scopetable );
scopetable_entry * pScopeTableEntry = pVCExcRec->scopetable; for ( unsigned i = 0; i <= pVCExcRec->trylevel; i++ ) {
printf( \ \ pScopeTableEntry->previousTryLevel, pScopeTableEntry->lpfnFilter, pScopeTableEntry->lpfnHandler );
pScopeTableEntry++; }
printf( \ }
//
// 遍历异常帧的链表,按顺序显示它们的信息 //
void WalkSEHFrames( void ) {
VC_EXCEPTION_REGISTRATION * pVCExcRec; // 打印出__except_handler3函数的位置
printf( \printf( \
// 从FS:[0]处获取指向链表头的指针 __asm mov eax, FS:[0] __asm mov [pVCExcRec], EAX
// 遍历异常帧的链表。0xFFFFFFFF标志着链表的结尾 while ( 0xFFFFFFFF != (unsigned)pVCExcRec ) {
ShowSEHFrame( pVCExcRec );
pVCExcRec = (VC_EXCEPTION_REGISTRATION *)(pVCExcRec->prev); } }
void Function1( void ) {
// 嵌套3层__try块以便强制为scopetable数组产生3个元素 __try {
__try {
__try {
WalkSEHFrames(); // 现在显示所有的异常帧的信息 } __except( EXCEPTION_CONTINUE_SEARCH ) {}
} __except( EXCEPTION_CONTINUE_SEARCH ) {}
} __except( EXCEPTION_CONTINUE_SEARCH ) {} }
int main() {
int i;
// 使用两个__try块(并不嵌套),这导致为scopetable数组生成两个元素 __try {
i = 0x1234;
} __except( EXCEPTION_CONTINUE_SEARCH ) {
i = 0x4321; } __try {
Function1(); // 调用一个设置更多异常帧的函数 } __except( EXCEPTION_EXECUTE_HANDLER ) {
// 应该永远不会执行到这里,因为我们并没有打算产生任何异常 printf( \ }
return 0; }
ShowSEHFrames 程 序中比较重要的函数是WalkSEHFrames和ShowSEHFrame。WalkSEHFrames函数首选打印出 __except_handler3的地址,打印它的原因很快就清楚了。接着,它从FS:[0]处获取异常链表的头指针,然后遍历该链表。此链表中每个结 点都是一个
VC_EXCEPTION_REGISTRATION类型的结构,它是我自己定义的,用于描述Visual C++的异常处理帧。对于这个链表中的每个结点,WalkSEHFrames都把指向这个结点的指针传递给ShowSEHFrame函数。
ShowSEHFrame 函 数一开始就打印出异常处理帧的地址、异常处理回调函数的地址、前一个异常处理帧的地址以及scopetable的地址。接着,对于每个 scopetable数组中的元素,它都打印出其priviousTryLevel、过滤器表达式的地址以及相应的__except块的地址。我是如何知 道scopetable数组中有多少个元素的呢?其实我并不知道。但是我假
定 VC_EXCEPTION_REGISTRATION 结构中的当前trylevel域的值比scopetable数组中的元素总数少1。
图 11是ShowSEHFrames的运行结果。首先检查以“Frame:”开头的每一行,你会发现它们显示的异常处理帧在堆栈上的地址呈递增趋势,并且在 前三个帧中,它们的异常处理程序的地址是一样的(都是004012A8)。再看输出的开始部分,你会发现这个004012A8不是别的,它正是 Visual C++运行时库函数__except_handler3的地址。这证明了我前面所说的单个回调函数处理所有异常这一点。
图11 ShowSEHFrames运行结果
你 可能想知道为什么明明ShowSEHFrames程序只有两个函数使用SEH,但是却有三个异常处理帧使用__except_handler3作为它们的 异常回调函数。实际上第三个帧来自Visual C++运行时库。Visual C++运行时库源代码中的CRT0.C文件清楚地表明了对main或WinMain的调用也被一个__try/__except块封装着。这个__try 块的过滤器表达式代码可以在WINXFLTR.C文件中找到。
回 到ShowSEHFrames程序,注意到最后一个帧的异常处理程序的地址是77F3AB6C,这与其它三个不同。仔细观察一下,你会发现这个地址在 KERNEL32.DLL中。这个特别的帧就是由KERNEL32.DLL中的BaseProcessStart函数安装的,这在前面我已经说过。
展开
在 挖掘展开(Unwinding)的实现代码之前让我们先来搞清楚它的意思。我在前面已经讲过所有可能的异常处理程序是如何被组织在一个由线程信息块的第一 个DWORD(FS:[0])所指向的链表中的。由于针对某个特定异常的处理程序可能不在这个链表的开头,因此就需要从链表中依次移除实际处理异常的那个 异常处理程序之前的所有异常处理程序。
正如你在Visual C++的__except_handler3函数中看到的那样,展开是
由 __global_unwind2 这个运行时库(RTL)函数来完成的。这个函数只是对 RtlUnwind 这个未公开的API进行了非常简单的封装。(现在这个API已经被公开了,但给出的信息极其简单,详细信息可以参考最新的Platform SDK文档。) __global_unwind2(void * pRegistFrame) {
_RtlUnwind( pRegistFrame, &__ret_label, 0, 0 ); __ret_label: }
虽然从技术上讲RtlUnwind是一个KERNEL32函数,但它只是转发到了NTDLL.DLL中的同名函数上。图12是我为此函数写的伪代码。 图12 RtlUnwind函数的伪代码
void _RtlUnwind( PEXCEPTION_REGISTRATION pRegistrationFrame,
PVOID returnAddr, // 并未使用!(至少是在i386机器上)
PEXCEPTION_RECORD pExcptRec, DWORD _eax_value) {
DWORD stackUserBase; DWORD stackUserTop;
PEXCEPTION_RECORD pExcptRec; EXCEPTION_RECORD exceptRec; CONTEXT context;
// 从FS:[4]和FS:[8]处获取堆栈的界限
RtlpGetStackLimits( &stackUserBase, &stackUserTop ); if ( 0 == pExcptRec ) // 正常情况 {
pExcptRec = &excptRec;
pExcptRec->ExceptionFlags = 0;
pExcptRec->ExceptionCode = STATUS_UNWIND; pExcptRec->ExceptionRecord = 0;
pExcptRec->ExceptionAddress = [ebp+4]; // RtlpGetReturnAddress() —获取返回地址
pExcptRec->ExceptionInformation[0] = 0; }
if ( pRegistrationFrame )
pExcptRec->ExceptionFlags |= EXCEPTION_UNWINDING;
else // 这两个标志合起来被定义为EXCEPTION_UNWIND_CONTEXT pExcptRec->ExceptionFlags|=(EXCEPTION_UNWINDING|EXCEPTION_EXIT_UNWIND); CONTEXT_INTEGER | CONTEXT_SEGMENTS);
RtlpCaptureContext( &context ); context.Esp += 0x10; context.Eax = _eax_value;
PEXCEPTION_REGISTRATION pExcptRegHead;
pExcptRegHead = RtlpGetRegistrationHead(); // 返回FS:[0]的值 // 开始遍历EXCEPTION_REGISTRATION结构链表 while ( -1 != pExcptRegHead ) {
EXCEPTION_RECORD excptRec2;
if ( pExcptRegHead == pRegistrationFrame ) {
NtContinue( &context, 0 ); } else {
// 如果存在某个异常帧在堆栈上的位置比异常链表的头部还低 // 说明一定出现了错误
if ( pRegistrationFrame && (pRegistrationFrame <= pExcptRegHead) )
xt.ContextFlags =( CONTEXT_i486 | CONTEXT_CONTROL |
百度搜索“77cn”或“免费范文网”即可找到本站免费阅读全部范文。收藏本站方便下次阅读,免费范文网,提供经典小说教育文库深入探索Win32结构化异常处理 - 图文(5)在线全文阅读。
相关推荐: