Программирование на языке MFC

Мой второй блог в серии программирования

Очевидно, что первое поле этой структуры представляет собой указатель на предыдущую структуру этого же типа, а второе поле -указатель на обработчик исключений. Из всего сказанного выше можно сделать вывод о том, что обработчики исключений связаны в однонаправленный список, указатель на начало которого запи­сывается в структуру типа _NT_TIB, адрес которой, в свою оче­редь, записывается в регистр FS.

Итак, мы получили отправную точку для дальнейших поисков. Скорее всего, мы начинаем формировать в стеке структуру типа _EXCEPTION_REGISTRATION, в которую будет записан указатель на обработчик исключений, находящийся в нашей программе. Од­нако не забудьте о том, что стек растет от больших адресов памя­ти, поэтому сначала в стек будут записан указатель на обработчик исключения, а потом указатель на следующую структуру.

Теперь забежим немного вперед. В нашем потоке до сего мо­мента не зарегистрировано НИ ОДНОГО обработчика исключений. Если мы сейчас взглянем на содержимое участка памяти, по кото­рому расположен TIB (fs:[00000000]), то мы увидим, что значение первого двойного слова по этому адресу, т. е. значение слова, в ко-тором должен находится указатель на структуру типа _EXCEPTION_REGISTRATION, равно OxFFFFFFFF. Из этого выте­кает, что значение OxFFFFFFFF является обозначением конца спи­ска регистрации обработчиков исключений*

Чтобы проверить наш вывод, напишем попутно еще одну не­большую программу, которая будет читать указатель на начало списка обработчиков исключений, а потом выводить полученную информацию в окно отладки. Текст этой программы приведен ниже:

#include <afxwin.h> #include "tib.h"

// Объявляем класс приложения, его поля и методы, class CTheFirstApp : public CWinApp

{

public:

virtual BOOL Initlnstance();

};

// Инициализация класса приложения. BOOL CTheFirstApp::Initlnstance () {

PTIB pTIB ; asm

{

// Выбираем из TIB’а указатель на самого себя. MOV ЕАХ , FS:[ 18h ] MOV pTIB , ЕАХ

}

// Выдаем на отображение значение указателя на TIB. TRACE( "Pointer to the TIB = %x\n", pTIB );

// Перебираем весь список обработчиков исключений

for ( _ЕХСЕРТION_REGISTRATION_RECORD* pTempExceptionPointer

= pTIB->pvExcept; UINT( pTempExceptionPointer ) != Oxffffffff; pTempExceptionPointer = pTempExceptionPointer->pNext )

{

TRACE("Pointer to

_EXCEPTION_REGISTRATION_RECORD=%08x\n",

pTempExceptionPointer ); TRACE( "Pointer to the next record = %08x\n",

pTempExceptionPointer->pNext ); TRACE( "Pointer to the exception handler = %08x\n",

pTempExceptionPointer->pfnHandler );

}

return TRUE;

CTheFirstApp theApp;



03.02.2010

Ниже приведен файл tib.h:

//==============================================

// File: TIB.H

// Author: Matt Pietrek

// From: Microsoft Systems Journal "Under the Hood", // May 1996

//===============================================

#pragma pack(l)

typedef struct _EXCEРТION_REGISTRATION_RECORD {

struct _EXCEРТION_REGISTRATION_RECORD * pNext; FARPROC pfnHandler; } EXCEPTION_REGISTRATION_RECORD,

*РЕХСЕ РТION_REGISTRATION_RECORD; typedef struct _TIB {

PEXCEPTION_REGISTRATION_RECORD pvExcept; // OOh Head of

// exception record list

PVOID pvStackUserTop; // 04h Top of user stack

PVOID pvStackUserBase; // 08h Base of user stack

union // OCh (NT/Win95 differences)

{

struct // Win95 fields {

WORD pvTDB; // OCh TDB

WORD pvThunkSS; // OEh SS selector used

// for thunking to 16 bits
DWORD unknownl;
// lOh

} WIN95;

struct // WinNT fields {

PVOID SubSystemTib; // OCh

ULONG FiberData; // lOh

} WINNT; } TIBJJNION1;

