Welcome to The Passion Of Code Laboratory!!!Статьи

Win32 обработка исключений для программистов на ассемблере

Автор: Jeremy Gordon

Перевод: Bill Prisoner / TPOC

Введение

Мы собираемся исследовать, как сделать приложение более устойчивым (робастным), обрабатывая собственные исключения, вместо того чтобы позволить делать это операционной системе. «Исключение» - это нарушение совершенное программой, которое иначе привело бы к появлению страшного Message Box’а:

или что-то похожее в Windows 9x.

Что делает обработка исключений…

Идея относительно обработки исключительных ситуаций(часто называемой «Structured Exception Handling»(«Структурная обработка исключений»)) – то, что Ваше приложение устанавливает одно или несколько функций повторного вызова, которые называются – «обработчиками исключительных ситуаций», во время выполнения, и затем если исключение происходит система вызовет одну из функций-обработчиков, чтобы позволить приложению иметь дело и исключением. Можно надеяться на то, что обработчик исключительных ситуаций может быть способным восстановить исключение и продолжить выполняться или от той же команды где исключение произошло, или с какого-либо «безопасного места» в коде как будто ничего не случилось. Никакое окно с сообщением об ошибке не будет отображено. Как часть этого восстановления возможно необходимо закрыть описатели, закрыть временные файлы, свободные контексты устройств(device context), свободные области памяти, сообщить другим потокам об исключении, раскрутить(unwind) стек или закрыть поток вызвавший исключение. В течении этого процесса обработчик исключительных ситуаций может создать отчет о том что было сделано, и сохранить его в файл для дальнейшего анализа. Если нельзя ничего восстановить, обработка исключительных ситуаций позволяет Вашему приложению закрываться изящно, произведя очистку, сохраняя данные и извиниться.

Запланированные исключения

Windows SDK предлагает другое использование обработки исключительных ситуаций. Было предложено следить за использованием памяти. Идея – в том, что исключение произойдет, если Вы передали больше памяти: Вы прерываете это и выполняете распределение. Это может быть сделано прерывая нарушение доступа к памяти [номер 0C0000005h исключения], которое произошло, если Ваш код пробовал бы читать или писать в память, которая не была передана. Другое предложение – следить за использованием памяти, если установить флаг защиты страницы при вызове VirtualAlloc для передачи памяти, или более позднего использования VirtualProtect. Это вызвало бы исключение защиты страницы [080000001h], если была бы сделана попытка чтения или записи в охраняемую область памяти, для которой был поставлен соответствующий флаг. Обработчик исключительных ситуаций был бы в этом случае проинформирован относительно требуемых объемов и конфигураций памяти, и мог сбросить атрибуты защиты, если это необходимо. Эти методы широко используются всюду в операционной системе, например, если потоку требуется больший объем стека, он автоматически расширяется. Пользовательское приложение, однако, обычно знает, что надо сделать после исключения, так что это более проще и быстрее, чем следить за требуемыми объемами и конфигурациями памяти. Приложение, сохраняя вершину области памяти в переменную, проверяет перед каждой попыткой чтения или записи, должна ли память быть расширенна или уменьшена. Это работает, даже если больше чем один поток использует одну и ту же область памяти, так как та же самая переменная данных может использоваться локально в каждом потоке. В случае исключения с номером 0С0000005h обработчик мог бы сделать резервную копию, если бы выполнение пошло не так как надо.

Что не делает обработка исключений…

Кроме деления на ноль [код 0C0000094h исключения], которое можно избежать защитным кодированием, самым часто встречающимся типом исключений является попытка записи или чтения в незаконные адреса памяти. Есть несколько путей путей, при которых может возникнуть данная ситуация. Для примера:

· Неправильное значение индексного регистра, который адресует память;

· Неожиданные непрерывные циклы, в теле которых идет обращение к памяти;

· Несоответствие PUSH и POP, в результате чего выполнение продолжается из неправильного места после возвращения CALL;

· Непредвиденное искажение во входных файлах данных.

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

Другие отказы программ

Ваши программы могут терпеть неудачу по другим причинам, которые вообще не будут приводить к исключениям.

Обычно причины этому такие:

· Недостаточно системных ресурсов;

· Бесконечные циклы в программе которые не вовлекают доступ к памяти.

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

Совершенно фатальные исключения

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


…и где же обработка исключений действительно набирает очки
Потратя некоторое время на то, что обработка исключительных ситуаций не может сделать, давайте сделаем обзор, где это применимо:

·В течении разработки программы, чтобы захватить и сообщать об ошибках, как алтернатива средствам отладки;

·При использовании кода, который написан другими, которому нельзя полностью доверять;

·При чтении или записи в области памяти, которые могут быть перемещены без предупреждения. Например, во время просмотра системных областей памяти (которые были под системным управлением) или областей памяти, которые могли быть закрыты другими потоками или процессами;

·Использование указателей от файлов, которые могут быть разрушены или неправильного формата. Здесь обработка исключительных ситуаций намного более быстра чем использование IsBadReadPtr или IsBadWritePtr API, чтобы проверить каждый указатель до его использования;

·Как общий обработчик для всех непредвиденных ошибок.

Обработка исключений на практике

Последовательность действий


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

1. Windows решает сначала, является ли это исключением, которой нужно послать обработчику исключительных ситуаций. При этом если программа отлаживается, Windows уведомит отладчик, посылая EXCEPTION_DEBUG_EVENT(значение 1h).

2. Если программа не отлаживается или если с исключением не имеет дело отладчик, система посылает исключение обработчику особых ситуаций в потоке, если Вы его установили. Обработчик в потоке устанавливается во время выполнения и указывается первым dword’ом в TIB’е(Thread Information Block), адрес которого – FS:[0].

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

