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

Роббинс Д. - Отладка приложений для Microsoft .NET и Microsoft Windows - 2004

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

ГЛАВА 15 Блокировка в многопоточных приложениях

543

 

 

[Initialization]

; Единственное обязательное значение, имя файла DeadDetExt,

;который будет обрабатывать вывод. ExtDll = "TextFileDDExt.dll"

;Если этот параметр равен 1, DeadlockDetection будет

;выполнять инициализацию в собственной функции DllMain,

;чтобы протоколирование могло быть начато как можно раньше. StartInDllMain = 0

;Если StartInDllMain равняется 1, этот ключ задает первоначальные

;параметры DeadlockDetection. В нем указываются значения флагов DDOPT_*.

;InitialOpts = 0

;Список модулей, игнорируемых при перехвате функций

;синхронизации. IMM32.DLL — это DLL Input Method Editor (редактор

;методов ввода), которую Windows XP загружает во все процессы.

;Создавайте список в последовательном порядке, начиная с номера 1. [IgnoreModules]

Ignore1=IMM32.DLL

Как вы можете увидеть по некоторым параметрам INI, DeadlockDetection мо жет выполнять инициализацию при простом вызове LoadLibrary. Для проактивной отладки было бы неплохо, чтобы во время инициализации ваше приложение проверяло конкретный раздел реестра или переменную среды и вызывало при их наличии LoadLibrary с указанным именем DLL. Благодаря этому вам не нужно было бы использовать условную компиляцию, и у вас были бы средства чистой загруз ки DLL в свое адресное пространство. Конечно, это подразумевает, что загружае мые вами таким образом DLL должны полностью инициализироваться в собствен ных функциях DllMain и не должны требовать вызова каких нибудь других экс портируемых функций.

Чтобы вы могли указывать параметры инициализации DeadlockDetection в своем коде, а не при помощи файла INI, вам нужно включить в свою программу файл DEADLOCKDETECTION.H и скомпоновать ее с библиотекой DEADLOCKDETEC TION.LIB. Если вы хотите инициализировать DeadlockDetection сами, вызовите в нужном месте функцию OpenDeadlockDetection, которая принимает единственный параметр — первоначальные флаги протоколирования. Все флаги DDOPT_* указа ны в табл. 15 2. Вызывать OpenDeadlockDetection следует до того, как ваша программа начнет создавать потоки, чтобы вы могли записать всю важную информацию об объектах синхронизации.

Изменять параметры протоколирования можно в любой момент при помощи функции SetDeadlockDetectionOptions. Она принимает тот же набор объединенных при помощи операции ИЛИ флагов, что и OpenDeadlockDetection. Чтобы увидеть текущие параметры, вызовите GetDeadlockDetectionOptions. Во время выполнения программы можете изменять параметры протоколирования сколько вашей душе угодно. Для приостановления и возобновления протоколирования служат функ ции SuspendDeadlockDetection и ResumeDeadlockDetection соответственно.

544

ЧАСТЬ IV

Мощные средства и методы отладки неуправляемого кода

 

Табл. 15-2. Параметры протоколирования DeadlockDetection

 

 

 

Флаг

 

Ограничивает протоколирование

DDOPT_WAIT

 

Функциями ожидания

DDOPT_THREADS

Функциями работы с потоками

DDOPT_CRITSEC

Функциями работы с критическими секциями

DDOPT_MUTEX

Функциями работы с мьютексами

DDOPT_SEMAPHORE

Функциями работы с семафорами

DDOPT_EVENT

Функциями работы с событиями

DDOPT_ALL

 

Регистрирует все перехваченные функции

 

 

 

Вместе с исходным кодом DeadlockDetection вы можете найти на диске мою библиотеку DeadDetExt под названием TEXTFILEDDEXT.DLL. Это относительно простое расширение записывает всю информацию в текстовый файл. При запус ке DeadlockDetection вместе с TEXTFILEDDEXT.DLL расширение создает текстовый файл в том же каталоге, в котором находится выполняемая программа. Текстовый файл будет иметь имя выполняемой программы с расширением .DD. Например, при запуске программы DDSIMPTEST.EXE итоговый файл будет назван DDSIMP TEST.DD. Вот пример вывода, сгенерированного TEXTFILEDDEXT.DLL (листинг 15 1).

