Реферат: Перехват методов COM интерфейсов
...
ATL_IMPL_THUNK(1023)
Метод-перехватчик будет вызываться клиентом с заранее неизвестным количеством параметров, поэтому написать такую функцию на языке высокого уровня невозможно – не подходят ни стандартные пролог/эпилог, генерируемые компилятором C++, ни “нормальное” завершение функции вызовом инструкции ret, так как stdcall-функции должны очищать стек сами, передавая размер стека параметров в ret.
Рисунок 2. Вызов COM метода.
На рисунке 2 приведен пример дизассемблированного кода вызова метода COM-интерфейса (ссылка на который находится в pUnk) с передачей двух параметров, arg1 и arg2.
Отключить генерирование стандартного пролога и эпилога можно с помощью директивы _declspec(naked) перед определением функции. Проблема, связанная с нормальным завершением путем вызова ret, решается за счет использования другой инструкции процессора – jmp. Вместо того, чтобы вызывать исходный метод с помощью инструкции call (мы не можем подготовить стек параметров для call, так как не знаем их количество) и затем выполнить “ret n” (нам неизвестно n – количество параметров * 4) – перехватчик определяет адрес исходного метода, заменяет в стеке указатель на объект (который внутри вызова будет рассматриваться как this), к методу которого производится вызов, а затем просто “перепрыгивает” по нужному адресу с помощью jmp. После вызова jmp в стеке не остается ничего, что напоминало бы о перехватчике – настоящая функция получает нетронутый стек параметров и после ее завершения мы попадем в клиентский код, минуя перехватчик. Ниже приведен код перехватчика, реализованный с помощью ATL:
mov eax, [esp+4] // первый параметр в стеке - this cmp dword ptr [eax+8], 0 // проверяем счетчик ссылок QIThunk::m_dwRef jg goodref call atlBadThunkCall goodref: mov eax, [esp+4] // первый параметр в стеке - this mov eax, dword ptr [eax+4] // получаем переменную-член QIThunk::m_pUnk mov [esp+4], eax // заменяем this-перехватчика в стеке на m_pUnk mov eax, dword ptr [eax] // получаем vptr (указатель на vtbl) // n – порядковый номер метода в vtbl mov eax, dword ptr [eax+4*n] // получаем адрес нужного виртуального метода jmp eax // переходим в нужный метод (обратно не вернемся) |
Необходимо отметить, что подобная техника позволяет выполнить предварительную обработку в перехватчике (в случае ATL – проверка счетчика ссылок перед вызовом), но не пост-обработку. После инструкции “jmp eax” мы больше не вернемся в код перехватчика (в стеке лежит адрес возврата в клиентский код, и после ret мы попадем именно туда).
Например, мы могли бы попытаться расширить код перехватчика так, чтобы писать отладочные сообщения, если вызов метода завершился с ошибкой. Чтобы решить эту задачу, нам пришлось бы заменить адрес возврата в стеке на код перехватчика (вместо адреса возврата в клиентский код), но тогда между пред- и пост-обработкой нужно было бы где-то хранить исходный адрес возврата. Стек не подходит в качестве такого хранилища, так как он будет использоваться вызываемым методом. Один из возможных вариантов – использование TLS или динамической памяти, кроме того, доступ к этому хранилищу должен синхронизироваться для многопоточных приложений.
ПРИМЕЧАНИЕ Количество слотов TLS ограничено, а вызовы к перехватчику в одном потоке могут быть вложенными. Поэтому для хранения адресов возврата пришлось бы использовать связанный список или аналогичную структуру данных, а также обеспечить быстрые выделения/освобождения памяти для элементов списка, чтобы уменьшить влияние перехватчика на скорость выполнения приложения. |
Подход, используемый ATL для перехвата вызовов COM-объектов, сводится к следующему:
Указатель на интерфейс заменяется на перехватчик в методе CComObjectRootBase::InternalQueryInterface при вызове QueryInterface. Поэтому перехватываются только вызовы COM-объектов, разработанных с помощью ATL.
vtbl перехватчика создается путем ручного объявления большого количества (1024) виртуальных методов, имеющих одинаковую реализацию.
ПРИМЕЧАНИЕ Такое решение нельзя назвать изящным – исходные тексты QIThunk получаются большими, но, с другой стороны это наиболее эффективный способ генерации vtbl. Альтернативный способ мог бы заключаться в заполнении vtbl во время выполнения приложения: |
HRESULT __stdcall thunk(void* pthis) К-во Просмотров: 668
Бесплатно скачать Реферат: Перехват методов COM интерфейсов
|