4. В конечном счете, если ни один из обработчиков потока не имеет дело с исключением, и если программа отлаживается, система снова остановит программу и уведомит отладчик.

5. Если программа не отлаживается или если с исключением все еще не имеет дело отладчик, система вызовет Ваш конечный обработчик, если он установлен. Это будет конечным обработчиком, установленным во время выполнения приложения, используя API функцию SetUnhandledExceptionFilter.

6. Если ваш конечный обработчик не имеет дело с исключением, то будет вызван системный обработчик. По умолчанию система показывает Message Box. В зависимости от параметров настройки системного реестра, это поле может дать пользователю шанс прикрепить отладчик к программе. Если никакой отладчик не может быть прикреплен или если отладчик бессилен помочь, программа обречена, и система вызовет ExitProcess, чтобы закончить программу.

7. Прежде, чем наконец, закончить программу, система вызовет «заключительную раскрутку»(«final unwind») стека потока, в котором произошло исключение.

Преимущества использования ассемблера для обработки особых ситуаций

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

Си-программисты будут использовать различные ключевые слова, обеспеченные их компиляторами, включая в исходный код текст типа _try, _except, _finally, _catch и _throw.

Один реальный недостаток в доверии коду компилятора – это то, что он может чрезвычайно увеличить размер конечного exe файла. Так большинство Cи программистов понятия не имеют, какой код произведен компилятором, когда используется SEH, и это - реальный недостаток, потому что для того чтобы обработать исключения должным образом Вы нуждаетесь в гибкости, понимании и управлении. Это важно для того, потому что исключения могут быть прерваны и обработаны различными способами и на различных уровнях в Вашем коде. Используя ассемблер, Вы можете сделать плотный, надежный и гибкий код, который Вы можете приспособить именно для Вашего приложения. Много потоковые приложения требуют особенной осторожности в обработке, и ассемблер обеспечивает простой и универсальный способ добавить обработку исключительный ситуаций таким программам. Информация об обработке исключительных ситуаций, на низком уровне сложна, чтобы овладеть ей, и примеры из SDK, переполнены примерами о том, как использовать Си-инструкции компилятора, а не то, как использовать Win32 структуры непосредственно. Информация в этой статье, была получена, используя тестовую программу и отладчик, а также дизассемблированный код сделанный Си-компиляторами. Сопроводительные программы, Except1.exe и Except2.exe, демонстрируют методы, описанные здесь.

Установка простых обработчик исключительных ситуаций

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

Два типа обработчиков исключительных ситуаций
Поскольку Вы видели заголовок, то Вы знаете, что есть два типа обработчиков исключительных ситуаций.

Тип 1 - конечный обработчик особых ситуаций

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

Установление конечного обработчика особых ситуаций

Как правило, он устанавливается в основном потоке как можно скорее после точки входа программы, вызывая API-функцию SetUnhandledExceptionFilter. Поэтому он охватывает целую программу от точки входа до завершения. Нет никакой потребности удалять обработчик после завершения - это будет сделано автоматически Windows.

Пример:
START: ;точка входа программы
PUSH ADDR FINAL_HANDLER
CALL SetUnhandledExceptionFilter
; ...
; ... ;код охваченный конечным обработчиком
; ...
CALL ExitProcess
;************************************
FINAL_HANDLER:
; ...
; ... ;код чтобы обеспечить вежливый выход
; ...
;(eax=-1 перегрузить контекст и продолжить)
MOV EAX,1 ;eax=1 выключает вывод Message Box’а
RET ;eax=0 включает вывод Message Box’а

Отсутствие формирования цепочки конечных обработчиков исключительных ситуаций

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

Тип 2 – обработка исключительных ситуаций в потоке

Этот тип обработчика обычно используется, чтобы сохранить некоторые области кода и устанавливается, изменяя значение, которое поддерживается системой в FS:[0]. Каждый поток в вашей программе имеет различное значение сегментного регистра, поэтому обработчик особых ситуаций будет определенным потоком. Он будет вызываться, если исключение происходит во время выполнения кода защищенного обработчиком. Значение в FS – 16 разрядный селектор, который указывает на TIB(Thread Information Block), структура, которая содержит важную информацию о каждом потоке. Самый первый DWORD указывает на структуру, которую мы собираемся называть структурой "ERR". Структура "ERR" – состоит по крайней мере из 2 двойных слов и определяется так:
 
1ый dword +0
Указатель на следующую ERR структуру

2ой dword +4
Указатель на обработчик исключния


Установка обработчика исключительных ситуаций потока

Теперь мы можем видеть, как просто установить данный тип исключения.

Пример:
PUSH ADDR HANDLER
FS PUSH [0] ; адрес следующей структуры ERR
FS MOV [0],ESP ;установить FS:[0] только сделанного адреса ERR
...
... ; код защищенный обработчиком, идет отсюда
...
FS POP [0] ; восстановить структуру ERR в FS:[0]
ADD ESP,4h ;выбросить остаток структуры ERR
RET
;***********************
HANDLER:
...
... ;код обработчика особых ситуаций идет отсюда
...
MOV EAX,1 ; eax=1 идет в следующий обработчик
; eax=0 перезагружает контекст и продолжает
RET

Формирование цепочки обработчиков особых ситуаций в потоке

В вышеупомянутом коде мы можем видеть, что 2-ой dword структуры ERR, который является адресом Вашего обработчика, помещен в стек сначала, тогда 1-ый dword следующей структуры ERR помещен в стек командой FS PUSH [0]. Тогда Вы можете создать другую структуру ERR и обработчик, чтобы защитить тот код точно тем же самым способом. Это называется формированием цепочки. Практически это означает, что, когда исключение происходит, система будет проходить цепочку обработчика первым вызывая обработчик особых ситуаций, последний раз установленный перед кодом, где исключение произошло. Если этот обработчик не имеет дело с исключением (возвращает EAX=1), то система вызывает следующий обработчик цепочки. Так как каждая структура ERR содержит адрес следующего обработчика в цепочке, любое количество таких обработчиков может быть установлено этим способом. Каждый обработчик мог бы принять меры или иметь дело со специфическими типами исключений в зависимости от того что является опасным в вашем коде. Стек используется, чтобы сохранить структуру ERR, и избежать перезаписи. Однако нет ничего, чтобы остановить Вас использовать другие части памяти для структур ERR, если Вы это предпочтете.

