Отладка приложений

       

Обработка _сdесl-подключений


Как показано в главе 12, _stdcail-функции легко подключать, потому что такая функция сама чистит стек; для _cdeci-функций стеки чистит вызывающая функция.

_stdcaii _cdeci — спецификаторы соглашений о вызовах функций в языках C/C++ (см. табл. 6.3 главы 6). — Пер

Кроме того, функции DiagOutputA, DiagOutputW И AfxTrace, имеют параметры переменной длины, так что перехватить их намного труднее. Само подключение — такое же, как и для экспортируемых _stdcaii-функций, но обработка _cdeci-функций должна быть совсем другой. В LIMODSDLL.DLL требовалось, чтобы функция подключения захватила адрес возврата и определила, является ли он диапазоном адресов, в котором пользователь хочет видеть предложения трассировки. После проверки источника функция трассировки либо выполняется, либо игнорируется, после чего выполняется возврат в вызывающую. Для _stdcaii-функций эта обработка очень проста. Можно напрямую вызвать функцию трассировки и возвратиться прямо из функции подключения в вызывающую функцию, потому стек очищается внутри функции подключения. Для _cdeci-функций нужно вернуть стек обратно в первоначальное состояние и затем, если необходимо выполнить функцию трассировки, перейти к ней (а не вызвать ее!).

 Листинг 14-3. cdecl-функция подключения с расширенным макросом 

VOID NAKEDDEF LIMODS_DiagOutputA ( void)

{

// Содержит адрес возврата вызывающей функции

DWORD_PTR dwRet;

// Содержит сохраненный регистр ESI, поэтому отладочные построения

// Visual C++ 6 работают. (ESI использует функцию chkesp,

// вставляемую ключом компилятора /GZ.)

DWORD_PTR dwESI;

_asm PUSH EBP /* Установить стандартный кадр. */

_asm MOV EBP, ESP

_asm SUB ESP, _LOCAL_SIZE /* Сохранить место для локальных */

/* переменных. */

_asm MOV EAX, EBP /* EBP указатели на исходный стек.*/

 _asm ADD EAX, 4 /* Счетчик для PUSH EBP. */ 

_asm MOV EAX, [EAX] /* Получить адрес возврата. */ 

_asm MOV [dwRet], EAX /* Сохранить адрес возврата. */

 _asm MOV [dwESI], ESI /* Сохранить ESI, чтобы в отладочных*/


/* построениях работала chkesp. */

// Вызвать функцию, которая определяет, предназначен ли данный адрес

// для показа. После этого вызова возвращаемое значение находится в

 // ЕАХ и затем проверяется. Возврат TRUE означает выполнение функции

 // трассировки, a FALSE — пропуск функции трассировки.

 ChecklfAddressIsOn ( dwRet);

_asm MOV ESI, [dwESI] /* Восстановить ESI. */

 _asm ADD ESP, _LOCAL_SIZE /* Исключить область локальных

/* переменных. */

_asm MOV ESP, EBP /* Восстановить стандартный кадр. */

 _asm POP EBP

// Здесь и начинается функция! Четыре предшествующих строки

 // ассемблерного кода восстанавливают стек точно до того состояния,

 //в котором он был до входа в эту функцию, поэтому теперь можно 

// перейти к функции трассировки. Функция pReadDiagOutputA содержит 

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

 _asm TEST ЕАХ, ЕАХ /* Проверить ЕАХ на 0. */

 _asm JZ IblDiagOutputA /* Если ЕАХ содержит 0, просто

/* выполнить возврат.*/ 

_asm JMP pReadDiagOutputA /* Сделано! JUMP выполняет возврат */

/* в вызывающую, а не в эту функцию. */

 IblDiagOutputA:

/* Пропущенный TRACE! Просто выполнить */

_asm RET /* возврат в вызывающую функцию. */

 }

В листинге 14-3 показана функция подключения с расширенным макросом, которая подключает функцию DiagOutputA из BUGSLAYERUTIL.DLL. Чтобы облегчить повторное использование общих подпрограмм языка ассемблера, таких как _cdeci-код пролога, в LIMODSDLL.CPP определены несколько макросов языка ассемблера (для использования в функциях подключения). Настоятельно рекомендую читателям выполнить пошаговый проход этих макросов в окне Disassembly отладчика Visual C++, чтобы наблюдать каждую инструкцию в действии.



Содержание раздела