Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Рихтер Дж., Назар К. - Windows via C C++. Программирование на языке Visual C++ - 2009

.pdf
Скачиваний:
6269
Добавлен:
13.08.2013
Размер:
31.38 Mб
Скачать

746Часть IV. Динамически подключаемые библиотеки

//Replaces a symbol's address in all modules' import sections static void WINAPI ReplaceIATEntryInOneMod(PCSTR pszCalleeModName,

PROC pfnOrig, PROC pfnHook, HMODULE hmodCaller);

//Replaces a symbol's address in a module's export sections

static void ReplaceEATEntryInOneMod(HMODULE hmod, PCSTR pszFunctionName, PROC pfnNew);

private:

// Used when a DLL is

newly loaded after hooking a function

static void

WINAPI

FixupNewlyLoadedModule(HMODULE hmod, DWORD dwFlags);

// Used to trap when DLLs are newly loaded

static HMODULE WINAPI LoadLibraryA(PCSTR pszModulePath); static HMODULE WINAPI LoadLibraryW(PCWSTR pszModulePath); static HMODULE WINAPI LoadLibraryExA(PCSTR pszModulePath,

HANDLE hFile, DWORD dwFlags);

static HMODULE WINAPI LoadLibraryExW(PCWSTR pszModulePath, HANDLE hFile, DWORD dwFlags);

// Returns address of replacement function if hooked function is requested static FARPROC WINAPI GetProcAddress(HMODULE hmod, PCSTR pszProcName);

private:

// Instantiates hooks on these functions static CAPIHook sm_LoadLibraryA;

static CAPIHook sm_LoadLibraryW; static CAPIHook sm_LoadLibraryExA; static CAPIHook sm_LoadLibraryExW; static CAPIHook sm_GetProcAddress;

};

//////////////////////////////// End of File //////////////////////////////////

Ч А С Т Ь V

СТРУКТУРНАЯ

ОБРАБОТКА

ИСКЛЮЧЕНИЙ

Оглавление

 

Г Л А В А 2 3 Обработчики завершения ...........................................................................

748

Примеры использования обработчиков завершения ...............................................

749

Funcensteinl .........................................................................................................................

750

Funcenstein2 ........................................................................................................................

750

Funcenstein3 ........................................................................................................................

752

Funcfurterl ............................................................................................................................

753

Проверьте себя: FuncaDoodleDoo................................................................................

754

Funcenstein4 ........................................................................................................................

756

Funcaramal ...........................................................................................................................

757

Funcarama2 ..........................................................................................................................

758

Funcarama3 ..........................................................................................................................

758

Funcarama4: последний рубеж ......................................................................................

759

И еще о блоке finally .........................................................................................................

761

Funcfurter2 ...........................................................................................................................

762

Программа-пример SEHTerm .........................................................................................

763

Г Л А В А 2 3

Обработчики завершения

Закроем глаза и помечтаем, какие бы программы мы писали, если бы сбои в них были невозможны! Представляете: памяти навалом, неверных указателей никто не передает, нужные файлы всегда на месте. Не программирование, а праздник, да? А код программ? Насколько он стал бы проще и понятнее! Без всех этих if и goto.

И если вы давно мечтали о такой среде программирования, вы сразу же оце-

ните структурную обработку исключений (structured exception handling, SEH).

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

Хотя полностью игнорировать ошибки в программе при использовании SEH нельзя, она все же позволяет отделить основную работу от рутинной обработки ошибок, к которой можно вернуться позже.

Главное, почему Майкрософт ввела в Windows поддержку SEH, было ее стремление упростить разработку операционной системы и повысить ее надежность. А нам SEH поможет сделать надежнее наши программы.

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

Различия в реализации SEH разными компиляторами могли бы затруднить описание конкретных примеров использования SEH. Но большинство

Глава 23. Обработчики завершения.docx 749