Листинг 15-1. Данные, выводимые утилитой DeadlockDetection при помощи TEXTFILEDDEXT.DLL

 

TID

Ret Addr

C/R Ret Value

Function & Params

 

 

 

0x00000DF8

[0x004011B2] (R) 0x00000000

InitializeCriticalSection 0x00404150

 

 

0x00000DF8

[0x004011CC] (R) 0x000007C0

CreateEventA 0x00000000, 1, 0,

 

 

 

 

 

0x004040F0 [The Event Name]

 

 

0x00000DF8

[0x004011EF] (R) 0x000007BC

CreateThread 0x00000000, 0x00000000,

 

 

 

 

 

0x00401000,

0x00000000,

 

 

 

 

 

0x00000000, 0x0012FF5C

 

 

0x00000DF8

[0x00401212] (R) 0x000007B8

CreateThread 0x00000000, 0x00000000,

 

 

 

 

 

0x004010BC,

0x00000000,

 

 

 

 

 

0x00000000, 0x0012FF5C

 

 

0x00000DF8

[0x00401229] (C)

EnterCriticalSection 0x00404150

 

 

0x000000A8

[0x00401030] (C)

EnterCriticalSection 0x00404150

 

 

0x00000F04

[0x004010F3] (R) 0x000007B0

OpenEventA 0x001F0003, 0, 0x004040BC

 

 

 

 

 

[The Event Name]

 

 

0x00000DF8

[0x00401229] (R) 0x00000000

EnterCriticalSection 0x00404150

 

 

0x00000DF8

[0x0040123E] (C)

WaitForSingleObject 0x000007C0,

 

 

 

 

 

INFINITE

 

 

0x00000F04

[0x00401121] (C)

EnterCriticalSection 0x00404150

 

 

 

 

 

 

 

 

Заметьте: сведения об именах функций и их параметрах представлены в лис тинге 15 1 на нескольких строках, чтобы они помещались на странице. Инфор мация выводится в таком порядке.

1.Идентификатор выполняемого потока.

2.Адрес возврата, показывающий, какая из ваших функций вызвала функцию синхронизации. При помощи утилиты CrashFinder из главы 12 можно просмот реть адреса возврата и узнать, как вы оказались в ситуации блокировки.

ГЛАВА 15 Блокировка в многопоточных приложениях

545

 

 

3.Индикатор вызова/возврата, который помогает определить действия, проис шедшие до или после конкретных функций.

4.Возвращаемое функцией значение, если ваша программа его сообщает.

5.Имя функции синхронизации.

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

Если при запуске вашей программы она заблокируется, завершите процесс и изучите файл вывода, чтобы узнать, какая функция синхронизации была вызвана последней. Для обновления информации TEXTFILEDDEXT.DLL сбрасывает файловые буферы в файл при каждом вызове функций WaitFor*, EnterCriticalSection и TryEnter CriticalSection.

Предупреждение: если вы включите полное протоколирование всех функций, почти мгновенно будут созданы очень большие файлы. Так, создав пару потоков при помощи приложения MTGDI из числа примеров к Visual C++, я за минуту или две сгенерировал 11 Мбайтный текстовый файл.

Реализация DeadlockDetection

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

Перехват импортируемых функций

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

Импортируемая функция — это функция, которая располагается в DLL. Напри мер, вызывая OutputDebugString, ваша программа вызывает функцию, находящую ся в KERNEL32.DLL. Кода я только начал писать программы для Win32, я думал, что вызов импортируемых функций аналогичен вызовам любых других функций: команда CALL или команда перехода передает управление по нужному адресу и начинает выполнение импортируемой функции. Единственное различие могло бы состоять в том, что в случае импортируемой функции загрузчик программ ОС должен был бы просмотреть исполняемый файл и исправить адреса, чтобы они соответствовали той области памяти, в которую будет загружена вызываемая DLL. Однако, взглянув на действительную реализацию вызовов импортируемых функ ций, я был поражен ее простотой и элегантностью.