PVOID pvArbitrary; // 14h Available for application use struct _tib *ptibSelf; // 18h Linear address of TIB structure

union {

// ICh (NT/Win95 differences)

struct // Win95 fields {

WORD WORD DWORD DWORD DWORD } WIN95;

ICh lEh 20h 24h

// // // //

TIBFlags; Winl6MutexCount; DebugContext; pCurrentPriority;

// 28h Message Queue selector

struct // WinNT fields {

DWORD unknownl;

// IChDWORD processID; // 20h

DWORD threadID; // 24h

DWORD unknown2; // 28h

} WINNT; } TIB_UNION2;

PVOID* pvTLSArray; // 2Ch Thread Local Storage array

union // 30h (NT/WinJ5 differences)

{

struct // Win95 fields {

PVOID* pProcess; // 30h Pointer to owning // process" database

} WIN95; } TIB_UNI0N3;

} TIB, *PTIB; #pragma pack()

И наконец, результат работы программы:

Pointer to the TIB = 815f75cc

Pointer to _EXCEРТION_REGISTRATION_RECORD = 0064fe28

Pointer to the next record = 0064ЈЈ68

Pointer to the exception handler = 00401c38

Pointer to _EXCEРТION_REGISTRATION_RECORD = 0064ff68

Pointer to the next record = ffffffff

Pointer to the exception handler = bffc05b4

Warning: m_pMainWnd is NULL in CWinApp: :Run – quitting

application.



Объем выдаваемой в структуре типа CONTEXT информации ре­гулируется полем ContextFlags. Назначение флагов этого поля при­ведено в табл. 6.

Необходимо заметить, что структуры, о которых говорилось выше, доступны ТОЛЬКО в фильтре исключений. Дело в том, что при передаче исключения обработчику данные в стеке разруша­ются. Следовательно, для того чтобы иметь возможность каким-то образом проанализировать информацию об исключении, мы долж­ны где-то сохранить данные, сформированные при исключении, верно? Чтобы продемонстрировать, каким образом можно это сде­лать, я написал небольшую программу. Эта программа сохраняет сформированные в момент исключения данные и затем отобра­жает их в окне отладки. Исходный текст программы приведен ниже:

tinclude <afxwin.h>

// Объявляем класс приложения, его поля и методы.

class CTheFirstApp : public CWinApp

{

public:

virtual BOOL Initlnstance();

};

// Инициализация класса приложения. BOOL CTheFirstApp::Initlnstance() {

EXCEPTION_RECORD erMyExceptionRecord; CONTEXT cMyContext;

__ try