поставщиков компиляторов придерживается синтаксиса, рекомендованного Майкрософт. Синтаксис и ключевые слова в моих примерах могут отличаться от применяемых в других компиляторах, по основные концепции SEH везде одинаковы. В этой главе я использую синтаксис компилятора Microsoft Visual C++.

Примечание. Не путайте SEH с обработкой исключений в C++, которая представляет собой еще одну форму обработки исключений, построенную на применении ключевых слов языка C++ catch и throw. При этом Microsoft Visual C++ использует преимущества поддержки SEH, уже обеспеченной компилятором и операционными системами Windows.

SEH предоставляет две основные возможности: обработку завершения (termination handling) и обработку исключений (exception handling). В этой главе мы рассмотрим обработку завершения.

Обработчик завершения гарантирует, что блок кода (собственно обработчик) будет выполнен независимо от того, как происходит выход из другого блока кода — защищенного участка программы. Синтаксис обработчика завершения при работе с компилятором Microsoft Visual C++ выглядит так:

__try {

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

...

}

__finally {

// обработчик завершения

...

}

Ключевые слова __try и __finally обозначают два блока обработчика завершения. В предыдущем фрагменте кода совместные действия операционной системы и компилятора гарантируют, что код блока finally обработчика завершения будет выполнен независимо от того, как произойдет выход из защищенного блока. И неважно, разместите вы в защищенном блоке операторы return, goto или даже longjump — обработчик завершения все равно будет вызван. Далее я покажу вам несколько примеров использования обработчиков завершения.

Примеры использования обработчиков завершения

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

750 Часть V. Структурная обработка исключений

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

Funcenstein1

Чтобы оценить последствия применения обработчиков завершения, рассмотрим более конкретный пример:

DWORD Funcenstein1() {

DWORD dwTemp;

// 1. Что-то делаем здесь

...

__try {

//2. Запрашиваем разрешение на доступ

//к защищенным данным, а затем используем их

WaitForSingleObject(g_hSem, INFINITE);

g_dwProtectedData = 5; dwTemp = g_dwProtectedData;

}

__finally {

// 3. Даем и другим попользоваться защищенными данными

ReleaseSemaphore(g_hSem, 1, NULL);

}

// 4. Продолжаем что-то делать return(dwTerap);

}

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

Funcenstein2

Теперь чуть-чуть изменим код функции и посмотрим, что получится:

DWORD Funcenstein2() {

DWORD dwTerop;

// 1. Что-то делаем здесь

...

__try {

// 2. Запрашиваем разрешение на доступ

Глава 23. Обработчики завершения.docx 751

// к защищенным данным, а затем используем их

WaitForSingleObject(g_hSem, INFINITE);

g_dwProtectedData = 5; dwTemp = g_dwProtectedData;

// возвращаем новое значение return(dwTemp);

}

__finally {

// 3. Даем и другим попользоваться защищенными данными

ReleaseSemaphore(g_hSem, 1, NULL);

}

//продолжаем что-то делать - в данной версии

//этот участок кода никогда не выполняется dwTemp = 9;

return(dwTemp);

}

В конец блока try в функции Funcenstein2 добавлен оператор return. Он сообщает компилятору, что вы хотите выйти из функции и вернуть значение переменной dwTemp (в данный момент равное 5). Но, если будет выполнен return, текущий поток никогда не освободит семафор, и другие потоки не получат шанса занять этот семафор. Такой порядок выполнения грозит вылиться в действительно серьезную проблему: ведь потоки, ожидающие семафора, могут оказаться не в состоянии возобновить свое выполнение.

Применив обработчик завершения, мы не допустили преждевременного выполнения оператора return. Когда return пытается реализовать выход из блока try, компилятор проверяет, чтобы сначала был выполнен код в блоке finally, — причем до того, как оператору return в блоке try будет позволено реализовать выход из функции. Вызов ReleaseSemaphore в обработчике завершения (в функции Funcenstein2) гарантирует освобождение семафора — поток не сможет случайно сохранить права на семафор и тем самым лишить процессорного времени все ожидающие этот семафор потоки.