Недостаток только что описанного мной подхода станет очевидным, если учесть наличие огромного числа API функций и возможность вызова одной и той же функции во многих местах. Если бы загрузчик должен был найти и исправить каждый вызов, скажем, функции OutputDebugString, загрузка программы могла бы продолжаться вечность. Даже если б компоновщик создавал таблицу, где указы

546 ЧАСТЬ IV Мощные средства и методы отладки неуправляемого кода

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

Так как же загрузчик сообщает программе о том, где находится импортируе мая функция? Решение чертовски умно. Представив, куда направляются вызовы OutputDebugString, вы вскоре поймете, что каждый вызов должен обращаться к одному и тому же адресу памяти, по которому OutputDebugString была загружена. Конеч но, ваша программа не может знать этот адрес заранее, поэтому все вызовы Output DebugString выполняются посредством единственного косвенного адреса. При за грузке вашего исполняемого файла и нужных ему DLL загрузчик корректирует этот единственный косвенный адрес, чтобы он соответствовал итоговому адресу за грузки OutputDebugString. Чтобы косвенная адресация работала, компилятор гене рирует при каждом вызове импортируемой функции переход к этому косвенно му адресу. Косвенный адрес хранится в исполняемом файле в разделе .idata (или import). Если вы импортируете функцию, объявляя ее как __declspec(dllimport), то вместо косвенного перехода будет косвенный вызов, что экономит несколько команд на каждом вызове функции.

Чтобы установить ловушку для импортируемой функции, нужно отыскать в исполняемом файле раздел импорта, найти адрес нужной функции и заменить его адресом функции ловушки. Вам может показаться, что это потребует большого объема работы, однако все не так уж плохо, так как формат файлов Win32 Portable Executable (PE) организован очень разумно.

Метод установки ловушки для импортируемых функций описан в главе 10 ве ликолепной книги Мэтта Питрека (Matt Pietrek) «Windows 95 System Programming Secrets» (IDG Books, 1995). Мэтт просто ищет для модуля раздел импорта и про сматривает в цикле импортируемые функции, используя значение, возвращаемое функцией GetProcAddress. Обнаружив нужную функцию, он перезаписывает ее первоначальный адрес адресом функции ловушки.

С момента издания книги Мэтта в 1995 г. в мире программирования произош ли два небольших изменения. Во первых, когда Мэтт писал свою книгу, большин ство программистов не объединяло раздел импорта с другими разделами PE файла. Поэтому, если раздел импорта располагается в памяти, доступной только для чте ния, попытка перезаписи адреса функции приведет к нарушению доступа. Чтобы избежать этой ошибки, я перед записью адреса функции ловушки устанавливаю защиту виртуальной памяти в состояние разрешения чтения и записи. Вторая, чуть более сложная проблема связана с невозможностью перехвата в некоторых слу чаях импортируемых функций в Microsoft Windows Me. Очень многие спрашива ют меня о перехвате функций, поэтому я решил реализовать его и для Windows Me и рассказать, что происходит в этой ОС.

Работая с DeadlockDetection, вам хотелось бы иметь возможность перенаправ ления функций работы с потоками при любом запуске своей программы, даже когда она выполняется под управлением отладчика. Однако установка ловушек под управ лением отладчика может представлять проблему, хотя на первый взгляд так не кажется. Получив адрес функции при помощи GetProcAddress в Windows XP или при выполнении программы в Windows Me вне отладчика, вы всегда сможете найти этот адрес в разделе импорта. Но в Windows Me адрес, возвращаемый функцией

ГЛАВА 15 Блокировка в многопоточных приложениях

547

 

 