Раскрутка стека

В этом пункте мы собираемся рассмотреть раскручивание стека, потому что это больше нельзя держать это в тайне! "Раскручивание стека" - звуки, очень драматические, но практически это - просто все о вызове обработчиков особых ситуаций, локальные данные которых помещаются далее вниз стека и затем (вероятно) продолжение выполнения от другого фрейма стека. Другими словами программа подготавливается игнорировать содержание стека между этими двумя позициями.

Предположим, что Вы имеете цепочку обработчиков в потоке, установленных как на этом рисунке, где функция A вызывает функцию B, которая вызывает функцию C.



Тогда стек будет выглядеть подобно этому:

 

стекâ +ve

3ий стек фрейм

Используется стеком функции C

Обработчик 3

Локальные данные функции C

2ой стек фрейм

Адрес возврата функции C

Используется стеком функции B

Обработчик 2

Локальные данные функции B

1ый стек фрейм

Адрес возврата функции B

Используется стеком функции A

Обработчик 1

Локальные данные функции A

 

Адрес возврата функции A

 

Стекâ +ve

Здесь, для каждой функции элементы помещаются в стек: во-первых адрес возврата, также локальные данные, и затем обработчик особых ситуаций (это - структура "ERR", упомянутая ранее).

Тогда предположите, что исключение происходит в Функции C. Поскольку мы видели, система вызовет обход цепочки обработчика. Обработчик 3 будут называть вызван первым. Предположим, что Обработчик 3 не имеет дело с исключением (возвращает EAX=1), тогда вызван Обработчик 2. Предположим Обработчик 2 также возвращения EAX=1 так, чтобы был вызван Обработчик 1. Если Обработчик 1 имеет дело с исключением, возможно, нужно сделать очистку локальных данных в стековых фреймах, созданных Функциями B и C. Это может сделать так, вызывая Раскручивание(unwind). Этот механизм просто снова делает обход цепочки, вызывая Обработчик 3, потом Обработчик 2, Обработчик 1 должен быть вызван в свою очередь.

Различия между этим типом обхода цепочки обработчика и обхода, инициализированного системой, когда исключение сначала произошло – в следующем:

1. Этот обход обработчика инициализирован Вашим обработчиком, а не системой.

2. Флажок исключения в EXCEPTION_RECORD должен быть установлен в 2h (EH_UNWINDING). Этот флаг указывает обработчику в потоке, что он должен вызвать другой обработчик выше в цепочке, чтобы очистить используемые локальные данные. Он не должен делать ничего кроме этого и он должнен возвратить EAX=1.

3. Обход обработчика останавливается немедленно перед обработчиком, который это затеял. Например, на рисунке, если Обработчик 1 инициирует раскручивание, последний Обработчик, который будет вызван в течение раскручивания - Обработчик 2. Нет никакой потребности в Обработчике 1, чтобы вызывать себя, потому что он имеет доступ к своим собственным местным данным, для очистки.

Вы можете видеть ниже ("Обеспечение доступа к локальным данным"), как обработчик способен найти локальные данные в течение обхода обработчика.

Как сделать раскручивание

Обработчик может инициировать раскручивание используя API-функцию RtlUnwind или, как мы увидим дальше, это может также легко быть сделано, используя ваш собственный код. Этот API можно вызвать так:
 

PUSH Return value
PUSH pExceptionRecord
PUSH ADDR CodeLabel
PUSH LastStackFrame
CALL RtlUnwind

,где

Return value – возвращаемое значение после раскручивания(вы вероятно не будете это использовать)

pExceptionRecord – указатель на структуру исключения, которая является одной из структур посланных обработчику когда исключение произошло

CodeLabel – место от которого должно продолжиться выполнение после раскручивания и обычно адрес кода после вызова RtlUnwind. Если это значение не определено, функция, кажется, возвращается нормальным способом, однако SDK предлагает, использовать этот параметр, и лучше играть безопасно с этой функцией.

LastStackFrame – стек фрейм, на котором раскрутка должна остановиться. Обычно это адрес стека структуры ERR, которая содержит адрес обработчика, который инициализирует раскрутку.

В отличие от других API Вы не можете положиться на RtlUnwind в сохранении регистров EBX, ESI или EDI - если Вы используете их в Вашем коде, Вы должны гарантировать, что они сохранены помещения первого параметра в стек и восстановлены после CodeLabel.

Раскрутка собственным кодом

Следующий код моделирует раскручивание (где ebx содержит адрес структуры EXCEPTION_RECORD, посланной обработчику):

MOV [EBX+4],2h ;сделать флагом исключения EH_UNWINDING
FS MOV EDI,[0] ;получить адрес первого обработчика потока
L2: ;
CMP [EDI],-1 ;смотрим, является ли это последним
JZ L3 ;если да, то конец
PUSH EDI,EBX ;push структуры ERR и EXCEPTION_RECORD
CALL [EDI+4] ;вызвать обработчик для очистки
ADD ESP,8h ;выкинуть два параметра из стека
MOV EDI,[EDI] ;получить указатель на следующую структуру ERR
JMP L2 ;делать дальше если не конец
L3: ;

