Централизованная обработка исключений (45809)

Посмотреть архив целиком

Централизованная обработка исключений

Беляев Алексей

Когда приложение перестает работать в офисе у разработчика, найти ошибку и исправить ее не составляет труда. Когда же приложение отказывает у клиента, то трудно найти общий язык с огорченным пользователем, и понять что ты сделал не так...

Введение

Что такое ошибка? Отвечая на этот вопрос кратко, можно сказать, что ошибка – это отклонение от описанного поведения. Для разработчика это означает, что необходимо искать и исправлять причину этого отклонения. Для программиста контроля качества ПО это означает, что необходимо доработать тесты и включить их в базовый цикл тестирования приложения. Для руководства это означает увеличение времени и затрат на разработку продукта.

Ошибки бывают разные, одни воспроизводятся легко, другие трудно. На поиск одних тратится немного времени, на поиск других уходят дни. Основная проблема поиска ошибки зачастую связана с недостатком информации по ее воспроизведению или состоянии приложения в момент возникновения ошибки. Если бы разработчик имел информацию о том, какая строчка программы содержит ошибку, ему не составило бы труда исправить ее.

Единственный способ избежать ошибок в программах – это писать код без ошибок. Но человек не может не делать ошибок, поэтому в любой программе они есть. Единственное, что разработчик может постараться сделать – это минимизировать количество ошибок, а также облегчить их поиск и исправление. В этой статье рассматривается способ, помогающий ускорить поиск и исправление ошибок.

Windows и необработанные исключения

Когда в приложении, работающем под управлением ОС Windows (от 9х до ХР), возникает необработанное исключение, операционная система обрабатывает его, создает dump-файл и записывает в него информацию, анализируя которую можно восстановить состояние приложения и быстро найти ошибку. К информации, которую сохраняет операционная система, относится:

информация о потоках;

информация о загруженных модулях;

информация об исключении;

информация о системе;

информация о памяти.

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

Каким образом Windows XP определяет, что в приложении произошло необработанное исключение? Ответить на этот вопрос можно, если разобраться с механизмом структурированной обработки исключений (SEH). Все версии Windows, начиная с версии Windows 95 и Windows NT, поддерживают этот механизм обработки исключений, позволяющий операционной системе и приложению тесно взаимодействовать в случае возникновения исключительной ситуации. И если в каком-либо приложении возникает необработанное исключение, операционная система обрабатывает его и завершает приложение.

Структурированная обработка ошибок

Структурированная обработка исключений (SEH) – это предоставляемый системой сервис, вокруг которого библиотеки современных языков программирования реализуют свои собственные функции для работы с исключениями.

C++-программисты наверняка знакомы с SEH в основном по использованию конструкций __try ... __except. Встретив в теле функции конструкцию __try … __except, компилятор, поддерживающий SEH, генерирует код для регистрации обработчика исключения. Затем, после возникновения исключения, операционная система ищет подходящий обработчик. Если подходящий обработчик не найден, операционная система создает dump-файл и завершает работу приложения.

Таким образом, перед нами стоит задача – сделать так, чтобы после возникновения в приложении необработанного исключения вызывался наш обработчик.

Для решения этой задачи необходимо выяснить, как операционная система ищет обработчик исключения. В поисках ответа на этот вопрос я углублялся в документацию по механизму структурированных исключений, анализировал системный ассемблерный код, смотрел, что генерирует компилятор, когда встречает конструкцию __try … __except, но подходящего решения не находилось. Ни в SDK, ни в DDK я не нашел ничего, что могло бы ответить на этот вопрос. Анализируя код, генерируемый компилятором для конструкции __try … __except, я увидел, что для каждого нового обработчика исключений в стеке создается запись, которая помещается в связанный список. Вот пример простой функции, который поможет понятнее объяснить это:

void foo()

{

__try

{

}

__except(1)

{

}

}

Код, который был сгенерирован компилятором VC 7.0:

void foo()