GetProcAddress в программе, выполняемой под управлением отладчика, отличает ся от адреса, получаемого при выполнении вне отладчика. В первом случае GetProc Address на самом деле возвращает отладочный шлюз (debug thunk) — специаль ную оболочку для действительного вызова.

Отладочный шлюз нужен потому, что Windows Me не выполняет копирование при записи (copy on write) для адресов, расположенных выше 2 Гб. Копирование при записи предполагает, что при записи в страницу разделяемой памяти ОС делает копию страницы и предоставляет ее процессу, выполняющему запись. Обычно Windows Me и Windows XP следуют одинаковым правилам, и все работает отлич но. Однако для разделяемой памяти, находящейся выше 2 Гб, где в Windows Me загружаются все DLL системы, Windows Me не выполняет копирования при запи си. Это значит, что при изменении памяти в DLL системы в результате установки точки прерывания или исправления функции изменение произойдет для всех процессов ОС, что вызовет ее крах, если измененная область будет использоваться другим процессом. Поэтому Windows Me прилагает серьезные усилия, чтобы по мешать вам исказить эту память.

Отладочный шлюз, возвращаемый GetProcAddress при выполнении под отлад чиком, — это средство, при помощи которого Windows Me предотвращает попытки отладки системных функций, расположенных выше 2 Гб. В целом отсутствие ко пирования при записи большинство программистов волновать не должно; оно представляет проблему только для тех, кто разрабатывает отладчики или желает корректно перехватывать функции независимо от того, выполняется программа под отладчиком или нет.

К счастью, получить действительный адрес импортируемой функции не так сложно — просто для этого требуется чуть поработать, избегая при этом GetProc Address. Структура IMAGE_IMPORT_DESCRIPTOR в PE файле, которая содержит всю ин формацию о функциях, импортируемых из конкретной DLL, имеет указатели на два массива в исполняемом файле — таблицы адресов импортируемых функций (import address table, IAT) (иногда их называют массивами данных шлюзов — thunk data array). Первый указатель указывает на действительную IAT, который загруз чик программ корректирует при загрузке исполняемого файла, второй — на ис ходную IAT, содержащую адреса импортируемых функций и не изменяемую заг рузчиком. Итак, для обнаружения действительного адреса импортируемой функ ции нужно просто найти ее в исходной IAT; после этого надо записать адрес ло вушки в соответствующий элемент действительной IAT, используемой програм мой. Благодаря этому ловушка будет работать всегда независимо от того, где она вызывается.

Всю работу, связанную с установкой ловушек, выполняет моя функция HookIm portedFunctionsByName (табл. 15 3). Так как я хотел сделать перехват функций как можно более общим, я реализовал возможность одновременного перехвата не скольких функций, импортируемых из одной DLL. Как можно догадаться по име ни, HookImportedFunctionsByName перехватывает только те функции, которые импор тируются по имени. Для перехвата функций, экспортируемых по ординалу, я на писал функцию HookOrdinalExport, но я не буду рассматривать ее в этой книге.

548

ЧАСТЬ IV Мощные средства и методы отладки неуправляемого кода

 

Табл. 15-3. Описание параметров функции HookImportedFunctionsByName

 

 

Параметр

Описание

hModule

Модуль, в котором находятся перехватываемые импортируемые

 

функции.

szImportMod Имя модуля импортируемых функций.

uiCount Число перехватываемых функций. Этот параметр равен числу элементов массивов paHookArray и paOrigFuncs.

paHookArray Массив указателей на структуры дескрипторов функций, в котором указываются перехватываемые функции. Он не обязан быть сортиро ванным по порядку имен szFunc (хотя это было бы мудрым решением, потому что в будущем я могу реализовать лучший алгоритм поиска). Если конкретный указатель pProc имеет значение NULL, HookImportedFunctionsByName пропускает этот элемент. Структура каждого элемента в массиве paHookArray содержит имя перехватываемой функ ции и указатель на новую функцию ловушку. Чтобы вы в любой мо мент могли установить/удалить ловушку, HookImportedFunctionsByName возвращает все исходные адреса импортируемых функций.

