Win32™ SEH изнутри (ч.2) — Архив WASM.RU
SEH на уровне компилятора
Пока я редко упоминал слова _try и _except, так как всё, что я описывал до сих пор, реализуется на уровне ОС. Однако, при взгляде на кривость двух моих маленьких программ, которые использовали, базовый SEH, обертка компилятора вокруг этого механизма - смотрится гораздо лучше. Давайте теперь посмотрим, как Visual C++ формирует свою SEH-надстройку над базовым SEH.
Для начала, важно запомнить, что другой компилятор, используя средства базового SEH уровня системы, может сделать что-то совершенно другое. Ничто не заставляет компилятор реализовывать модель _try/_except, которую описывает документация Win32 SDK. Например, развивающийся Visual Basic 5.0 использует SEH во время выполнения кода, но структуры данных и алгоритмы полностью отличаются от описанных здесь.
Если вы читали раздел документации Win32 SDK о SEH, вам будет понятен следующий синтаксис так называемого “основанного на кадре” ("frame-based”) обработчика исключений:
try { // тело защищаемого кода } except (выражение-фильтр) { // блок обработчика исключений }Проще говоря, весь код внутри _try-блока защищён структурой EXCEPTION_REGISTRATION, которая создана в кадре стека функции. При входе в функцию новая структура EXCEPTION_REGISTRATION помещается в начало связанного списка обработчиков исключений. После завершения блока try, его структура EXCEPTION_REGISTRATION удаляется из списка. Как я уже говорил, адрес начала списка хранится по адресу FS:[0]. Таким образом, если вы при отладке программы встречаете строчки типа:
MOV DWORD PTR FS:[00000000],ESPили
MOV DWORD PTR FS:[00000000],ECXвы можете быть уверены, что код устанавливает или удаляет _try/_except-блок.
Теперь, когда вы знаете, что блок _try соответствует структуре EXCEPTION_REGISTRATION находящейся в стеке, вам, возможно, захочется узнать: что известно относительно callback-функции закрепленной за EXCEPTION_REGISTRATION? Используя терминологию Win32, callback-функция обработчика исключений соответствует коду выражения-фильтра. Чтобы освежить вашу память скажу, выражение-фильтр - это код находящийся в скобках, которые идут сразу после ключевого слова _except. Именно этот код решает, выполнится ли код в последующем {}-блоке.
После того как вы напишите код выражения-фильтра, вы получаете возможность решать, будет ли возникшее исключение обработано кодом данного _except-блока. Код выражения-фильтра может быть очень простым, и просто возвращать значение "EXCEPTION_EXECUTE_ HANDLER". С другой стороны, выражение-фильтр могло бы вызвать функцию, которая производит сложные вычисления, и на основе полученного результата возвращает код, который указывает системе, что делать дальше. В общем, что это будет за код, решаете вы. Ключевой момент: код выражения-фильтра вызывается системой (callback) при возникновении исключения.
То описание, которое я только что дал, является на самом деле упрощенным и идеализированным отражением реальности, предназначенным для того, чтобы вам было проще понять ее. На самом деле все несколько сложнее. Для начала, код выражения-фильтра не вызывается непосредственно операционной системой. На самом деле, поле exception handler в любом EXCEPTION_REGISTRATION указывает на одну и ту же функцию. Эта функция находится в RTL Visual C++, и называется __except_handler3. Код выражения-фильтра вызывается уже из неё. Я вернусь к этому чуть позже.
Другое искажение, сделанное мной для облегчения восприятия, заключается в том, что структура EXCEPTION_REGISTRATION не создаётся и не разрушается каждый раз при входе и выходе из любого _try-блока. Вместо этого, в каждой функции использующей SEH создается только один EXCEPTION_REGISTRATION. Другими словами, вы можете иметь множество _try/_except конструкций в одной функции, но в стеке будет создан только один EXCEPTION_REGISTRATION. Аналогично, вы могли бы сделать в функции вложенные блоки _try. Однако, при этом, Visual C++ создаст только один EXCEPTION_REGISTRATION.
Если единственный обработчик исключений (это упоминавшийся ранее __except_handler3) покрывает весь EXE или DLL, и если единственный EXCEPTION_REGISTRATION разделяет множество _try-блоков, то там очевидно делается более сложная работа, чем кажется на первый взгляд. Этот фокус реализован с помощью таблицы данных, которую вы обычно не видите. Однако, так как общая цель этой статьи состоит в том, чтобы разобрать SEH по винтикам, давайте взглянем, как это делается.
Расширенный фрейм обработки исключений
SEH, реализованный в Visual C++, не использует базовую структуру EXCEPTION_REGISTRATION. Вместо этого, он добавляет в конец этой структуры дополнительные поля данных. Именно эти дополнительные данные позволяют одной функции (__except_handler3) обработать все исключения программы, и управлять вызовом соответствующих выражений-фильтров и _except-блоков. Подсказка к формату используемой в Visual C++ расширенной структуры EXCEPTION_REGISTRATION была найдена в файле EXSUP.INC, который находится в исходниках RTL Visual C++. В этом файле, вы найдете следующее (закомментированное) определение:
;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; ;};С первыми двумя полями (prev и handler) вы уже знакомы. Они составляют основу структуры EXCEPTION_REGISTRATION. Новыми здесь являются последние три поля: scopetable, trylevel и _ebp. Поле scopetable содержит адрес массива структур типа scopetable_entries, в то время как поле trylevel по существу содержит индекс в этом массиве. Последнее поле, _ebp - значение указателя кадра стека (EBP) до создания структуры EXCEPTION_REGISTRATION.
Это не случайно, что поле _ebp стало частью расширенной структуры EXCEPTION_REGISTRATION. Оно включается в структуру операцией PUSH EBP, с которой начинается большинство функций. Это даёт возможность обращаться к другим полям структуры EXCEPTION_REGISTRATION с помощью отрицательного смещения от указателя кадра. Например, поле trylevel находится по адресу [EBP-04], указатель scopetable находится по адресу [EBP-08] и т.д.
Сразу вслед за расширенной структурой EXCEPTION_REGISTRATION, Visual C++ кладёт в стек два дополнительных значения. В значении DWORD, которое он кладёт в первую очередь, хранится указатель на структуру EXCEPTION_POINTERS (стандартную структуру Win32). Это указатель, который возвращает API-функция GetExceptionInformation. Хотя документация SDK подразумевает, что GetExceptionInformation является стандартной API-функцией Win32, на самом же деле это встроенная функция компилятора. Когда вы вызываете эту функцию, Visual C++ генерирует следующее:
MOV EAX,DWORD PTR [EBP-14]Так же, как и GetExceptionInformation, встроенной функцией компилятора, является связанная с ней функция GetExceptionCode. GetExceptionCode просто находит, и возвращает значение поля, принадлежащего одной из структур данных, возвращенных GetExceptionInformation. Я надеюсь что читатель, в качестве упражнения, самостоятельно выяснит, что делают, следующие команды, которые Visual C++ генерирует для GetExceptionCode:
MOV EAX,DWORD PTR [EBP-14] MOV EAX,DWORD PTR [EAX] MOV EAX,DWORD PTR [EAX]Вернемся к расширенной структуре EXCEPTION_REGISTRATION. В восьми байтах от её начала Visual C++ резервирует значение DWORD для того значения указателя стека (ESP), которое он имеет сразу после исполнения кода пролога функции. Этот DWORD - нормальное значение регистра ESP во время выполнения функции (конечно, кроме тех случаев, когда перед вызовом другой функции параметры кладутся в стек).
Похоже, я вывалил на вас тонну информации. Прежде чем двигаться дальше, давайте взглянем на стандартный SEH-фрейм, который создаёт Visual C++ для функции, использующей SEH:
EBP-00 _ebp EBP-04 trylevel EBP-08 указатель на таблицу scopetable EBP-0C адрес функции обработчика EBP-10 Указатель на предыдущую EXCEPTION_REGISTRATION EBP-14 GetExceptionPointers EBP-18 Обычное значение регистра ESP в фреймеС точки зрения ОС, существуют только два поля, из которых состоит базовая структура EXCEPTION_REGISTRATION: указатель prev в [EBP-10] и указатель функцию обработчика в [EBP-0Ch]. Остальное содержимое фрейма является специфическим расширением Visual C++. Теперь, зная всё это, давайте посмотрим на процедуру из RTL Visual C++, которая воплощает SEH на уровне компилятора: __except_handler3.
__except_handler3 и scopetable
Я не могу отправить вас к исходникам RTL Visual C++, чтобы вы сами посмотрели функцию __except_handler3, потому, что её там нет. Вместо этого вам придётся иметь дело с написанной мной версией этой функции в псевдокоде (см. рис. 9).
int __except_handler3( struct _EXCEPTION_RECORD * pExceptionRecord, struct EXCEPTION_REGISTRATION * pRegistrationFrame, struct _CONTEXT *pContextRecord, void * pDispatcherContext ) { LONG filterFuncRet LONG trylevel EXCEPTION_POINTERS exceptPtrs PSCOPETABLE pScopeTable CLD // Сбрасываем флаг направления (не делайте никаких допущений!) // Если ни один из битов: EXCEPTION_UNWINDING, или EXCEPTION_EXIT_UNWIND, // не установлен, то обработчик работает // в своем основном режиме (т.е. он работает не в режиме раскрутки). if ( ! (pExceptionRecord->ExceptionFlags & (EXCEPTION_UNWINDING | EXCEPTION_EXIT_UNWIND)) ) { // Создаем в стеке структуру EXCEPTION_POINTERS. exceptPtrs.ExceptionRecord = pExceptionRecord; exceptPtrs.ContextRecord = pContextRecord; // Помещаем указатель на EXCEPTION_POINTERS в стек на 4 байта ниже // SEH-фрейма. // Смотри ассемблерный код функции GetExceptionInformation. *(PDWORD)((PBYTE)pRegistrationFrame - 4) = &exceptPtrs; // Получаем начальное значение поля "trylevel". trylevel = pRegistrationFrame->trylevel // Получаем указатель на массив scopetable. scopeTable = pRegistrationFrame->scopetable; search_for_handler: if ( pRegistrationFrame->trylevel != TRYLEVEL_NONE ) { if ( pRegistrationFrame->scopetable[trylevel].lpfnFilter ) { PUSH EBP // Сохраняем значение регистра EBP для этого фрейма. // !!!Очень важно!!! Переключение на оригинальное значение регистра // EBP. Это позволяет иметь, в пределах фрейма, одно и // тоже значение в регистре EBP, как до возникновения исключения, // так и после него. EBP = &pRegistrationFrame->_ebp // Вызываем функцию фильтра. filterFuncRet = scopetable[trylevel].lpfnFilter(); POP EBP // Восстанавливаем значение регистра EBP нужное для этого // фрейма обработчика. if ( filterFuncRet != EXCEPTION_CONTINUE_SEARCH ) { if ( filterFuncRet < 0 ) // EXCEPTION_CONTINUE_EXECUTION return ExceptionContinueExecution; // Если управление дошло до этого места, значит фильтр вернул // значение EXCEPTION_EXECUTE_HANDLER. scopetable == pRegistrationFrame->scopetable // С помощью следующей функции ОС фактически производит очистку // зарегистрированных фреймов. // Эта функция рекурсивная. __global_unwind2( pRegistrationFrame ); // Когда выполнение доходит до этого места, все фреймы, // зарегистрированные после фрейма обработчика обрабатывающего // исключение, уже очищены. EBP = &pRegistrationFrame->_ebp __local_unwind2( pRegistrationFrame, trylevel ); // NLG == "non-local-goto" (setjmp/longjmp stuff) __NLG_Notify( 1 ); // EAX == scopetable->lpfnHandler // Устанавливаем текущий trylevel относительно того элемента // SCOPETABLE, // который использовался, когда был найден обработчик. pRegistrationFrame->trylevel = scopetable->previousTryLevel; // Вызываем код _except-блока. Он не возвращает сюда // управление. pRegistrationFrame->scopetable[trylevel].lpfnHandler(); } } scopeTable = pRegistrationFrame->scopetable; trylevel = scopeTable->previousTryLevel goto search_for_handler; } else // trylevel == TRYLEVEL_NONE { retvalue == DISPOSITION_CONTINUE_SEARCH; } } else // Установлен какой-то из флажков: EXCEPTION_UNWINDING или // EXCEPTION_EXIT_UNWIND. { PUSH EBP // Сохраняем значение регистра EBP. EBP = pRegistrationFrame->_ebp // Устанавливаем значение регистра EBP для // __local_unwind2 __local_unwind2( pRegistrationFrame, TRYLEVEL_NONE ) POP EBP // Восстанавливаем значение регистра EBP/ retvalue == DISPOSITION_CONTINUE_SEARCH; } }Рисунок 9. Псевдокод __except_handler3. Хотя вы увидите в __except_handler3 большое количество кода, помните, что это всего лишь callback-функция, вызываемая при возникновении исключения, подобная той, которую я описал в начале этой статьи. Она имеет четыре параметра, аналогичные тем, которые имеют изготовленные мной функции MYSEH.EXE и MYSEH2.EXE. На самом верхнем уровне, функция __except_handler3, с помощью оператора if, разбита на две части. Это сделано потому, что функция может быть вызвана в двух режимах: в обычном режиме (в этом режиме функция вызывается при возникновении исключения) и в режиме раскрутки (в этом режиме функция вызывается в процессе раскрутки). Большая часть функции предназначена работы в обычном режиме.
Сначала этот код создает в стеке структуру EXCEPTION_POINTERS, инициализируя ее двумя параметрами, переданными в __except_handler3. Адрес этой структуры, которую в псевдокоде я назвал: exceptPtrs, помещается по адресу [EBP-14]. Этим производится инициализация указателя, который используют функции GetExceptionInformation и GetExceptionCode.
Затем, __except_handler3 получает текущее значение trylevel из фрейма EXCEPTION_REGISTRATION (в [EBP-04]). Значение поля trylevel используется в качестве индекса в массиве scopetable, благодаря которому множество _try-блоков (в том числе, и вложенных) находящиеся в одной функции, используют одну и туже структуру EXCEPTION_REGISTRATION. Каждый элемент scopetable выглядит следующим образом:
typedef struct _SCOPETABLE { DWORD previousTryLevel; DWORD lpfnFilter DWORD lpfnHandler } SCOPETABLE, *PSCOPETABLE;Назначение второго и третьего поля в SCOPETABLE понять не сложно. Они содержат адреса кодов выражения-фильтра и соответствующего _except-блока. Понять назначение поля previousTryLevel немного сложнее. В общем, оно нужно для вложенных try-блоков. Важно, что для каждого _try-блока в функции существует один элемент в SCOPETABLE.
Как я уже говорил, текущий trylevel определяет используемый элемент в массиве scopetable. Этот элемент, в свою очередь, определяет адреса выражения-фильтра и _except-блока. Теперь рассмотрим сценарий, когда один _try-блок вложен в другой. Если выражение-фильтр внутреннего _try-блока решает что связанный с ним _except-блок не будет обрабатывать возникшее исключение, о нём должно быть проинформировано выражение-фильтр внешнего _try-блока. Каким образом __except_handler3 узнает, какой элемент SCOPETABLE соответствует внешнему _try-блоку? Индекс этого элемента содержится в поле previousTryLevel, принадлежащем элементу SCOPETABLE. Используя эту схему, вы сможете создать произвольное количество вложенных _try-блоков. Поле previousTryLevel используется как узел в связанном списке возможных обработчиков исключений в пределах функции. В последнем элементе этого списка, поле trylevel содержит значение 0xFFFFFFFF.
Вернемся к коду функции __except_handler3. После того, как текущий trylevel получен, она берет соответствующий элемент SCOPETABLE, и вызывает указанный в нем код выражения-фильтра. Если выражение-фильтр возвращает значение EXCEPTION_CONTINUE_SEARCH, функция __except_handler3 переходит к следующему элементу SCOPETABLE, индекс которого указан в поле previousTryLevel. Если в процессе перебора списка подходящий обработчик не найден, функция __except_handler3 возвращает значение DISPOSITION_CONTINUE_SEARCH, которое заставляет систему перейти к следующему фрейму EXCEPTION_REGISTRATION.
Если выражение-фильтр возвращает значение EXCEPTION_EXECUTE_HANDLER, значит, исключение должно быть обработано кодом соответствующего _except-блока. Значит, все предыдущие фреймы EXCEPTION_REGISTRATION должны быть удалены из списка, и должен быть выполнен код _except-блока. Первая из этих технических задач решается вызовом функции __global_unwind2, которую я опишу позже. После кода, совершающего очистку, который я пока проигнорирую, исполнение продолжается в _except-блоке. Странно, что управление, никогда не возвращается из _except-блока, несмотря на то, что __except_handler3 вызывает его с помощью инструкции CALL.
Как устанавливается текущее значение trylevel? Это делается компилятором, который “на лету” изменяет поле trylevel в расширенной структуре EXCEPTION_REGISTRATION. Если вы исследуете ассемблерный код, который был сгенерирован для функции, использующей SEH, то в различных местах функции вы увидите код, который изменяет текущее значение trylevel в [EBP-04].
Почему, несмотря на то, что функция __except_handler3 передает управление _except-блоку с помощью инструкции CALL, управление к ней никогда не возвращается? Так как операция CALL помещает адрес возврата в стек, вы наверно подумали, что код _except-блока портит стек. Если вы исследуете код сгенерированный для _except-блока, то увидите, что первым делом он загружает в регистр ESP значение (DWORD), которое находится в стеке на 8 байтов ниже структуры EXCEPTION_REGISTRATION. Как часть своего кода пролога, функция сохраняет значение ESP, чтобы блок _except мог получить его позже.
Программа ShowSEHFrames
Если сейчас вы чувствуете, что немного ошеломлены вещами наподобие EXCEPTION_REGISTRATIONs, scopetables, trylevels, выражениями-фильтрами и раскруткой, то скажу вам, что в начале я тоже прошел через это. Тему: “SEH на уровне компилятора”, нельзя изучить постепенно. По отдельности большая ее часть не имеет смысла, если вы не воспринимаете её всю как единое целое. Когда я сталкиваюсь с большим количеством теории я стараюсь писать код, использующий концепции, которые я изучаю. Если код работает, значит (обычно) что я все понял правильно.
На рис. 10 показан исходный код программы ShowSEHFrames.EXE. Она использует _try/_except-блоки, чтобы создать список из нескольких Visual C++ SEH-фреймов. Впоследствии, она отображает информацию о каждом фрейме, а также структуры scopetable, которые Visual C++ создаёт для каждого SEH-фрейма. Программа не вызывает и не ожидает никаких исключений. Я включил все _try-блоки для того, чтобы Visual C++ создал множество кадров EXCEPTION_REGISTRATION, с множеством элементов scopetable в них.
В ShowSEHFrames важны следующие функции: WalkSEHFrames и ShowSEHFrame. WalkSEHFrames сначала выводит на экран адрес функции __except_handler3 (причина, по которой это делается, станет ясна вам чуть позже). Затем, функция получает указатель на начало списка обработчиков исключений из FS:[0], и обходит каждый его узел. Все узлы имеют тип VC_EXCEPTION_REGISTRATION, который является структурой, которую я определил, чтобы описать Visual C++ SEH-фрейм. WalkSEHFrames передает в функцию ShowSEHFrame указатель на каждый узел в списке.
//================================================== // ShowSEHFrames - Мэт Питрек 1997 // Microsoft Systems Journal, Февраль 1997 // FILE: ShowSEHFrames.CPP // To compile: CL ShowSehFrames.CPP //================================================== #define WIN32_LEAN_AND_MEAN #include#include #pragma hdrstop //--------------------------------------------------------------------------- // !!! ВНИМАНИЕ !!! Эта программа будет компилироваться только в Visual C++, // т.к. использует специфичные для Visual C++ структуры данных. //--------------------------------------------------------------------------- #ifndef _MSC_VER #error Visual C++ Required (Visual C++ specific information is displayed) #endif //---------------------------------------------------------------------------- // Structure Definitions //---------------------------------------------------------------------------- // Структура данных, используемая в качестве основного SEH-фрейма, определенного // на уровне ОС. struct EXCEPTION_REGISTRATION { EXCEPTION_REGISTRATION* prev; FARPROC handler; }; // Структура (-ы) данных на которую указывает расширенный SEH-фрейм, используемый // на уровне Visual C++. struct scopetable_entry { DWORD previousTryLevel; FARPROC lpfnFilter; FARPROC lpfnHandler; }; // Расширенная структура данных, используемая в качестве SEH-фрейма, определенного // на уровне Visual C++. struct VC_EXCEPTION_REGISTRATION : EXCEPTION_REGISTRATION { scopetable_entry * scopetable; int trylevel; int _ebp; }; //---------------------------------------------------------------------------- // Prototypes //---------------------------------------------------------------------------- // __ except_handler3 - функция RTL Visual C++. Мы хотим обратиться к ней, // чтобы вывести на экран её адрес. Однако, т.к. она не представлена в заголовочных // файлах нам нужен ее псевдокод. extern "C" int _except_handler3(PEXCEPTION_RECORD, EXCEPTION_REGISTRATION *, PCONTEXT, PEXCEPTION_RECORD); //---------------------------------------------------------------------------- // Code //---------------------------------------------------------------------------- // // Отобразить на экране информацию об одном SEH-фрейме, наряду с его scopetable. // void ShowSEHFrame( VC_EXCEPTION_REGISTRATION * pVCExcRec ) { printf( "Frame: %08X Handler: %08X Prev: %08X Scopetable: %08X\n", pVCExcRec, pVCExcRec->handler, pVCExcRec->prev, pVCExcRec->scopetable ); scopetable_entry * pScopeTableEntry = pVCExcRec->scopetable; for ( unsigned i = 0; i <= pVCExcRec->trylevel; i++ ) { printf( " scopetable[%u] PrevTryLevel: %08X " "filter: %08X __except: %08X\n", i, pScopeTableEntry->previousTryLevel, pScopeTableEntry->lpfnFilter, pScopeTableEntry->lpfnHandler ); pScopeTableEntry++; } printf( "\n" ); } // // Обходим связанный список обработчиков, и по очереди отображаем информацию // о каждом из них. void WalkSEHFrames( void ) { VC_EXCEPTION_REGISTRATION * pVCExcRec; // Вывести на экран местоположение функции __ except_handler3. printf( "_except_handler3 расположен по адресу: %08X\n", _except_handler3 ); printf( "\n" ); // Получить указатель на начало списка из 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 ) { // Установить три вложенные _try-блоки (вследствие этого, появляется три // элемента scopetable). _try { _try { _try { WalkSEHFrames(); // Показать все SEH-фреймы. } _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(); // Вызываем функцию, которая установит побольше SEH-фреймов. } _except( EXCEPTION_EXECUTE_HANDLER ) { // Сюда управление передано не будет, т.к. мы не ожидаем исключения. printf( "Caught Exception in main\n" ); } return 0; } Рисунок 10. ShowSEHFrames.cpp В процессе своей работы функция ShowSEHFrame выводит на экран адрес SEH-фрейма, адрес callback-функции обработчика исключений, адрес предыдущего SEH-фрейма и указатель на scopetable. Затем, для каждого элемента scopetable, она выводит на экран предыдущий trylevel, адрес выражения-фильтра и адрес _except-блока. Каким образом я узнаю, сколько элементов содержится в scopetable? На самом деле, я этого не знаю. Скорее, я предполагаю, что текущий trylevel в структуре VC_EXCEPTION_REGISTRATION на единицу меньше чем общее количество элементов в scopetable.
На рис. 11 показаны результат выполнения программы ShowSEHFrames. Сначала, исследуйте каждую строчку, которая начинается с "Frame:". Обратите внимание, что каждый следующий показанный экземпляр SEH-фрейма находится в стеке выше предыдущего. Затем, обратите внимание, что первые три строки, начинающиеся с "Frame: ", имеют одно и то же значение в поле Handler (004012A8). Посмотрев в начало информации, выданной программой, вы увидите, что это значение (004012A8) соответствует адресу функции __except_handler3 находящейся в RTL Visual C++. Это доказывает сделанное мной ранее утверждение, что существует только одна точка входа для всех обработчиков исключений.
Рисунок 11. Результат работы программы ShowSEHFrames. У вас может возникнуть вопрос: почему программа показала три SEH-фрейма, использующих в качестве callback-обработчика функцию __except_handler3, в то время как в функции ShowSEHFrames явно указанны только две функции, использующие SEH? Третий фрейм устанавливается в RTL Visual C++. В коде находящемся в файле CRT0.C, который можно найти в исходниках RTL Visual C++, видно, что вызов функции main или WinMain обернут _try/_except-блоком. Код выражения-фильтра для этого _try-блока можно найти в файле WINXFLTR.C.
Вернемся к программе ShowSEHFrames, значение поля Handler в последней строчке начинающейся с "Frame:" содержит уже другой адрес - 77F3AB6C. Немного пошуровав вокруг, вы поймете, что этот адрес находится в KERNEL32.DLL. Этот специфический фрейм установлен, описанной мной ранее, функцией BaseProcessStart, которая находится в KERNEL32.DLL.
Из Microsoft Systems Journal. Январь 1997..
© Matt Pietrek / пер. Oleg_SK, SI
Win32™ SEH изнутри (ч.2)
Дата публикации 11 авг 2005