Обработка _с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++, чтобы наблюдать каждую инструкцию в действии.