Здесь каждый обработчик вызывается с установленным флагом в 2h, пока последний обработчик не будет достигнут (система имеет значение -1 в последней структуре ERR). Вышеупомянутый код не проверяет искажение значений в [EDI] и в [EDI+4]. Первый – адрес стека и мог быть проверен, гарантируя, что он является выше базы стека потока, находящегося в FS:[8] и ниже вершины стека потока, находящегося в FS:[4]. Второй - адрес кода и так что Вы могли бы проверить, что он находится в пределах двух меток кода, один в начале вашего кода и один в конце него. Альтернативно Вы могли проверить [EDI] и [EDI+4] мог читаться, вызывая API-функцию IsBadReadPtr.

Раскручивание стека конечным обработчиком и продолжение

Это не просто обработчик в потоке, который инициировал раскрутку. Это может также быть сделано в Вашем конечном обработчике, вызывая RtlUnwind или собственный код раскручивает и возвращает EAX=-1. (См. "Продолжение выполнения после вызова конечного обработчика").

Финальная раскрутка и завершение работы

Если конечный обработчик установлен, и он возвращает или EAX=0 или EAX=1, то система заставит процесс уничтожиться. Однако, перед конечным завершением случается кое-что интересное. Система делает конечную раскрутку, возвращаясь на самый первый обработчик в цепочке (то есть обработчик, охранявший код, в котором произошло исключение). Это самая последняя возможность Вашего обработчика выполнить очистку, необходимую в пределах каждого стекового фрейма. Вы можете видеть эту финальную раскрутку, при установке демонстрационной программы позволяя исключению проходить до конечного обработчика и нажимать там F3 или F5. Это также случается в более простой программе Except1.exe.

Информация, посылаемая обработчикам

Нужно посылать обработчикам достаточно информации, чтобы они были способны пробовать восстановить исключение. В дополнение к этому Вы можете послать вашу собственную информацию обработчикам, расширяя структуру ERR так, чтобы она содержала подробную информацию.

Информация, посылаемая конечному обработчику

Конечный обработчик документирован в SDK как API-функция UnhandledExceptionFilter. Она получает только один параметр, указатель на структуру EXCEPTION_POINTERS. Эта структура выглядит так:

 

EXCEPTION_POINTERS   +0

Указатель на структуру:
EXCEPTION_RECORD

+4

Указатель на структуру:
CONTEXT record

Структура EXCEPTION_RECORD состоит из следующих полей

EXCEPTION_RECORD  +0

ExceptionCode

                                             +4

ExceptionFlag

                                             +8

NestedExceptionRecord

                                             +C

ExceptionAddress

                                             +10

NumberParameters

                                             +14

AdditionalData

где,
ExceptionCode дает тип исключения, которое произошло. Их много, они перечислены в SDK и в заголовочных файлах, но практически, типы, на которые Вы можете натолкнуться такие:

C0000005h – Нарушение чтения или записи памяти

C0000094h – Деление на ноль

C0000095h – Переполнение деления

C00000FDh – Стек расширился больше чем его максимальное значение

80000001h – Нарушение сторожевой страницы в памяти, установленной с помощью функции VirtualAlloc

Следующие происходят только при обработке исключения:

C0000025h – Исключение без продолжения – обработчик не должен пробовать иметь дело с этим исключением

C0000026h – код исключения, используется системой в течении обработки особых ситуаций. Этот код мог бы использоваться, если система сталкивается с неожиданным возвращением от обработчика. Это также используется, если нет записи об исключении при вызове RtlUnwind.

Эти используются при отладке:
80000003h – остановка на контрольной точке , потому что в коде было встречено int3.
80000004h – один шаг в течении отладки
 

Own user Code – это ислючение послало бы Ваше собственное приложение, вызывая API-функцию RaiseException. Это - если требуется, быстрый путь к коду завершения непосредственно в Вашем обработчике.

Exception flag, который дает команды обработчику. Значения могут такими:

0 – исключение с продолжением (может быть восстановлено)

1 – исключение без продолжения (cannot be repaired)

2 – раскрутка стека – восстановление не требуется

Nested exception record, указывает на другую структуру EXCEPTION_RECORD, если сам обработчик вызвал другое исключение.

Exception address - адрес в коде, где исключение произошло.

NumberParameters – количество dword’ов для дополнительной информации

Additional information – массив dword’ов для дополнительной информации

Это может быть информация, посланная приложением непосредственно при запросе RaiseException, или, если код исключения - C0000005h, она будет такая:

1-ый dword - 0=нарушение чтения памяти, 1=нарушения записи в память

2-ой dword - адрес нарушения прав доступа

Вторая часть структуры EXCEPTION_POINTERS, которую посылают конечному обработчику, указывает на структуру записей CONTEXT, которая содержит значения всех регистров для определенного процессора во время исключения. WINNT.H содержит структуры CONTEXT для различных процессоров. Ваша программа может узнать, какой процессор используется, вызывая GetSystemInfo. CONTEXT определяется следующим образом для IA32 (Intel 386 и выше):
 
+0 context flags
(используется при вызове GetThreadContext)
Отладочные регистры
+4 debug register #0
+8 debug register #1
+C debug register #2
+10 debug register #3
+14 debug register #6
+18 debug register #7
Плавающая точка / MMX регистры
+1C ControlWord
+20 StatusWord
+24 TagWord
+28 ErrorOffset
+2C ErrorSelector
+30 DataOffset
+34 DataSelector
+38 FP registers x 8 (10 bytes each)
+88 Cr0NpxState
Сегментные ргистры
+8C gs register
+90 fs register
+94 es register
+98 ds register
Индексные регистры
+9C edi register
+A0 esi register
+A4 ebx register
+A8 edx register
+AC ecx register
+B0 eax register
Управляющие регистры

+B4 ebp register
+B8 eip register
+BC cs register
+C0 eflags register
+C4 esp register
+C8 ss register

Информация, посылаемая обработчику потока