{

RaiseException ( 0xc0000005, EXCEPTION_NONCONTINUABLE, 0, NULL );

_ except( memcpy( SerMyExceptionRecord,

(GetExceptionlnformation())->ExceptionRecord, sizeof( EXCEPTION_RECORD ) ), memcpy( &cMyContext,

(GetExceptionlnformation ())->ContextRecord, sizeof ( CONTEXT ) ), EXCEPTION_EXECUTE_HANDLER )

{

TRACE( "erMyExceptionRecord.ExceptfonCode = %08x\n \ erMyExceptionRecord.ExceptionFlags = %08x\n \ erMyExceptionRecord.ExceptionRecord = %08x\n \ erMyExceptionRecord.ExceptionAddress = %08x\n \ erMyExceptionRecord.NumberParameters = %08x\n", erMyExceptionRecord.ExceptionCode, erMyExceptionRecord.ExceptionFlags, erMyExceptionRecord.ExceptionRecord, erMyExceptionRecord.ExceptionAddress, erMyExceptionRecord.NumberParameters ) ;

if( cMyContext.ContextFlags & CONTEXT_CONTROL ) TRACE( "cMyContext.Ebp = %08x\n \

cMyContext.Eip = %08x\n \

cMyContext.SegCs = %08x\n \

cMyContext.EFlags = %08x\n \

cMyContext.Esp = = %08x\n \

cMyContext.SegSs = %08x\n",

cMyContext.Ebp,

cMyContext.Eip,

cMyContext.SegCs,

cMyContext.EFlags,

cMyContext.Esp,

cMyContext.SegSs ); if( cMyContext.ContextFlags & CONTEXT_INTEGER ) TRACE( "cMyContext.Edi = %08x\n \

CMyContext.Esi = %08x\n \

cMyContext.Ebx = %08x\n \

CMyContext.Edx = %08x\n \

cMyContext.Ecx = %08x\n \

cMyContext.Eax = %08x\n",

cMyContext.Edi,

cMyContext.Esi,

cMyContext.Ebx,

cMyContext.Edx,

cMyContext.Есх,

cMyContext.Еах ); if( cMyContext.ContextFlags & CONTEXTJSEGMENTS ) TRACE( "cMyContext.SegGs = %08x\n \

cMyContext.SegFs = %08x\n \

cMyContext.SegEs = %08x\n \

cMyContext .SegDs = %08x\n",

cMyContext.SegGs,

cMyContext.SegFs,

cMyContext.SegEs,

cMyContext.SegDs )/ if( cMyContext.ContextFlags & CONTEXT_FLOATING_POINT ) TRACE ( "cMyContext.FloatSave.ControlWord = %08x\n \

cMyContext.FloatSave.StatusWord = %08x\n \

cMyContext.FloatSave.TagWord = %08x\n \

cMyContext.FloatSave.ErrorOffset = %08x\n \ cMyContext.FloatSave.ErrorSelector = %08x\n \

cMyContext.FloatSave.DataOffset = %08x\n \ cMyContext.FloatSave.DataSelector = %08x\n",

cMyContext.FloatSave.ControlWord,

cMyContext.FloatSave.StatusWord,

cMyContext.FloatSave.TagWord,

cMyContext.FloatSave.ErrorOffset,

cMyContext.FloatSave.ErrorSelector,

cMyContext.FloatSave.DataOffset,

cMyContext.FloatSave.DataSelector ); if( cMyContext.ContextFlags & CONTEXT_DEBUG_REGISTERS ) TRACE( "cMyContext.DrO = %08x\n \

cMyContext.Drl = %08x\n \

cMyContext.Dr2 = %08x\n \

cMyContext.Dr3 = %08x\n \

cMyContext.Dr6 = %08x\n \

cMyContext.Dr7 = %08x\n",

cMyContext.DrO,

cMyContext.Dr1,

cMyContext.Dr2,

cMyContext.Dr3,

cMyContext.Dr6,

cMyContext.Dr7 );

}

return TRUE;

}

CTheFirstApp theApp;

Результат работы этой программы приведен ниже:

erMyExceptionRecord.ExceptionCode = сО000005

erMyExceptionRecord.ExceptionFlags = 00000001 erMyExceptionRecord.ExceptionRecord = 00000000 erMyExceptionRecord.ExceptionAddress = 0040109a erMyExceptionRecord.NumberParameters = 00000000 cMyContext.Ebp = 0064fd60

cMyContext.Eip = 0040109a *• cMyContext.SegCs = 00000167 cMyContext.EFlags = 00000207 cMyContext.Esp = = 0064f9dc cMyContext.SegSs = 0000016f cMyContext.Edi = 0064fd48

cMyContext.Esi = 0064f9dc cMyContext.Ebx = 00540000 cMyContext.Edx = 004135d4 cMyContext.Ecx = 00414748 cMyContext.Eax = cccccccc cMyContext.SegGs = 00002a2e

cMyContext.SegFs = 00002167 cMyContext.SegEs = 0000016f cMyContext.SegDs = 0000016f cMyContext.FloatSave.ControlWord = ffff027f

cMyContext.FloatSave.StatusWord = ffffOOOO cMyContext.FloatSave.TagWord = ffffffff cMyContext.FloatSave.ErrorOffset = 00000000 cMyContext.FloatSave.ErrorSelector = 00000000 cMyContext.FloatSave.DataOffset = 00000000 cMyContext.FloatSave.DataSelector = ffffOOOO cMyContext.DrO = 00000000

cMyContext.Drl = 00000000 cMyContext.Dr2 = 00000000 cMyContext.Dr3 = 00000000 cMyContext.Dr6 = 00000000 cMyContext.Dr7 = 00000000 Warning: m_j?MainWnd is NULL in CWinApp: :Run – quitting application.