{

00411DE0 push ebp

00411DE1 mov ebp,esp

00411DE3 push 0FFFFFFFFh

00411DE5 push 424140h

00411DEA push offset @ILT+390(__except_handler3) (41118Bh)

00411DEF mov eax,dword ptr fs:[00000000h]

00411DF5 push eax

00411DF6 mov dword ptr fs:[0],esp

00411DFD add esp,0FFFFFF38h

00411E03 push ebx

00411E04 push esi

00411E05 push edi

00411E06 lea edi,[ebp-0D8h]

00411E0C mov ecx,30h

00411E11 mov eax,0CCCCCCCCh

00411E16 rep stos dword ptr [edi]

00411E18 mov dword ptr [ebp-18h],esp

__try

00411E1B mov dword ptr [ebp-4],0

00411E22 mov dword ptr [ebp-4],0FFFFFFFFh

00411E29 jmp $L19329+0Ah (411E3Bh)

{

}

__except(1)

00411E2B mov eax,1

$L19330:

00411E30 ret

$L19329:

00411E31 mov esp,dword ptr [ebp-18h]

00411E34 mov dword ptr [ebp-4],0FFFFFFFFh

{

}

}

00411E3B mov ecx,dword ptr [ebp-10h]

00411E3E mov dword ptr fs:[0],ecx

00411E45 pop edi

00411E46 pop esi

00411E47 pop ebx

00411E48 mov esp,ebp

00411E4A pop ebp

00411E4B ret

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

00411DE3 push 0FFFFFFFFh

00411DE5 push 424140h

00411DEA push offset @ILT+390(__except_handler3) (41118Bh)

00411DEF mov eax,dword ptr fs:[00000000h]

00411DF5 push eax

00411DF6 mov dword ptr fs:[0],esp

Вначале в стек кладется -1 (как оказалось впоследствии, просто резервируется место в стеке), а затем в стек записывается адрес статической переменной и адрес обработчика исключения. Если присмотреться к последним трем инструкциям, то можно увидеть, что из памяти по адресу fs:[0] считывается какое-то число, и кладется в стек, а на его место заносится текущий указатель стека. В принципе ничего подозрительного тут нет, но если расположить эти три инструкции последовательно несколько раз, то станет заметно, что они формируют связанный список, причем первый элемент этого списка всегда указывает на предыдущий элемент. На выходе из функции находится код, который восстанавливает предыдущее значение переменной по адресу fs:[0] :

00411E3B mov ecx,dword ptr [ebp-10h]

00411E3E mov dword ptr fs:[0],ecx

Таким образом, если функция имеет в себе конструкцию __try … __except, то компилятор создает в стеке запись о новом обработчике исключений и помещает информацию о ней в список обработчиков. Придя к такому выводу, я начал искать хоть какую-то информацию об обработчиках исключений и нашел публикацию, написанную Matt Pietrek-ом 7 лет назад (A Crash Course on the Depths of Win32 Structured Exception Handling). В этой статье описана структура SEH, и подтверждаются выводы, сделанные путем анализа кода приведенной выше функции. Изучив эту статью и проверив написанное в ней, я обнаружил, что с тех пор в области обработки исключений практически ничего не изменилось.

Из статьи следует, что по адресу fs:[0], находится начало связанного списка зарегистрированных обработчиков исключения, элементами которого являются структуры типа _EXCEPTION_REGISTRATION, расположенные в стеке.

struct _EXCEPTION_REGISTRATION

{

// указатель на следующую запись

_EXCEPTION_REGISTRATION *prev;

// обработчик исключения, созданный Runtime библиотекой

SEHHandler handler;

// указатель на структуру, описывающий блок __try…__except

PSCOPETABLE scopetable;

// уровень вложенности текущего блока try

int trylevel;

// указатель на следующую запись

int _ebp;

};

В этой структуре handler является процедурой обработки исключения. Прототип этой функции приведен ниже:

typedef int (*SEHHandler)(PEXCEPTION_RECORD, PEXCEPTION_REGISTRATION, PCONTEXT, void*);


Случайные файлы

Файл
60023.rtf
5249-1.rtf
175488.rtf
11884-1.rtf
124436.rtf




Чтобы не видеть здесь видео-рекламу достаточно стать зарегистрированным пользователем.
Чтобы не видеть никакую рекламу на сайте, нужно стать VIP-пользователем.
Это можно сделать совершенно бесплатно. Читайте подробности тут.