Во время вызова обработчика потока, ESP указывает на 3 структуры определенные так:

ESP+4

Указатель на структуру
EXCEPTION_RECORD

ESP+8

Указатель на собственную структуру ERR

ESP+C

Указатель на структуру  
CONTEXT

 

В отличие от функций обратного вызова в Windows, когда обработчик в потоке вызывают, соглашение о вызовах в Си(вызывающий удаляет аргументы из стека) не совпадает с соглашением о вызовах в Паскале. Это может быть замечено в коде Kernel32, который обычно вызывает так:

 

PUSH Param, CONTEXT record, ERR, EXCEPTION_RECORD

CALL HANDLER

ADD ESP,10h

На практике первый аргумент обычно не содержит важную информацию.


Структуры EXCEPTION_RECORD и CONTEXT были описаны выше.

Структура ERR - структура, которую Вы создавали в стеке, когда обработчик был установлен, и она должна содержать указатель на следующую структуру ERR и адрес кода устанавливаемого обработчика (см. "Установка простых обработчиков особых ситуаций ", выше). Указатель на структуру ERR передается обработчику потока на вершине этой структуры. Поэтому, можно увеличивать структуру ERR так, чтобы обработчик мог получать дополнительную информацию. В обычном виде структура ERR могла бы напомнить это, где [ESP+8h] указывает на вершину этой структуры, когда обработчик был вызван:

ERR +0

Указатель на следующую структуру ERR

+4

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

+8

Адрес в коде"безопасного место" для обработчика

+C

Информация для обработчика

+10

Место для флагов

+14

Значение EBP для безопасного место

Как мы будем видеть ниже ("Продолжение выполнения от безопасного места"), поля в +8 и +14 могут использоваться обработчиком, чтобы оправиться от исключения.

Обеспечение доступа к локальным данным

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

MYFUNCTION: ; точка входа процедуры

PUSH EBP ; сохранить ebp(используется для адреса стекового фрейма)
MOV EBP,ESP ;использовать EBP как указатель на стековый фрейм
SUB ESP,40h ;создать16 dword’ов стеке для локальных данных
;******** локальные данные теперь в

;********** установленном обработчике

PUSH EBP ;ERR+14h сохраняет ebp
PUSH 0 ;ERR+10h область для флагов
PUSH 0 ;ERR+0Ch информация для обработчика
PUSH ADDR SAFE_PLACE ;ERR+8h новый eip на безопасное место
PUSH ADDR HANDLER ; ;ERR+ 4h адрес обработчика
FS PUSH [0] ;ERR+ 0h сохраняет следующую структуру ERR в цепочке
FS MOV [0],ESP ;указатель на ERR только сделанную в стеке
...
... ; код, который защищен, идет сюда

...
JMP >L10 ; нормальный конец, если нет никакого исключения
SAFE_PLACE: ; обработчик устанавливает здесь eip/esp/ebp

L10:
FS POP [0] ;восстановить следующую ERR структуру выше в цепочке

MOV ESP,EBP
POP EBP
RET
;*****************
HANDLER:
RET


Используя этот код когда обработчик вызывают, следующее находиться в стеке, и указатель [ESP+8h] указывает на вершину структуры ERR(то есть на ERR+0):

 

стекâ +ve

ERR +0

Указатель на следующую структуру ERR

ERR +4

Указатель на собственный обработчик исключений

ERR +8

Адрес в коде «безопасного места» для обработчика

ERR +C

Информация для обработчика

ERR +10

Место для флагов

ERR +14

Значение EBP в безопасном месте

+18

Локальные данные

+1C

Локальные данные

+20

Локальные данные

 

Больше локальных данных â



Вы видеть можете здесь, что, так как обработчику дают указатель на структуру ERR, он может также найти адрес локальных данных относительно стека. Это происходит потому, что обработчик знает размер структуры ERR и также позиции локальных данных относительно стека. Поле EBP используется в ERR+14h как в вышеупомянутом примере, но оно могло бы также использоваться как указатель на локальные данные.

Восстановление исключений

Продолжение выполнения от безопасного места

Выбор безопасного места

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

Другое главное соображение состоит в том, как простой можно получить правильные значения EIP, ESP и EBP в безопасном месте. Как мы увидим ниже это не будет сложным.

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

Пример того, как добраться до безопасного места

Как пример, тем не менее, снова смотрите на пример кода выше в MYFUNCTION. Вы можете видеть метку в коде "SAFE-PLACE". Это - адрес в коде, от которого выполнение могло продолжиться безопасно, и обработчик может выполнить очистку. В примере кода, чтобы продолжить успешно выполнение нужно создать в памяти безопасное место, которое находиться в пределах стекового фрейма если исключение произошло, значения ESP и EBP нужно тщательно восстановить перед продолжением выполнения с EIP. Эти три регистра должны быть установлены по следующим причинам:

ESP – чтобы использовать инструкции FS POP [0] или POP по другим адресам

EBP - чтобы гарантировать, что к локальным данным можно обратиться в пределах обработчика, и восстановить правильное значение ESP, чтобы возвратиться из MYFUNCTION.

EIP – чтобы продолжить выполнение от безопасного места

Теперь Вы можете видеть, что каждое из этих значений с доступно внутри функции обработчика. Правильное значение ESP фактически совпадает со значение вершины ERR(из [ESP+8h]) при вызове обработчика. Правильное значение EBP доступно из ERR+14h, потому что оно было помещено в стек при создании структуры ERR. Правильный адрес кода безопасного места, чтобы поместить его в EIP, находиться в ERR+8h. Теперь мы можем видеть, как обработчик может гарантировать продолжение выполнения от безопасного места, вместо того, чтобы позволить процессу закрыться при возникновении исключения.