После выполнения блока finally функция фактически завершает работу. Любой код за блоком finally не выполняется, поскольку возврат из функции происходит внутри блока try. Так что функция возвращает 5 и никогда — 9.

Каким же образом компилятор гарантирует выполнение блока finally до выхода из блока try? Дело вот в чем. Просматривая исходный текст, компилятор видит, что вы вставили return внутрь блока try. Тогда он генерирует код, который сохраняет возвращаемое значение (в нашем примере 5) в созданной им же временной переменной. Затем создает код для выполнения инструкций, содержащихся внутри блока finally, — это называется локальной раскруткой (local unwind). Точнее, локальная раскрутка происходит, когда система выполняет блок finally из-за преждевременного выхода из блока try. Значение

752 Часть V. Структурная обработка исключений

временной переменной, сгенерированной компилятором, возвращается из функции после выполнения инструкций в блоке finally.

Как видите, чтобы все это вытянуть, компилятору приходится генерировать дополнительный код, а системе — выполнять дополнительную работу. На разных типах процессоров поддержка обработчиков завершения реализуется по-разному. Например, процессору Alpha понадобится несколько сотен или даже тысяч машинных команд, чтобы перехватить преждевременный возврат из try и вызвать код блока finally. Поэтому лучше не писать код, вызывающий преждевременный выход из блока try обработчика завершения, — это может отрицательно сказаться на быстродействии программы. Чуть позже мы обсудим ключевое слово __leave, которое помогает избежать написания кода, приводящего к локальной раскрутке.

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

Заметьте: когда поток управления выходит из блока try естественным образом (как в Funcenstein1), издержки от вызова блока finally минимальны. При использовании компилятора Microsoft на процессорах x86 для входа в finally при нормальном выходе из try исполняется всего одна машинная команда — вряд ли вы заметите ее влияние на быстродействие своей программы. Но издержки резко возрастут, если компилятору придется генерировать дополнительный код, а операционной системе — выполнять дополнительную работу, как в Funcenstein2.

Funcenstein3

Снова изменим код функции:

DW0RD Funcenstein3() {

DWORD dwTemp;

// 1. Что-то делаем здесь

...

__try {

//2. Запрашиваем разрешение на доступ

//к защищенным данным, а затем используем их

WaitForSingleObject(g_hSen, INFINITE);

g.dwProtectedData = 5; dwTemp = g.dwProtectedData;

// пытаемся перескочить через блок finally goto ReturnValue;

}

Глава 23. Обработчики завершения.docx 753

__finally {

// 3. Даем и другим попользоваться защищенными данными

ReleaseSemaphore(g_hSem, 1, NULL);

}

dwTemp = 9;

// 4. Продолжаем что-то делать

ReturnValue:

return(dwTemp);

}

Обнаружив в блоке try функции Funcenstein3 оператор goto, компилятор генерирует код для локальной раскрутки, чтобы сначала выполнялся блок finally. Но на этот раз после finally исполняется код, расположенный за меткой ReturnValue, так как возврат из функции не происходит ни в блоке try, ни в блоке finally. В итоге функция возвращает 5. И опять, поскольку вы прервали естественный ход потока управления из try в finally, быстродействие программы — в зависимости от типа процессора — может снизиться весьма значительно.

Funcfurter1

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

DWORD Funcfurter1() {

DWORD dwTemp;

// 1. Что-то делаем здесь

...

__try {

//2. Запрашиваем разрешение на доступ

//к защищенным данным, а затем используем их

WaitForSingleObject(g_hSem, INFINITE);

dwTemp = Funcinator(g_dwProtectedData);

}

__finally {

// 3. Даем и другим попользоваться защищенными данными

ReleaseSemaphore(g_hSem, 1, NULL);

}

// 4. Продолжаем что-то делать return(dwTemp);

}

Допустим, в функции Funcinator, вызванной из блока try, — «жучок», изза которого возникает нарушение доступа к памяти. Без SEH пользователь

Соседние файлы в предмете Программирование на C++