Я подробно рассказал о механизме обработки исключений сред­ствами Windows. Теперь настало время рассказать о том, как ре­шается проблема обработки исключений в MFC.



Наверное, нужно обратить внимание и на то, что в пределах одной функции допускается использование только одной формы обработки исключений. Поэтому внутри функции, использующей С++-форму обработки исключений нам необходимо будет вызвать функцию, использующую SEH-форму обработки исключений. Ис­ходя из всего сказанного выше, мной была написана небольшая программа, которая демонстрирует, каким образом информация о прерываниях SEH может стать доступной MFC:

#include <afxwin.h> #include <eh.h>

void SehToMFCTranslator( UINT, EXCEPTION_POINTERS* ); void RaiseExceptionCall( void );

class CSehToMFCException : public CException { public:

// Структура для хранения данных из стека

EXCEPTION_RECORD erMyExceptionRecord; // Структура для хранения данных из стека

CONTEXT cMyContext’;

CSehToMFCException(

EXCEPTION_POINTERS* epExceptionPointers ) ;

};

CSehToMFCException::CSehToMFCException(

EXCEPTION_POINTERS* epExceptionPointers )

{

memcpy( SerMyExceptionRecord,

epExceptionPointers->ExceptionRecord,

sizeof( EXCEPTION_RECORD ) ); memcpy( &cMyContext,

epExceptionPointers->ContextRecord,

sizeof ( CONTEXT ) );

}

class CSehToMFCApp : public CWinApp {

public:

BOOL Initlnstance ();

};

BOOL CSehToMFCApp::Initlnstance() {

try {

_set_se_translator( SehToMFCTranslator ); RaiseExceptionCall ();

}

catch ( CSehToMFCException* pException ) {

TRACE( "In catch block.\n" );

TRACE( "erMyExceptionRecord.ExceptionCode = %08x\n \ erMyExceptionRecord.ExceptionFlags = %08x\n \ erMyExceptionRecord.Exception"Record = %08x\n \ erMyExceptionRecord.ExceptionAddress = %08x\n \ erMyExceptionRecord.NumberParameters = %08x\n", pExcept ion->erMyExceptionRecord.ExceptionCode, pException->erMyExceptionRecord.ExceptionFlags,

pException->erMyExceptionRecord.ExceptionRecord, pException->erMyExceptionRecord.ExceptionAddress, pException->erMyExceptionRecord.NumberParameters ) ; if ( pException->cMyContext.ContextFlags & CONTEXT_CONTROL ) TRACE ( "cMyContext.Ebp = %08x\n \ cMyContext.Eip = %08x\n \ cMyContext.SegCs = %08x\n \ cMyContext.EFlags = %08x\n \ cMyContext.Esp = = %08x\n \ cMyContext.SegSs = %08x\n", pException->cMyContext.Ebp, pException->cMyContext.Eip, pException->cMyContext.SegCs, pException->cMyContext.EFlags, pException->cMyContext.Esp, pException->cMyContext.SegSs ); if( pException->cMyContext.ContextFlags & CONTEXT_INTEGER ) TRACE( "cMyContext.Edi = %08x\n \ cMyContext.Esi = %08x\n \ cMyContext.Ebx = %08x\n \ cMyContext.Edx = %08x\n \ cMyContext.Ecx = %08x\n \ cMyContext.Eax = %08x\n", pException->cMyContext.Edi, pException->cMyContext.Esi, pException->cMyContext.Ebx, pException->cMyContext.Edx, pException->cMyContext.Ecx, pException->cMyContext.Eax ); if( pException->cMyContext.ContextFlags & CONTEXT_SEGMENTS ) TRACE( "cMyCSntext.SegGs = %08x\n \ cMyContext.SegFs = %08x\n \ cMyContext.SegEs = %08x\n \ cMyContext.SegDs = %08x\n", pException->cMyContext.SegGs, pException->cMyContext.SegFs, pException->cMyContext.SegEs, pException->cMyContext.SegDs ); if( pException->cMyContext.ContextFlags & CONTEXT_FLOATING_POINT )

TRACE( "cMyContext.FloatSave.ControlWord = %08x\n \ cMyContext.FloatSave.StatusWord = %08x\n \ cMyContext.FloatSave.TagWord = %08x\n \ cMyContext.FloatSave.ErrorOffset = %08x\n \ cMyContext.FloatSave.ErrorSeiector = %08x\n \ cMyContext.FloatSave.DaTaOffset = %08x\n \ cMyContext.FloatSave.DataSelector = %08x\n", pException->cMyContext.FloatSave.ControlWord, pException->cMyContext.FloatSave.StatusWord, pException->cMyContext.FloatSave.TagWord, pException->cMyContext.FloatSave.ErrorOffset, pException->cMyContext.FloatSave.ErrorSelector, pException->cMyContext.FloatSave.DataOffset, pException->cMyContext.FloatSave.DataSelector

) ;

if( pException->cMyContext.ContextFlags & CONTEXT_DEBUG_REGISTERS ) TRACE( "cMyContext.DrO = %08x\n \

cMyContext.Drl = %08x\n \

cMyContext.Dr2 = %08x\n \

cMyContext.Dr3 = %08x\n \

cMyContext.Dr6 = %08x\n \

cMyContext.Dr7 = %08x\n", ‘ pException->cMyContext.DrO,

pException->cMyContext.Drl,

pException->cMyContext.Dr2,

pException->cMyContext.Dr3,

pException->cMyContext.Dr6,

pException->cMyContext.Dr7 ); pException->Delete(); return TRUE;

}

return TRUE;

}