HANDLER:
PUSH EBP
MOV EBP,ESP
;** сейчас [EBP+8]=указатель EXCEPTION_RECORD
;** [EBP+0Ch]=указатель ERR структуру
;** [EBP+10h]= указатель на структуру CONTEXT
PUSH EBX,EDI,ESI ;сохраняем регистры которые использует Windows
MOV EBX,[EBP+8] ;получить запись об исключении в ebx
TEST D[EBX+4],1h ;смотреть исключение без продолжения
JNZ >L5 ;если да, то не должен иметь дело с этим
TEST D[EBX+4],2h ;смотреть не раскрутка ли это
JZ >L2 ;нет
...
...      ;если раскрутка, то очищаем
...
JMP >L5 ;нет
L2:
PUSH 0 ;возвращаемое значение(не используется)
PUSH [EBP+8h] ;указатель на запись исключения
PUSH ADDR UN23 адрес в коде для возврата RtlUnwind
PUSH [EBP+0Ch] ;указатель на структуру ERR
CALL RtlUnwind
UN23:
MOV ESI,[EBP+10h] ;получить в ESI структуру CONTEXT
MOV EDX,[EBP+0Ch] ;получить указатель на структуру ERR
MOV [ESI+0C4h],EDX ;использовать новый ESP
MOV EAX,[EDX+8];безопасное место в структуре ERR
MOV [ESI+0B8h],EAX ;вставить новый EIP
MOV EAX,[EDX+14h] ;получить EBP для безопасного места
MOV [ESI+0B4h],EAX ;вставить новый EBP
XOR EAX,EAX ;перезагрузить контекст и возвратить системе eax=0
JMP >L6 ;идти в следующий обработчик – в eax=1
L5: ;обычный возврат(не требует параметров)
MOV EAX,1
L6:
POP ESI,EDI,EBX
MOV ESP,EBP
POP EBP
RET

Восстановление исключения

Если вы желаете, то Вы можете иметь дело с исключением в конечном обработчике. Об этом вызове я рассказывал в начале этой статьи, заключительный обработчик вызывает система, перед тем как завершить процесс.

Это истина.

Возращенное значение в заключительном обработчике из EAX, не тоже самое, что в обработчиках потока. Если возращенное значение EAX=1, то процесс завершается система не показывает Message Box, а если EAX=0, то показывает. Есть еще третье значение EAX=-1, которое описывается в SDK как EXCEPTION_CONTINUE_EXECUTION. Это возвращаемое значение имеет тот же самый эффект, что и при возвращении EAX=0 обработчиком потока, то есть при этом перезагружается структура CONTEXT в процессоре и продолжается выполнение от eip, данного в контексте. Конечно, конечный обработчик может изменить структуру CONTEXT перед возвратом к системе, тем же самым способом, поскольку обработчик поток делает так. Этим способом конечный обработчик может оправиться от исключения, продолжая выполнение от подходящего безопасного места, или он может попробовать восстановить исключение.

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

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

PUSH ADDR FINAL_HANDLER
CALL SetUnhandledExceptionFilter ;сохранить адрес предыдущего обработчика

PUSH EAX
...
... ;этот код будет защищен
...
CALL SetUnhandledExceptionFilter ;восстановить предыдущий обработчик

Обратите внимание, что во время второго вызова SetUnhandledExceptionFilter адрес предыдущего обработчика находится уже в стеке из-за команды PUSH EAX которая находиться ранее. Другая трудность с использованием конечного обработчика состоит в том, что информация, посланная ему ограничена записью об исключении и о контексте. Поэтому Вы должны сохранить адрес кода безопасного места, и значений ESP и EBP в том безопасном месте, в статической памяти. Это может быть сделано легко во время выполнения. Например, когда имеешь дело с сообщением WM_COMMAND - сообщением в пределах оконной процедуры,

PROCESS_COMMAND: ;обращаемся сюда при uMsg=111h
MOV EBPSAFE_PLACE,EBP ;сохранить ebp в безопасном месте
MOV ESPSAFE_PLACE,ESP ;сохранить esp в безопасном месте
...
... ; здесь защищенный код
...
SAFE_PLACE: ; метка кода для безопасного места
XOR EAX,EAX ;возвратить eax=0=сообщение обработано
RET


В примере выше, чтобы восстановить исключение, продолжая выполнение от безопасного места, обработчик вставил бы значения EBPSAFE_PLACE в CONTEXT+0B4h (ebp), ESPSAFE_PLACE в CONTEXT+0C4h (esp), и ADDR SAFE_PLACE в CONTEXT+0B8h (eip) и затем возвратил бы -1.

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

Выполнение в пошаговом режиме, устанавливая trap-флаг в обработчике

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

SS_HANDLER:
PUSH EBP
MOV EBP,ESP
PUSH EBX,EDI,ESI ;сохранить регистры требуемые Windows
MOV EBX,[EBP+8] ;получить запись об исключении в ebx
TEST D[EBX+4],01h ;проверить если это исключение без продолжения
JNZ >L14 ;да
TEST D[EBX+4],02h JNZ >L14 ;посмотреть если это EH_UNWINDING
MOV ESI,[EBP+10h] ;получить контекстную запись в esi
MOV EAX,[EBX] ;получить ExceptionCode
CMP EAX,80000004h ;посмотреть не установлен ли trap-флаг
JZ >L10 ;да
CMP EAX,80000003h ;посмотреть не был ли вставлен его INT3 для одно шага
JNZ >L14 ;нет
L10:
DEC D[SSCOUNT] ;остановиться если сформирован правильный номер
JZ >L12

OR D[ESI+0C0h],100h ;установить trap-флаг в контексте
L12:
...
... ;код здесь выводит результаты на экран
...
XOR EAX,EAX ; eax=0 перезагрузить контекст и возвратиться к системе

JMP >L17
L14:
MOV EAX,1 ;eax=1 система идет к следующему обработчику
L17:
POP ESI,EDI,EBX
MOV ESP,EBP
POP EBP
RET

 

