Как показано в главе 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, чтобы в отладочных*/