void SehToMFCTranslator( UINT nExceptionCode,

EXCEPTION_POINTERS* epExceptionPointers )

{

TRACE( "In SehToMFCTranslator().\n" );

throw ( new CSehToMFCException( epExceptionPointers ) ) ;

}

void RaiseExceptionCall ()

{

__ try

{

TRACE( "Before RaiseException () .\n" ); RaiseException( 0xC0000005,

EXCEPTION_NONCONTINUABLE, 0, NULL )/

}

__ finally

{

TRACE( "In finally block.\n" );

}

}

CSehToMFCApp theApp;

Результат работы этой программы:

Before RaiseException(). In SehToMFCTranslator().

In finally block.

In catch block.

erMyExceptionRecord. ExceptionCode = c0000005

erMyExceptionRecord.ExceptionFlags = 00000001 erMyExceptionRecord.ExceptionRecord = 00000000 erMyExceptionRecord. ExceptionAddress = 00401639 erMyExceptionRecord. Number Parameters = 00000000 cMyContext.Ebp = 0064fcf0

cMyContext.Eip = 00401639

cMyContext.SegCs = 00000167

cMyContext.EFlags = 00000202

cMyContext.Esp = = 0064fc8c

cMyContext.SegSs = 0000016f cMyContext.Edi = 0064fcd8

cMyContext.Esi = 0064fc8c

cMyCfon text. Ebx = 00540000

cMyContext.Edx = 00000000

cMyContext.Ecx = 00000000

cMyContext.Eax = 5f4ce0f0 cMyContext.SegGs = 000031e6

cMyContext.SegFs = 00003b67

cMyContext.SegEs = 0000016f

cMyContext.SegDs = 0000016f cMyContext.FloatSave.ControlWord = ffff027f

cMyContext.FloatSave.StatusWord = ffffOOOO

cMyContext.FloatSave.TagWord = ffffffff

cMyContext.FloatSave.ErrorOffset = 00000000 cMyContext.FloatSave.ErrorSelector = 00000000

cMyContext.FloatSave.DataOffset = 00000000

cMyContext.FloatSave.DataSelector = ffffOOOO cMyContext.DrO = 00000000

cMyContext.Drl = 00000000

cMyContext.Dr2 = 00000000

cMyContext.Dr3 = 00000000

cMyContext.Dr6 = 00000000

cMyContext.Dr7 = 00000000 Warning: mjpMainWnd is NULL in CWinApp::Run – quitting application.

Ура! Мы сделали это! Как видно из результатов работы програм­мы, результаты обработки исключения средствами Windows стали доступны в MFC.

Мне думается, что об исключениях можно рассказывать еще достаточно долго. И вопросов осталось еще достаточно много. Но, думаю, вы знаете уже достаточно много об обработке ис­ключений, чтобы самостоятельно находить ответы на возникаю­щие вопросы.