Здесь первый запрос на обработчик вызван INT 3(система была против использования INT1, когда я использовал это). При получении этого исключения, которое мог только вызвать кодовый фрагмент, вставленный в тестовую программу, обработчик устанавливает trap-флаг в контексте перед возвращением. Это заставляет, после генерации 80000004-го исключения, после следующей команды возвращаться на обработчик. Обратите внимание, что с этими исключениями, eip - уже установлен на следующую инструкцию, то есть один прошлый INT 3 или последняя команда выполненная с trap-флагом. Соответственно все, что Вы должны сделать в обработчике, чтобы продолжить выполнение в пошаговом режиме, это установить trap-флаг снова и возвратиться к системе.

* Благодарность G.W.Wilhelm, Младшему из IBM за эту идею
 

Обработка особых ситуаций в многопоточных приложениях

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

Правила относящиеся к обработке особых ситуаций, обеспеченной системой (в контексте многопоточного приложения) такие:

Только один тип 1 (конечный обработчик) может быть уже существовать в любой момент для каждого процесса. Если новый поток вызывает SetUnhandledExceptionFilter, это просто заменит конечный обработчик - нет никакой цепочки конечных обработчиков как есть для типа 2 (потоковые обработчики) обработчика. Поэтому самый простой способ, это использовать конечный обработчик - все еще вероятно лучший путь в многопоточном приложении - устанавливают его в основном потоке как можно скорее после точки начала программы.

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

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

Поэтому другие (невинные) потоки не могут ожидать конечную раскрутку, если процесс должен закончиться. И они останутся неосведомленными о неизбежном завершении.

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

Путь, с помощью которого невинные потоки будут информированы относительно ожидаемого завершения программы, зависит от точного характера Вашего кода. Если невинный поток имеет окно и цикл сообщения, то конечный обработчик может использовать SendMessage к тому окну, чтобы послать приложению, определенное сообщение (должно быть 400 или выше), сообщить потоку, чтобы он закончился изящно. Если нет никакого окна и цикла сообщения, конечный обработчик мог бы установить глобальный переменный флаг, опрашиваемый время от времени другим потоком. Альтернативно Вы могли использовать SetThreadContext, чтобы вынудить поток выполнять некоторый код завершения, устанавливая значение eip, чтобы указать на тот код. Этот метод не работал бы, если поток находится в API, например, ждущий возвращения от GetMessage. В этом случае Вы были бы должны послать сообщение, чтобы поток, возвратился бы из API, так, чтобы установить новый контекст.

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

Как конечный обработчик узнает, когда он может продолжиться после информирования другим потокам, которые программа собирается заканчивать? SendMessage не будет возвращаться, пока получатель не возвратился из его оконной процедуры, и конечный обработчик может ждать этого возвращения. Альтернативно он мог опрашивать флаг, и таким образом ждать ответа от другого потока, который закончил очистку (обратите внимание, что Вы должны вызывать API-функцию Sleep в цикле опроса, чтобы избежать замедления системы). Или лучше чтобы, конечный обработчик мог ждать, пока другой поток не закончится (это может быть сделано, используя API-функцию WaitForSingleObject или WaitForMultipleObjects, если есть больше чем один поток). Альтернативно можно использовать функции API для Семафоров и Событий.

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

Если связь между потоками трудна, можно иначе одному потоку обратиться к стеку другого потока, и таким образом вызвать раскрутку. Этот процесс использует факт, что каждый поток имеет свой собственный стек, память, зарезервированная для стека - в пределах одного адресного пространства для процесса непосредственно. Вы можете проверить это непосредственно, если Вы наблюдаете многопоточное приложение, используя отладчик. Поскольку Вы двигаетесь между потоками, значения ESP и EBP изменятся, но они все сохраняются в пределах адресного пространства процесса непосредственно. Значение FS также будет различно между потоками и укажет на TIB для каждого потока. Так, если Вы делаете следующие шаги, один поток может обратиться к стеку и вызвать раскручивание другого:

a. Поскольку каждый поток создает запись в статической переменной соответствуя значению его регистра FS.

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

c. Обработчик, который должен раскрутить другие потоки, должен взять все статические переменные, в свою очередь, и для тех, которые имеют ненулевое значение (то есть поток выполнялся во время исключения), обработчики нужно вызвать с флагом исключения 2 (EH_UNWINDING), и, пользовательским флагом, который нужен, чтобы показать ему, что обработчик потока вызывает Ваш конечный обработчик. Вы не можете вызвать обработчик потока в различных потоках, используя RtlUnwind (который является определенным потоком), но это может быть сделано, используя следующий код (где ebx содержит адрес EXCEPTION_RECORD):
 

MOV D[EBX+4],402h ; создать флаг исключения EH_UNWINDING + 400h
L1:
PUSH ES
MOV AX,[FS_VALUE] ;получить значение FS потока для раскутки
MOV ES,AX
ES MOV EDI,[0] ;получить адрес первого обработчика потока
POP ES
L2:
CMP D[EDI],-1 ;посмотреть если это последний
JZ >L3 ;да, заканчиваем
PUSH EDI,EBX ;push ERR структуру, EXCEPTION_RECORD
CALL [EDI+4] ;вызвать обработчик для запуска кода очистки
ADD ESP,8h ;удалить два положенных в стек параметра
MOV EDI,[EDI]; получить указатель на следующую структуру ERR
JMP L2;и делать следующее, если не закончили
L3: ;кодовая метка при завершении
; теперь цикл возвращается назад к L1 с новым значением FS_VALUE если не все потоки обработаны