paOrigFuncs Массив первоначальных адресов функций, перехватываемых при по мощи HookImportedFunctionsByName. Если функция не была перехвачена, соответствующий ей элемент будет иметь значение NULL.

pdwHooked Возвращает число перехваченных функций из массива paHookArray.

В листинге 15 2 показана функция HookImportedFunctionsByNameA, но в своем коде вы будете вызывать HookImportedFunctionsByName: этот макрос выполняет отображение между форматами ANSI и Unicode. Однако, поскольку все имена в разделе IAT хранятся в формате ANSI, я реализовал и функцию HookImportedFunctionsByNameW, которая просто преобразует соответствующие параметры в формат ANSI и вызы вает HookImportedFunctionsByNameA.

Листинг 15-2. Функция HookImportedFunctionsByNameA из файла HOOKIMPORTEDFUNCTIONBYNAME.CPP

BOOL BUGSUTIL_DLLINTERFACE __stdcall

HookImportedFunctionsByNameA ( HMODULE

hModule

,

LPCSTR

szImportMod

,

UINT

uiCount

,

LPHOOKFUNCDESC

paHookArray ,

PROC *

paOrigFuncs ,

LPDWORD

pdwHooked

)

{

 

 

// Проверка параметров.

 

 

ASSERT ( FALSE == IsBadReadPtr ( hModule

,

 

sizeof ( IMAGE_DOS_HEADER )

) ) ;

ASSERT ( FALSE == IsBadStringPtrA ( szImportMod , MAX_PATH ) ) ; ASSERT ( 0 != uiCount ) ;

ASSERT ( NULL != paHookArray ) ;

ASSERT ( FALSE == IsBadReadPtr ( paHookArray ,

sizeof (HOOKFUNCDESC) * uiCount ));

 

ГЛАВА 15 Блокировка в многопоточных приложениях

549

 

 

 

 

// В отладочных компоновках выполняется глубокая проверка paHookArray.

 

#ifdef _DEBUG

 

 

 

if ( NULL != paOrigFuncs )

 

 

 

{

 

 

 

 

ASSERT ( FALSE == IsBadWritePtr ( paOrigFuncs ,

 

 

 

 

sizeof ( PROC ) * uiCount ) );

 

}

 

 

 

 

if ( NULL != pdwHooked )

 

 

 

{

 

 

 

 

ASSERT ( FALSE == IsBadWritePtr ( pdwHooked , sizeof ( UINT )));

 

}

 

 

 

 

// Проверка всех элементов массива перехватываемых функций.

 

 

{

 

 

 

 

for ( UINT i = 0 ; i < uiCount ; i++ )

 

 

{

 

 

 

 

 

ASSERT ( NULL != paHookArray[ i ].szFunc ) ;

 

 

 

ASSERT ( '\0' != *paHookArray[ i ].szFunc ) ;

 

 

 

// Если адрес функции не равен NULL, выполняется его проверка.

 

 

if ( NULL != paHookArray[ i ].pProc )

 

 

 

{

 

 

 

 

ASSERT ( FALSE == IsBadCodePtr ( paHookArray[i].pProc));

 

 

}

 

 

 

}

 

 

 

 

}

 

 

 

 

#endif

 

 

 

 

// Дополнительная проверка параметров и установка кода ошибки.

 

 

if ( ( 0

== uiCount

)

||

 

( NULL == szImportMod

)

||

 

( TRUE == IsBadReadPtr ( paHookArray ,

 

 

 

 

sizeof ( HOOKFUNCDESC ) * uiCount ) ))

 

{

 

 

 

 

SetLastErrorEx ( ERROR_INVALID_PARAMETER , SLE_ERROR ) ;

 

 

return ( FALSE ) ;

 

 

 

}

 

 

 

 

if ( ( NULL != paOrigFuncs )

&&

 

 

( TRUE == IsBadWritePtr ( paOrigFuncs ,

 

 

 

 

sizeof ( PROC ) * uiCount ) )

)

 

{

 

 

 

 

SetLastErrorEx ( ERROR_INVALID_PARAMETER , SLE_ERROR ) ;

 

 

return ( FALSE ) ;

 

 

 

}

 

 

 

 

if ( ( NULL != pdwHooked )

 

&&

 

( TRUE == IsBadWritePtr ( pdwHooked , sizeof ( UINT ) ) )

)

 

{

 

 

 

 

SetLastErrorEx ( ERROR_INVALID_PARAMETER , SLE_ERROR ) ;

 

 

return ( FALSE ) ;

 

 

 

}

 

 

 

 

 

 

 

 

 

см. след. стр.

550ЧАСТЬ IV Мощные средства и методы отладки неуправляемого кода

//Здесь я проверяю, расположена ли данная системная DLL выше 2 Гб,

//в случае чего Windows 98 не позволит ее скорректировать.

if ( ( FALSE == IsNT ( ) ) && ( (DWORD_PTR)hModule >= 0x80000000 ) )

{

SetLastErrorEx ( ERROR_INVALID_HANDLE , SLE_ERROR ) ; return ( FALSE ) ;

}

//СООБРАЖЕНИЯ ПО ПОВОДУ УЛУЧШЕНИЯ ПРОГРАММЫ

//Следует ли проверять каждый элемент массива

//перехватываемых фукнций в заключительных компоновках?

if ( NULL != paOrigFuncs )

{

// Присвоение всем элементам массива paOrigFuncs значения NULL. memset ( paOrigFuncs , NULL , sizeof ( PROC ) * uiCount ) ;

}

if ( NULL != pdwHooked )

{

// Присвоение числу перехваченных функций значения 0. *pdwHooked = 0 ;

}

// Получение специфического дескриптора импорта. PIMAGE_IMPORT_DESCRIPTOR pImportDesc =

GetNamedImportDescriptor ( hModule , szImportMod ); if ( NULL == pImportDesc )

{

// Запрошенный модуль не был импортирован. Не возвращать ошибку. return ( TRUE ) ;

}

//ИСПРАВЛЕННАЯ ОШИБКА. Спасибо Аттиле Шепезвари (Attila Szepesvary)!

//Проверка того, что первый шлюз и исходный первый шлюз

//не равны NULL. Исходный первый шлюз может быть нулевым

//дескриптором импорта, что вызвало бы крах этой функции.

if ( ( NULL == pImportDesc >OriginalFirstThunk

)

||

( NULL == pImportDesc >FirstThunk

)

)

{

 

 

//Я возвращаю TRUE, потому что это аналогично случаю,

//в котором запрошенный модуль не был импортирован.

//Все в порядке!

SetLastError ( ERROR_SUCCESS ) ; return ( TRUE ) ;

}

//Получение информации об исходном шлюзе для этой DLL.

//Я не могу использовать информацию, хранимую

//в pImportDesc >FirstThunk, так как загрузчик уже изменил

//этот массив во время коррекции импортируемых функций.

ГЛАВА 15 Блокировка в многопоточных приложениях

551

 

 

 

 

 

 

// Исходный шлюз предоставляет мне доступ к именам функций.

 

PIMAGE_THUNK_DATA pOrigThunk =

 

 

MakePtr ( PIMAGE_THUNK_DATA

,

 

hModule

,

 

pImportDesc >OriginalFirstThunk ) ;

 

//Получение указателя на массив pImportDesc >FirstThunk, при

//помощи которого выполняется действительный перехват функций.

PIMAGE_THUNK_DATA pRealThunk = MakePtr ( PIMAGE_THUNK_DATA

,

hModule

,

pImportDesc >FirstThunk

);

// Поиск перехватываемых функций.

 

while ( NULL != pOrigThunk >u1.Function )

 

{

 

// Выполняется поиск только тех функций, которые

 

// импортируются по имени, но не по значению ординала.

 

if ( IMAGE_ORDINAL_FLAG !=

 

( pOrigThunk >u1.Ordinal & IMAGE_ORDINAL_FLAG ))

{

// Изучение имени

этой импортируемой функции.

 

PIMAGE_IMPORT_BY_NAME pByName ;

 

pByName = MakePtr

( PIMAGE_IMPORT_BY_NAME

,

 

hModule

,

pOrigThunk >u1.AddressOfData ) ;

// Если имя начинается с NULL, оно пропускается. if ( '\0' == pByName >Name[ 0 ] )

{

//ИСПРАВЛЕННАЯ ОШИБКА (спасибо Аттиле Шепезвари!)

//Я забыл про увеличение указателей на шлюз! pOrigThunk++ ;

pRealThunk++ ; continue ;

}

//Этот флаг показывает, перехватываю ли я функцию. BOOL bDoHook = FALSE ;

//СООБРАЖЕНИЯ ПО ПОВОДУ УЛУЧШЕНИЯ ПРОГРАММЫ

//Возможно, здесь следует реализовать двоичный поиск.

//Я проверяю, есть ли имя этой импортируемой функции

//в массиве перехватываемых функций. Возможно, paHookArray

//следует держать отсортированным по именам функций, чтобы

//можно было ускорить его просмотр при помощи двоичного

//поиска. Однако передаваемый в эту функцию параметр

//uiCount будет довольно небольшим, поэтому при поиске

//каждой функции, импортируемой по szImportMod, вполне

//допустимо просматривать весь массив paHookArray.

см. след. стр.

552

ЧАСТЬ IV Мощные средства и методы отладки неуправляемого кода

 

 

 

 

 

 

 

 

 

 

 

for ( UINT i = 0 ; i < uiCount ; i++ )

 

 

 

 

{

 

 

 

 

if ( ( paHookArray[i].szFunc[0] ==

 

 

 

 

pByName >Name[0] ) &&

 

( 0 == strcmpi ( paHookArray[i].szFunc ,

 

 

 

 

(char*)pByName >Name

)

)

)

 

{

 

 

 

 

// Если адрес функции равен NULL, выполняется выход;

 

// в противном случае функция перехватывается.

 

 

if ( NULL != paHookArray[ i ].pProc )

 

 

 

 

{

 

 

 

 

bDoHook = TRUE ;

 

 

 

 

}

 

 

 

 

break ;

 

 

 

 

}

 

 

 

 

}

 

 

 

 

if ( TRUE == bDoHook )

 

 

 

 

{

 

 

 

 

// Я обнаружил функцию, которую нужно перехватить. Теперь,

 

// прежде чем перезаписать указатель на функцию, я должен

 

// изменить защиту памяти, разрешив запись в нее. Заметьте,

 

// что я выполняю запись в область действительного шлюза!

 

MEMORY_BASIC_INFORMATION mbi_thunk ;

 

 

 

 

VirtualQuery ( pRealThunk

 

 

,

 

&mbi_thunk

 

 

,

 

sizeof ( MEMORY_BASIC_INFORMATION ) ) ;

 

if ( FALSE == VirtualProtect ( mbi_thunk.BaseAddress ,

 

mbi_thunk.RegionSize

,

 

PAGE_READWRITE

 

 

,

 

&mbi_thunk.Protect

 

))

 

{

 

 

 

 

ASSERT ( !"VirtualProtect failed!" ) ;

 

 

 

 

SetLastErrorEx ( ERROR_INVALID_HANDLE , SLE_ERROR );

 

return ( FALSE ) ;

 

 

 

 

}

 

 

 

 

// Сохранение исходного адреса в случае надобности.

 

 

if ( NULL != paOrigFuncs )

 

 

 

 

{

 

 

 

 

paOrigFuncs[i] =

 

 

 

 

(PROC)((INT_PTR)pRealThunk >u1.Function) ;

 

 

}

 

 

 

 

// Перехват функции.

 

 

 

 

DWORD_PTR * pTemp = (DWORD_PTR*)&pRealThunk >u1.Function ;

 

*pTemp = (DWORD_PTR)(paHookArray[i].pProc);

 

 

 

 

 

 

 

 

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