Лекції з WinApi

Тема №6 Структурна обробка виключень

6. Структурна обробка виключень
 6.1. Загальний порядок обробки виключень

Виключення - події, що відбуваються під час виконання програми і вимагають зміни її природного ходу.

Апаратні виключення виникають у випадку спроби виконання неприпустимої послідовності команд (наприклад, розподіл на 0). Програми можуть самостійно викликати програмні виключення, використовуючи існуючі в Win32® механізми.

Структурна обробка виключень (SEH-Structured Exception Handling) - спеціальний механізм, що існує в Win32®, що надає можливість визначати дії програми у випадку виникнення виключень.

Компілятори, розроблені для середовища Win32® надають спеціальні кошти, призначені для структурної обробки виключень. У компіляторах Microsoft® - це ключові слова tryfinallyexcept. Ключове слово try визначає обгороджена ділянка коду. Якщо при виконанні цієї ділянки виникає виключення, то керування передається ділянці коду, визначеній словом finally чи except.

try {

// захищений блок

}

except (filter-expression) {

// оброблювач виключення

}

try {

// захищений блок

}

finally {

// оброблювач завершення

}

У випадку виникнення виключення ОС запам'ятовує середовище потоку, що виповнюється, у структурі CONTEXT і заповнює структуру EXCEPTION_RECORD інформацією про виключення.

Якщо при виникненні виключення виконувався прикладний процес (user-mode code) , то система виконує наступні дії:

  1. Намагається звернеться до відладчику, що обслуговує даний процес. Відладчик повинний знаходитися в стані чекання після виклику функції WaitForDebugEvent.
  2. Якщо процес чи не налагоджується відтладчик не обробляє це виключення, система передає керування блоку обробки виключення, визначеному ключовими словами finally чи except.
  3. Якщо оброблювач не визначений, чи він не обробляє дане виключення, керування передається відтладчику вдруге.
  4. Якщо знову відладчик не обробляє дане виключення, використовується обробка виключення за замовчуванням. У більшості випадків - це виклик функції ExitProcess.

Якщо виключення виникає при роботі ядра системи, то система передає керування оброблювачу виключення. Якщо такого ні, то система завершує свою роботу подібно тому, як це робить функція ExitWindows.

6. Структурна обробка виключень.
 6.2. Оброблювачі завершення. Локальне і глобальне розкручування.

Оброблювачі завершення гарантовано виповнюються відразу після виходу з захищеного блоку, незалежно від того, яким способом здійснюється цей вихід

Захищений код усередині блоку try може завершитися будь-яким способом: операторами returngoto чи навіть викликом функції longjump. Оброблювач завершення буде викликаний у будь-якому випадку.

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

Приклад:

DWORD Function1( void )

{ /* Робота із семафором */

WaitForSingleObject( g_hSem, INFINITE ) ; /* Вхід у критичну секцію */

/* Робота з поділюваними даними */

ReleaseSemaphore( g_hSem, 1, NULL ) ; /* Вихід із критичної секції */

return( RetValue ) ;

}

У цієї функції є дуже істотний недолік. Якщо під час роботи з поділюваними даними відбудеться виключення і програма завершитися, то семафор залишиться заблокованим. Для усунення цієї помилки необхідно переписати функцію в такий спосіб:

DWORD Function1( void )

{ /* Робота із семафором */

try {

WaitForSingleObject( g_hSem, INFINITE ) ; /* Вхід у критичну секцію */

/* Робота з поділюваними даними */

return( RetValue ) ;

}

finally {

ReleaseSemaphore( g_hSem, 1, NULL ) ; /* Вихід із критичної секції */

}

}

Поза залежністю від ходу робіт з поділюване даними, звільнення семафора гарантовано відбудеться.

Однак, у приведеного приклада є істотний недолік. Необхідно уникати використання операторів, що штучно переривають захищений блок. Спроба виходу з захищеного блоку до його закінчення, включає механізм глобального чи локального розкручування, що вимагає великої кількості накладних витрат через те, що операційна система бере на себе обробку подібної ситуації.

Локальне розкручування виникає у випадку, коли відбувається спроба передчасного завершення захищеного блоку операторами returnbreak і т.д. Глобальне розкручування виникає в тому випадку, коли передчасне завершення виникає поза захищеним блоком, наприклад, при виконанні викликаної з блоку функції. Для передчасного завершення захищеного блоку без додаткових витрат призначене ключове слово leave. Приведений нижче приклад приводить до того ж результату, що і попередній, але має значно більшу ефективність:

DWORD Function1( void )

{ /* Робота із семафором */

try {

WaitForSingleObject( g_hSem, INFINITE ) ; /* Вхід у критичну секцію */

/* Робота з поділюваними даними */

}

finally {

ReleaseSemaphore( g_hSem, 1, NULL ) ; /* Вихід із критичної секції */

}

return( RetValue ) ;

}

6. Структурна обробка виключень.
 6.3. Фільтри й оброблювачі виключень.

У випадку виникнення виключення можна визначити тип виключення, використовуючи функцію DWORD GetExceptionCode(VOID). Функція повертає код виключення. Визначені 16 кодів. От деякі з них:

EXCEPTION_ACCESS_VIOLATION

Спроба звертання до комірки пам'яті, доступ до якої заборонений (наприклад, пам'ять не виділена)

EXCEPTION_FLT_DIVIDE_BY_ZERO

Розподіл на нуль із крапкою, що плаває

EXCEPTION_INT_DIVIDE_BY_ZERO

Розподіл на нуль з фіксованою крапкою

EXCEPTION_INT_OVERFLOW

Цілочисельне переповнення

EXCEPTION_PRIV_INSTRUCTION

Спроба виконання привілейованої команди

Синтаксична конструкція try-except, використовується для опису блоку обробки, що виповнюється у випадку виникнення виключення усередині захищеного блоку. Перед початком виконання блоку обробки виробляється обчислення значення фільтра. Значення фільтра визначає реакцію програми на виключення.

Можливі три значення фільтра

EXCEPTION_EXECUTE_HANDLER

Викликає виконання оброблювача виключення

EXCEPTION_CONTINUE_SEARCH

Викликає передачу обробки виключення більш ранньому з вкладених блоків try-except чи операційній системі

EXCEPTION_CONTINUE_EXECUTION

Викликає продовження виконання перерваного блоку

Приклад:

LPTSTR SafeStrcpy(LPTSTR lpszString1, LPTSTR lpszString2) {

try {

return strcpy(string1, string2);

}

except (GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION ?

EXCEPTION_EXECUTE_HANDLER :

EXCEPTION_CONTINUE_SEARCH ) {

return NULL;

}

}

Функція SafeStrcpy працює аналогічно функції strcpy за винятком одного моменту. Якщо при операції копіювання виникає помилка звертання до пам'яті, то ця помилка обробляється усередині тіла функції і її програмі, що викликав, повертається значення NULL. Переривання програми, що викликала, не відбувається. Приклад демонструє використання прапорів EXCEPTION_EXECUTE_HANDLER іEXCEPTION_CONTINUE_SEARCH. Прапор EXCEPTION_CONTINUE_EXECUTION використовується якщо при обчисленні значення фільтра можна усунути причину виключення і продовжити виконання перерваної програми.