Здесь Вы видите, что Thread Information Block каждого невинного потока читается, используя регистр ES, которому временно дают значение регистра FS потока. Вместо того, чтобы использовать FS, чтобы найти TIB Вы могли использовать следующий код, чтобы получить 32-разрядный линейный адрес для него. В этом коде LDT_ENTRY - структура из 2 dword’ов, в ax кладется 16-разрядное значение селектора(FS_VALUE) которое должно быть преобразованно, и hThread - любой правильный описатель потока:

AND EAX,0FFFFh
PUSH ADDR LDT_ENTRY,EAX,[hThread]
CALL GetThreadSelectorEntry
OR EAX,EAX ;смотреть если ошибка
JZ >L300 ;да, возвратился ноль
MOV EAX,ADDR LDT_ENTRY
MOV DH,[EAX+7] ;получить старшую часть базы
MOV DL,[EAX+4] ;получить среднюю часть базы
SHL EDX,16D ;сдвинуть edx
MOV DX,[EAX+2] ; edx теперь 32-битный линейный адрес
OR EDX,EDX
L300:;возвратить не ноль если успешно


Причина почему важно (использование флага 400h) сообщить вызываемому обработчику, что его вызывает другой поток (конечный обработчик), состоит в том, что вызываемый поток все еще выполняется, потому что исключение произошло в отличном потоке. Обработчик вполне может приостановить поток в этих обстоятельствах, так, чтобы задание очистки могло быть достигнуто вызывающим потоком. Невинному потоку тогда дали бы безопасное место, чтобы идти до вызова ResumeThread. Все это должно быть сделано перед конечным обработчиком, который позволяет возвратиться к системе, потому что по возвращению система просто закончит все потоки решением "в лоб".


Except1

Эта программа обеспечивает простой пример того, как обработка особых ситуаций может использоваться практически в программах Windows, написанных на ассемблере. Исходный текст содержится в Except1.asm. Это написано на синтаксисе GoAsm. Хотя программа – GDI программа Windows, она только полагается на Message Box’ы, поэтому нет никакого цикла сообщений.

Программа имеет два обработчика особых ситуаций, конечный обработчик особых ситуаций обработчик особых ситуаций потока. Конечный обработчик особых ситуаций создан сначала, затем вызывают процедуру, которая находится в коде, защищенном обработчиком особых ситуаций потока. Исключение происходит в пределах этой процедуры, и вызывается обработчик потока. В пределах обработчика, пользователя спрашивают, должен ли обработчик глотать исключение или нет. Если бы пользователь решает глотать исключение, программа была бы способна продолжить работать, но фактически в этом случае все заканчивается как обычно. Если пользователь решает, что исключение нельзя глотать обработчиком, то вызывается конечный обработчик особых ситуаций (один из способов закрытия программы). В реальной жизни, этот обработчик был бы ответственен за завершение log-файлов и записей, закрытие описателей файла, освобождая память и т.д. Но прежде, чем программа наконец закончится, кое-что интересное случается. Система вызывает обработчик особых ситуаций потока для очистки, используя стековый фрейм. Это система делает раскрутку. Все эти события сопровождаются окнами с информацией о проделанной работе.

Except2

Это - более сложная программа, которая предназначена, чтобы демонстрировать более подробно содержание этой статьи. Исходный текст для Except2.Exe (Except2.asm и Except2.RC) также доступен и он снова в GoAsm синтаксисе. Основное окно - фактически модальный диалог. Конечный обработчик устанавливается очень рано в процессе. Когда кнопка "Cause Exception" нажата, сначала вызывают диалоговую процедуру с командой, для дальнейшего вызова 2 подпрограмм, третья подпрограмма, вызывает исключение типа, выбранного переключателями. При выполнении этого кода создаются 3 обработчика особых ситуаций.

Происходит восстановление на месте если возможно, или программа отправляется в выбранном обработчике на безопасного места. Если исключению позволяют идти в конечный обработчик, Вы можете или выйти, нажимая F3 или F5, или если Вы нажимаете F7, конечный обработчик будет пробовать оправиться от исключения.

Вы можете следить за событиями когда они происходят, потому что каждый обработчик отображает различные сообщения в окне списка. Есть небольшая задержка между каждым сообщением так, чтобы Вы могли следить более легко, или Вы можете листать сообщения, чтобы прочитать их. Когда программа собирается завершаться, случается кое-что интересное. Система вызывает конечную раскрутку с флагом исключения установленным в 2h. Сообщения, посланные окну списка замедлены даже при этом, потому что программа скоро будет закончена! Вы будете видеть, что тот же самый тип раскрутки, происходит, если Вы определяете, что выполнение должно продолжиться от "безопасного места" или если нажата клавиша F7 от конечного обработчика. Это раскрутка и инициализация обработчиком непосредственно.

COPYRIGHT NOTE - this article, Except1.asm, Except2.asm, Except2.RC and Except2.Exe are all
Copyright © Jeremy Gordon 1996-2002,
[McDuck Software]
e-mail: JG@JGnet.co.uk
http://www.GoDevTool.com
LEGAL NOTICE - автор не принимает никакой ответственности за потери любого типа, являющегося результатом этой статьи. Принимая во внимание, что автор старался гарантировать, что содержание этой статьи правильно, Вы не должны полагаться на это, и Вы должны проделывать Ваши собственные испытания.

[C] Jeremy Gordon, пер. Bill Prisoner / TPOC

Наши новости

Новые события из жизни нашей лаборатории

Статьи

Статьи и переводы лаборатории TPOC

Программы

Программы лаборатории TPOC

Релизы

Здесь мы сообщаем Вам, какие творения скоро появятся

Ссылки

Ссылки на сайты, где можно найти больше информации

Наша лаборатория

История нашей лаборатории и ее члены

Дата последнего обновления: 25 июля 2005 года
У вас есть предложения по нашему сайту?
Напишите сюда
Любимые сайты вирмейкеров:
(WASM)   (RSDN)