Форум: "WinAPI";
Текущий архив: 2004.08.15;
Скачать: [xml.tar.bz2];
ВнизПроблемы при перехвате IDispatch::Invoke Найти похожие ветки
← →
Ihor Osov'yak © (2004-06-22 23:22) [0]При иследовании одной проблемы пришел к выводу о необходимости перехвата Invoke некоего наследника от IDispatch, если конкретно - IHtmlDocument2 (поначалу с целью протоколирования).
В общем-то с реализацией особых проблем не было, но.. После вызова старого обработчика Invoke, после выхода со своего перехватчика - довольно таки странный эксепшен. Причем корректноость постановки перехватика проверялась с помощью иследования стека вызовов - входные параметры старому методу передаются идентичные тем, что идут на вход перехватчика (обнаружен некий интересный момент при передачи word - старшие 16 бит в стеке захламляются - но это отношения к проблеме не имеет - делался ручной сброс). Проверка делалась и при установленом перехватчике, и при отключеном - в том и том случае на вход штатного метода Invoke идут одинаковые параметры.
Эксепшен возникает после выхода с кода перехватчика, где-то в глубине вызвавшего кода - довольно далеко, пока не хватило терпения детально протрассировать.
Пока есть две версии.
Первая, более вероятная. Возможно, что вызывавший код ставит более жеские требования по сохранению регистров, чем делфи. Вызывавший код, вероятнее всего написан в недрах майкрософт :-), может и на cипласплас, хотя возникают некие сомнения - уж очень не рыхлый там код, хотя вероятнее всего мною трасировалась пока только прокси соотв. ком-обьекта, а не сам обьект...
Итак, первый вопрос - какие требования по сохранению регистров в коде, написанном на msVC, с учетом того, что код имеет прямое отношение к COM, да и с поправкой на то, что разработчик мог делать предпосылку, что его вызов соотв. метода ну никак не будет перехвачвсться..
Вторая предпосылка, более с разряда фантастики. Используется что-то похожжее на технику вложенных процедур в паскале, то есть там делается попытка работать с локальными переменными в стеке обрамляющих процедур. Естественно, при этом делаются некоторые предпосылки о структуре стека. Мною же в стек добавлена одна ступень...
Ах, в чем вопрос. Хочу услышать критику этой второй версии как несостоятельной, чтобы не спешить ее прорабатывать..
← →
Burmistroff (2004-06-23 00:37) [1]Ну так можно сразу расставить все точки: попробуй запомнить какие регистры должны быть в "хорошем" случае, затем - запусти "плохой" (перехватываемый) случай и на выходе в дебаггере установи "хорошие" значения регистров. Если прокатит - значит в этом дело.
По поводу второй версии - а шут этих MS знает. Вариант со стеком очень даже возможен, причем, как мне кажется, не обязательно даже должны быть и вложенные процедуры...
← →
Anatoly Podgoretsky © (2004-06-23 00:44) [2]За счет старших бит не беспокойся, стек всегда 32 бита, даже если байт на него помещается, просто не нужные биты не используются.
Насчет передачи параметров, если предположить, что это stdcall, то ситуация следующая
Темв в справке Calling conventions
Directive Parameter order Clean-up Passes parameters in registers?
register Left-to-right Routine Yes
pascal Left-to-right Routine No
cdecl Right-to-left Caller No
stdcall Right-to-left Routine No
safecall Right-to-left Routine No
Все регистры должны созраняться, кроме EAX в нем возвращается результат. Для register можно не заботиться об EAX, EDX, ECX
И еще важная тема Program control, там смотреть под темы.
ЗЫ: информации по сохранению регистров нет, поэтому лучше исходить из выше описаного, все сохранять, документировано только для register
← →
Ihor Osov'yak © (2004-06-23 01:39) [3]> За счет старших бит не беспокойся, стек всегда 32 бита, даже если байт на него помещается, просто не нужные биты не используются.
Это понятно, но здесь уже зависит от качества кода реализации метода.
А то, что гененрирует компилятор в этом случае для Flags: Word;class function TOpenHookIHTMLDocumentHolder.Invoke_Hook(DispID: Integer;
const IID: TGUID; LocaleID: Integer; Flags: Word; var Params; VarResult,
ExcepInfo, ArgErr: Pointer): HResult;
var
oldDoer: function(DispID: Integer;
const IID: TGUID; LocaleID: Integer; Flags: Word; var Params; VarResult,
ExcepInfo, ArgErr: Pointer): HResult of object; stdcall;
begin
TMethod(oldDoer).Code := oldIHTMLDocument2_InvokeProc;
TMethod(oldDoer).Data := self;
result := oldDoer(DispID, IID, LocaleID, Flags, Params,
VarResult, ExcepInfo, ArgErr);
end;
mov ax, [ebp+$18]
push ebx
здесь [ebp+$18] соотв. Flags: Word;
имхо, не есть хорошо - здесь мы отдаемся на добрую волю кодера, который делал код вызываемого метода. Никто ему не мешает при особом желании эти старшие биты заюзать.
Я бы ничего против не имел к примеру для такого результата компиляции:
xor eax, eax
mov ax, [ebp+$18]
push ebx
Но это так, между прочим.
По существу. Экспериментально определил, что в случае сохранения ebx, ecx и edx проблем не возникает. Код же
class function TOpenHookIHTMLDocumentHolder.Invoke_Hook
сбивал ecx и edx, мало того, трассировкой нашел место, где эта сладкая парочка после выхода с перехватчика использовалась, причем довольно странным таки способом -
add [ecx+$00],dl
jno +$08
результат таки довольно крутой оптимизации...
Пока что на той стадии, что перехватчик - чисто ассемблерная процедура, которая ничего не делает, а лишь прямой jmp на старый обработчик..
Буду думать сейчас как эти ebx , ecx и edx сохранить, и свою логику в перехватчик внедрить. Уж очень неохота чисто ассемблерную реализацию делать, но чувствую, придется. Ибо как то не могу сообоазить, как в делфийском релизе сохранить ebx, ecx и edx
Зы. финт TMethod(oldDoer).Data := self; делался сознательно и во вменяемом состоянии :-). Никто же не говорил, что OpenHookIHTMLDocumentHolder.Invoke_Hook вызывается напрямую :-). Собственно, адресс этого метода подставляется в таблицу методов интерфейса..
← →
Ihor Osov'yak © (2004-06-23 01:41) [4]Было
mov ax, [ebp+$18]
push ebx
Нужноmov ax, [ebp+$18]
push eax
сори..
← →
GuAV © (2004-06-23 02:28) [5]
> class function TOpenHookIHTMLDocumentHolder.Invoke_Hook(DispID:
> Integer;
> const IID: TGUID; LocaleID: Integer; Flags: Word; var Params;
> VarResult,
> ExcepInfo, ArgErr: Pointer): HResult;
> var
> oldDoer: function(DispID: Integer;
> const IID: TGUID; LocaleID: Integer; Flags: Word; var
> Params; VarResult,
> ExcepInfo, ArgErr: Pointer): HResult of object; stdcall;
Почему oldDoer stdcall, а TOpenHookIHTMLDocumentHolder - register ?
********
> Ибо как то не могу сообоазить, как в делфийском релизе сохранить
> ebx, ecx и edx
не уверен... уже почти сплю... но кажется можно так:
procedure AsmHook;
class function HookProcOnDelphi(dummy1,dummy2,dummy3: LongInt; DispID: Integer; const IID: TGUID; LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer):HResult; cdecl;
asm
PUSH EBX
PUSH ECX
PUSH EDX
CALL HookProcOnDelphi
POP EDX
POP ECX
POP EBX
PUSH EBX
PUSH ECX
PUSH EDX
CALL OldProc
POP EDX
POP ECX
POP EBX
end;
т.е. стек систит как надо OldProc, ни HookProcOnDelphi его не трогают.
← →
Ihor Osov'yak © (2004-06-23 05:27) [6]2 GuAV © (23.06.04 02:28) [5]
>Почему oldDoer stdcall, а TOpenHookIHTMLDocumentHolder - register ?
Опячятка. В разделе деклараций нормально, stdcall указано.. В секции реализации упустил, компилятор не обиделся.. Но он все же принял во внимание, что stdcall.
>но кажется можно так
Не, не совсем так.. Но Ваш вариант натолкнул меня на вполне приемлимый для моего случая прообраз решения:class function TOpenHookIHTMLDocumentHolder.Invoke_Hook2(dummy1,dummy2,dummy3: LongInt; DispID: Integer; const IID: TGUID; LocaleID: Integer;
Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult; cdecl;
begin
Log("=====> " + IntToStr(DispID));
....
end;
procedure SimpleHook2; assembler; { адресс этого подсовываем в соотв. место таблицы методов интерфейса}
asm
PUSH EBX
PUSH ECX
PUSH EDX
CALL TOpenHookIHTMLDocumentHolder.Invoke_Hook2
POP EDX
POP ECX
POP EBX
jmp oldIHTMLDocument2_InvokeProc
end;
А я то зацыклился на том, что старый метод должен вызываться обязательно из метода-перехватчика.. А вот такое решение без Вашей подсказки может и не сообразил бы... Спасибо. Особенно за идею с cdecl;
← →
Ihor Osov'yak © (2004-06-23 05:37) [7]ну и для конкретно случая вариант с конечным jmp на старый обработчик, кажется, едиственно приемлемый, из-за того, что похоже штатный обработчик Invoke все же ожидает, что в стеке перед ним не появилась лишняя спупенька, правда, не из-за любви к вложенным а-ля паскаль процедурам, там немного другая история..
← →
Digitman © (2004-06-23 08:14) [8]
> Ihor Osov"yak © (22.06.04 23:22)
> необходимости
> перехвата Invoke некоего наследника от IDispatch
а не проще ли перехватить все обращения к интересующему интерфейсу, воспользовавшись станд.механизмом делегирования интерфейсов ?
← →
Ihor Osov'yak © (2004-06-23 12:41) [9]2 [8] Digitman © (23.06.04 08:14)
> а не проще ли перехватить все обращения к интересующему интерфейсу, воспользовавшись станд.механизмом делегирования интерфейсов ?
Обьекты моего внимания - не мои обьекты (в смысле, не мой исходный код, это можно считать экземпляры несклольких "стандартных" inproc виндозных ком-серверов). При создании некоторого ком-обьекта он создает еще несколько иного типа ком-обьектов для поддержки своей функциональности, некоторые интерфейсы к которым также можно получить. Вот меня интересует и взаимодействие между этими обектами (как видите, часть из них мною даже не создаются). То есть я никак не могу вклиниться во внутреннию логику этих обьектов, чтобы в делегирование поиграть...
.. Меня интересует на даном этапе протоколирование некоторых взамодействий между этими обьектов , которые они осуществляют через вызовы методов интерфейсов друг друга. В перспективе - возможно иммуляция завершения с отрицательным результатом некоторых вызовов при некоторых обстоятельствах.
Штатные нотификационные интерфейсы, которые поддерживаются этими ком-обьектами не могут полностью удовлетворить моего любопытства и потребностей. Пока я ничего лучшего, чем перехват некоторых методов интерфейсов не придумал.
← →
Digitman © (2004-06-23 16:20) [10]
> Ihor Osov"yak © (23.06.04 12:41) [9]
мне любопытно, какой механизм перехвата интерфейсного вызова ты используешь в наст.момент ... поделись ...
← →
GuAV © (2004-06-23 17:10) [11]Ihor Osov"yak © (23.06.04 05:27) [6]
jmp oldIHTMLDocument2_InvokeProc
Хорошо придумано, я бы возможно долго к этому шел. ihmo почти универсальный подход - ничего кроме EAX не трогает но позволяет писать на Delphi а не на asm.
теперь, кстати и PUSH EBX/POP EBX не нужны.
> мне любопытно, какой механизм перехвата интерфейсного вызова
> ты используешь в наст.момент ... поделись ...
мне, кстати, тоже.
← →
Ihor Osov'yak © (2004-06-23 17:32) [12]да какой. Самый очевидный :-).
Сейчас.. Пока код ну совершенно неопрятен, так как стадия исследований в условиях цейтнота, и в перемешку с другой задачей, так что не обезсудь..
Ну не предначался он для обнародывания.. А облогораживать его я начну часов через три - поэтому даю как есть..
Надеюсь, реализацию можно отследить. Ставит перехватчик doTestHtmlDoc..
e0_log, e0_log.Log - запись в лог файл, с целью отладки.. Правда, пищу я туда пока почти что бред сумашедшего..unit hook_HTMLDoc;
interface
uses mshtml;
procedure doTestHtmlDoc(iDoc: IHtmlDocument2);
implementation
uses windows,
sysutils,
e0_log;
const
dfOffsetOpenIHTMLDocument2 = 61 * sizeof(DWORD); // offset to open method, open is the ..th entry in the vtbl
dfOffset_InvokeIHTMLDocument2 = 6 * sizeof(DWORD); //$18 offset to Invoke method
var
hookedIHTMLDocument2VMT: pointer;
oldIHTMLDocument2OpenProc: pointer;
oldIHTMLDocument2_InvokeProc: pointer;
type
TOpenHookIHTMLDocumentHolder = class
private
class function OpenIHTMLDocument2_Hook(const url: WideString; name: OleVariant; features: OleVariant;
replace: OleVariant): IDispatch; safecall;
class function GetAddr_OpenIHTMLDocument2_Hook: pointer;
class function Invoke_Hook(DispID: Integer; const IID: TGUID; LocaleID: Integer;
Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult; stdcall;
class function Invoke_Hook2(dummy1, dummy2, dummy3: LongInt; DispID: Integer; const IID: TGUID; LocaleID: Integer;
Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult; cdecl;
class function GetAddr_Invoke_Hook: pointer;
end;
{ TOpenHookIHTMLDocumentHolder }
class function TOpenHookIHTMLDocumentHolder.OpenIHTMLDocument2_Hook(const url: WideString; name: OleVariant; features: OleVariant;
replace: OleVariant): IDispatch; safecall;
var
oldDoer: function(const url: WideString; name: OleVariant; features: OleVariant;
replace: OleVariant): IDispatch of object; safecall;
begin
TMethod(oldDoer).Code := oldIHTMLDocument2OpenProc;
TMethod(oldDoer).Data := self; //pointer(intData);
result := oldDoer(url, name, features, replace);
end;
class function TOpenHookIHTMLDocumentHolder.GetAddr_OpenIHTMLDocument2_Hook: pointer;
var
hop: function(const url: WideString; name: OleVariant; features: OleVariant;
replace: OleVariant): IDispatch of object; safecall;
begin
hop := self.OpenIHTMLDocument2_Hook;
Result := TMethod(hop).Code;
end;
procedure SimpleHook; assembler;
asm
mov eax, oldIHTMLDocument2_InvokeProc
jmp eax
end;
procedure SimpleHook2; assembler;
asm
PUSH EBX
PUSH ECX
PUSH EDX
CALL TOpenHookIHTMLDocumentHolder.Invoke_Hook2
POP EDX
POP ECX
POP EBX
jmp oldIHTMLDocument2_InvokeProc
end;
procedure doTestHtmlDoc(iDoc: IHtmlDocument2);
var
p: pointer;
hdocIVMT: pointer;
hop: pointer;
op, op2: DWORD;
//iDisp: IDispatch;
tmpP: pointer;
begin
if not assigned(iDoc) then
exit;
move(integer(iDoc), p, sizeof(pointer));
move(p^, hDocIVMT, sizeof(pointer));
Log(" p = " + IntToHex(integer(p), 8) + "; hDocIVMT = " + IntToHex(integer(hDocIVMT), 8));
if not assigned(hookedIHTMLDocument2VMT) then
begin
Log(" **** Set hookedDocIVMT: " + IntToHex(integer(hDocIVMT), 8));
hookedIHTMLDocument2VMT := hDocIVMT;
p := pointer(integer(hookedIHTMLDocument2VMT) + dfOffsetOpenIHTMLDocument2);
move(p^, oldIHTMLDocument2OpenProc, sizeof(pointer));
hop := TOpenHookIHTMLDocumentHolder.GetAddr_OpenIHTMLDocument2_Hook;
if VirtualProtect(p, sizeof(pointer), PAGE_READWRITE, @op) then
begin
move(hop, p^, sizeof(pointer));
VirtualProtect(p, sizeof(pointer), op, @op2);
end
else
hookedIHTMLDocument2VMT := nil; // ? or generate raise
p := pointer(integer(hookedIHTMLDocument2VMT) + dfOffset_InvokeIHTMLDocument2);
move(p^, oldIHTMLDocument2_InvokeProc, sizeof(pointer));
hop := @SimpleHook2; //{@InvokeHookWraper; }//{@Skip;} TOpenHookIHTMLDocumentHolder.GetAddr_Invoke_Hook;
if VirtualProtect(p, sizeof(pointer), PAGE_READWRITE, @op) then
begin
move(hop, p^, sizeof(pointer));
VirtualProtect(p, sizeof(pointer), op, @op2);
end
else
hookedIHTMLDocument2VMT := nil; // ? or generate raise
end
else
begin
if hookedIHTMLDocument2VMT <> hDocIVMT then
begin
Log(" ************ ??????? Other iVTM: " + IntToHex(integer(hDocIVMT), 8));
end
else
begin
p := pointer(integer(hookedIHTMLDocument2VMT) + dfOffset_InvokeIHTMLDocument2);
move(p^, tmpP, sizeof(pointer));
if tmpP <> oldIHTMLDocument2_InvokeProc then
Log(" $$$$$$$$$ ??????? Other iVTM: " + IntToHex(integer(hDocIVMT), 8));
end;
end;
end;
class function TOpenHookIHTMLDocumentHolder.GetAddr_Invoke_Hook: pointer;
var
hop: function(DispID: Integer;
const IID: TGUID; LocaleID: Integer; Flags: Word; var Params; VarResult,
ExcepInfo, ArgErr: Pointer): HResult of object; stdcall;
begin
hop := self.Invoke_Hook;
Result := TMethod(hop).Code;
end;
class function TOpenHookIHTMLDocumentHolder.Invoke_Hook(DispID: Integer;
const IID: TGUID; LocaleID: Integer; Flags: Word; var Params; VarResult,
ExcepInfo, ArgErr: Pointer): HResult; stdcall;
var
oldDoer: function(DispID: Integer;
const IID: TGUID; LocaleID: Integer; Flags: Word; var Params; VarResult,
ExcepInfo, ArgErr: Pointer): HResult of object; stdcall;
begin
TMethod(oldDoer).Code := oldIHTMLDocument2_InvokeProc;
TMethod(oldDoer).Data := self;
result := oldDoer(DispID, IID, LocaleID, Flags, Params,
VarResult, ExcepInfo, ArgErr);
end;
class function TOpenHookIHTMLDocumentHolder.Invoke_Hook2(dummy1, dummy2, dummy3: LongInt; DispID: Integer; const IID: TGUID; LocaleID: Integer;
Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult; cdecl;
begin
Log("=========> " + IntToStr(DispID));
result := E_NOTIMPL;
end;
end.
Ключевой момент - получение указателя на таблицу методов интерфейсаmove(integer(iDoc), p, sizeof(pointer));
move(p^, hDocIVMT, sizeof(pointer));
ну а далее дело техники.
Еще один некрасивый момент - получение нужного смешения в таблице.
Копаться в премудностях IDL и вокруг него терпения не хватило - поэтому просто смотрю, какой код генерирует компилятор при вызове соотв. метода, далее прописываю как константу - см. к примеру dfOffset_InvokeIHTMLDocument2
В приведенном примере с OpenIHTMLDocument2_Hook проблем не возникало, возникли проблемы с Invoke_Hook - см. выше. Посему сейчас работает парочка
SimpleHook2 + Invoke_Hook2.. Вернее пока придерживаюсь мнения, что реализация будет идти в этом направлении.
← →
Ihor Osov'yak © (2004-06-23 17:36) [13]2 [11] GuAV © (23.06.04 17:10)
>jmp oldIHTMLDocument2_InvokeProc
>Хорошо придумано,
ну это.. детство в районах машинных кодов 8080, досовская юность :-).
Там такая техника вдоль и впоперек использовалась..
← →
GuAV © (2004-06-23 19:43) [14]Кстати а в stdcall self слева или справа? я к тому, что если слева, то реальный self в dummy3 а в self - лажа!
← →
GuAV © (2004-06-23 21:00) [15][14] - если есть такой момент, то исправить imho можно:
POP EAX
PUSH EBX
PUSH ECX
PUSH EDX
PUSH EAX
CALL TOpenHookIHTMLDocumentHolder.Invoke_Hook2
POP EAX
POP EDX
POP ECX
POP EBX
PUSH EAX
← →
Ihor Osov'yak © (2004-06-23 22:48) [16]2 [15] GuAV © (23.06.04 21:00)
Снова спасибо за подсказку (еще руки не дошли нарваться на соотв. баг), но снова решение не совсем так :-).
Дело в том, что в момент выполнения первого POP EAX в стеке в текущей позиции будет адрес возврата, а не значение self.
Корректным будет такое решение (уже проверялось отладчиком на предмет корректной передачи self):procedure SimpleHook2; assembler;
asm
mov eax, [esp+4] // self
PUSH EBX
PUSH ECX
PUSH EDX
push eax
CALL TOpenHookIHTMLDocumentHolder.Invoke_Hook2
pop eax
POP EDX
POP ECX
POP EBX
jmp oldIHTMLDocument2_InvokeProc
end;
← →
GuAV © (2004-06-23 23:00) [17]Вы правы. Да, и EAX то можно и не изменять если он нужен будет:
procedure SimpleHook2; assembler;
asm
PUSH EBX //зачем?
PUSH ECX
PUSH EDX
push eax
mov edx, [esp+4+12+4] // self
mov [esp], edx
CALL TOpenHookIHTMLDocumentHolder.Invoke_Hook2
pop EDX
POP EDX
POP ECX
POP EBX //зачем?
jmp oldIHTMLDocument2_InvokeProc
end;
зы - опять тут наверное баг - не в Дельфе же пишу, и не проверяю :)
← →
GuAV © (2004-06-23 23:12) [18]
> push eax
> mov edx, [esp+4+12+4] // self
> mov [esp], edx
имелось ввиду:mov edx, [esp+4+12+4] // self
:)
push edx
← →
GuAV © (2004-06-23 23:24) [19]Я как те хирурги, что сначала режут, а потом считают :(
mov edx, [esp+4+12] // self
push edx
← →
Ihor Osov'yak © (2004-06-25 01:47) [20]В общем-то тема оказалась более интересной и более "чреватой". Есть несколько довольно таки интересных вещей, которые элементарно превращаются на грабли..
Но первым делом немного причесанный код, который не чень "граблеустойчив", но который при известной степени осторожности можно брать за основу. После кода - немного комметнариев об этих граблях. Некоторые из них обойдены, некоторые - хорошо травкой притрушенны.unit e0_ihook;
{-$DEFINE DEBUG - moved to project options}
interface
function SetHookForMethod(aIntrf: pointer; aMethodDisp: integer;
aNewMethod: pointer;
var aOldIVMT, aOldMethod: pointer): integer;
implementation
uses windows,
sysutils,
{$IFDEF DEBUG}
e0_log
{$ENDIF}
;
function PointerToHex(p: pointer): string;
begin
result := IntToHex(integer(p), 8);
end;
function SetHookForMethod(aIntrf: pointer; aMethodDisp: integer;
aNewMethod: pointer;
var aOldIVMT, aOldMethod: pointer): integer; {-1 - ошибки при работе
0 - без изменений;
1 - изменения проведены}
var
wp: pointer;
pIVMT: pointer; // адресс таблицы методов
pMethod: pointer; // адресс метода
op, op2: DWORD;
begin
result := 0;
if not assigned(aIntrf) then
exit;
move(integer(aIntrf), wp, sizeof(pointer));
move(wp^, pIVMT, sizeof(pointer));
{$IFDEF DEBUG}
if pIVMT <> aOldIVMT then
begin
Log(PointerToHex(aIntrf) + ": iVTM : "
+ PointerToHex(aOldIVMT) + " -> " + PointerToHex(pIVMT));
end;
{$ENDIF}
if pIVMT <> aOldIVMT then
aOldIVMT := pIVMT;
wp := pointer(integer(pIVMT) + aMethodDisp);
move(wp^, pMethod, sizeof(pointer));
if aNewMethod <> pMethod then
begin
if VirtualProtect(wp, sizeof(pointer), PAGE_READWRITE, @op) then
begin
move(aNewMethod, wp^, sizeof(pointer));
VirtualProtect(wp, sizeof(pointer), op, @op2);
{$IFDEF DEBUG}
if aOldMethod <> pMethod then
begin
Log(PointerToHex(aIntrf) + ": mthd :"
+ PointerToHex(aOldMethod) + " -> " + PointerToHex(pMethod));
end;
{$ENDIF}
aOldMethod := pMethod;
result := 1;
end
else
begin
{$IFDEF DEBUG}
Log(PointerToHex(aIntrf) + ": can""t replace method...");
{$ENDIF}
result := -1;
end
end;
end;
end.
=====unit hook2_HTMLDoc;
interface
uses mshtml;
procedure SetHook_IHtmlDocument2(iDoc: IHtmlDocument2);
implementation
uses windows,
sysutils,
e0_ihook,
e0_log;
const
offset_Open_IHTMLDocument2 = 61 * sizeof(DWORD); // offset to open method, open is the ..th entry in the vtbl
offset_Invoke_IHTMLDocument2 = 6 * sizeof(DWORD); //$18 offset to Invoke method
var
lastHooked_IVMT: pointer; {таблица методов последнего перехваченного IHTMLDocument2;
для текущей задачи запоминать не нужно, просто любопытно, есть ли сия вещь постоянная -
для определления есть ли постоянство или нет храним последнее значение}
old_Open_Entry: pointer;
old_Invoke_Entry: pointer;
var
oldDispID: integer;
countOldDispID: integer;
function IDoc_Invoke_Hook(self: pointer; dummy1, dummy2, dummy3: LongInt;
DispID: Integer; const IID: TGUID; LocaleID: Integer;
Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult; cdecl;
begin
if oldDispID <> DispID then
begin
Log("== invoke ==> " + IntToStr(DispID) + " self = " + IntToHex(integer(self), 8) +
" coutOld = " + IntToStr(countOldDispID));
countOldDispID := 1;
oldDispID := DispID;
end
else
inc(countOldDispID);
result := E_NOTIMPL;
end;
procedure IDoc_Invoke_Hook_Wraper; assembler;
asm
mov eax, [esp+4] // self
PUSH EBX
PUSH ECX
PUSH EDX
push eax
//CALL IDoc_Invoke_Hook
pop eax
POP EDX
POP ECX
POP EBX
jmp old_Invoke_Entry
end;
procedure IDoc_Open_Hook(
//self: pointer;
result: pointer;
d0, dummy1, dummy2, dummy3 {, dextr}: LongInt;
self: pointer;
const url: WideString;
(*
name: OleVariant;
features: OleVariant;
replace: OleVariant);
*)
nameL, nameH: int64;
featuresL, featuresH: int64;
replaceL, replaceH: int64);
cdecl;
var
featuresCopy: OleVariant;
begin
move(featuresL, featuresCopy, sizeof(OleVariant));
Log("== open ==> URL = " + url + " " + featuresCopy + " self = " + IntToHex(integer(self), 8));
fillChar(featuresCopy, 0, sizeof(OleVariant));
end;
procedure IDoc_Open_Hook_Wraper; assembler;
asm
push eax
PUSH EBX
PUSH ECX
PUSH EDX
//push $55AA1122
CALL IDoc_Open_Hook
//pop eax
POP EDX
POP ECX
POP EBX
pop eax
jmp old_Open_Entry
end;
procedure SetHook_IHtmlDocument2(iDoc: IHtmlDocument2);
var
interfaceAsPointer: pointer absolute iDoc;
begin
if not assigned(iDoc) then
exit;
case SetHookForMethod(interfaceAsPointer, offset_Invoke_IHTMLDocument2,
@IDoc_Invoke_Hook_Wraper,
lastHooked_IVMT,
old_Invoke_Entry) of
-1:
begin
{$IFDEF DEBUG}
Log("**** error in SetHookForMethod for Invoke");
{$ENDIF}
end;
0: ;
1:
begin
{$IFDEF DEBUG}
Log("Invoke for IHTMLDocument2 replaced....");
{$ENDIF}
end;
end;
case SetHookForMethod(interfaceAsPointer, offset_Open_IHTMLDocument2,
@IDoc_Open_Hook_Wraper,
lastHooked_IVMT,
old_Open_Entry) of
-1:
begin
{$IFDEF DEBUG}
Log("**** error in SetHookForMethod for Open");
{$ENDIF}
end;
0: ;
1:
begin
{$IFDEF DEBUG}
Log("Open for IHTMLDocument2 replaced....");
{$ENDIF}
end;
end;
end;
end.
комментарии далее.
← →
Ihor Osov'yak © (2004-06-25 01:50) [21]Первый модуль содержит всего-навсего только процедуру модификации адреса соотв. метода в таблице методов интерфейса, там должно быть все очевидно.
Интерфейс передаем как обычный поинтер, чтобы компилятор не делал лишних движений по подсчету ссылок.
Второй модуль. Собственно реализация ловушек. Сделал отказ от классовой реализации в пользу процедурной. Как минимум - более понятно.
Здесь пока пример перехвата IHTMLDocoment2::Invoke и IHTMLDocoment2::Open
В переменных old_Open_Entry: pointer;
old_Invoke_Entry: pointer; - адреса оригинальных методов.
Вместо их в таблицы подставлено процедуры-хуки IDoc_Open_Hook, IDoc_Invoke_Hook.
В конце каждой процедуры-хука - джамп на оригинальные методы интерфейса. Поскольку мы сохраняем структуру стека – то по ret в оригинальном методе - возврат уже не на наший код, а код, который вызывал перехваченный метод. То есть туда, куда нужно.
Посреди хука - вызов нашей заглушки. Да, здесь я уже не стал играть с тем, чтобы self был на первом месте. Здесь все же не классовая реализация, а процедурная - то есть это уже не важно.
А теперь о граблях. Дело в том, что заглушка имеет все основания считать, что у нее в стеке - локальные параметры, и может делать с ними все, что ей заблагорассудится. Собственно, для Invoke проблем не возникло. А вот для Open (IDoc_Open_Hook)- таки да. Даже если IDoc_Open_Hook был полностью пустой.
Здесь несколько моментов. Первым делом, о тех, которые провоцируются нашей реализацией. Дело в том, что формальные параметры - OleVariant, передаваемые по значению. И естественно, компилятор генерирует код, который снимает копию с этого параметра. И имеет все право расположить копию в стеке, так как там как бы локальная переменная. Но он ведь не знает, что эту с его точки зрения локальную переменную мы потом передаем оригинальному методу.. :-(.
Отказываться напрочь от идеи GuAV с cdecl; пока не хочется, уж очень красиво в большинстве случаев получается.
Посему я пока пошел по пути немного надурить компилятор, объявив соотв. параметры не как OleVariant, а в случае необходимости сделать необходимые преобразования ручками. Собственно прообраз таких телодвижений в IDoc_Open_Hook на примере features.. Может в конечном итоге откажусь от такого подхода, но тока есть так.
А теперь еще одни грабли. Уже не зависящие от нашей реализации.
Дело в том, что штатно Open должен возвращать IDispatch. ячейка для возврата - в стеке. И поскольку там якобы интерфейс - то поскольку он будет замещен - то компилятор делфи предпринимает некоторые телодвижения для очистки этого интерфейса. Что, в общем - то и правильно. Но проблема в том, что с недр вызывающего кода в той ячейке приходит какой-то мусор, а не nil. Понятно, что получается из-за попытки очистить интерфейсную ссылку в данном случае. Обход этой проблемы и объясняет почему IDoc_Open_Hook декларирован как процедура с еще одним параметром (result: pointer;), который я пока не трогаю. Трогать придется потом, так как не исключено, что в некоторых случаях в реальной задаче оригинальный метод не будет нужно вызывать, а результат нужно будет формировать самому...
зы. Задача оказалась значительно интереснее и более тяжелой, чем казалось поначалу.
зы2. Не знаю, будет ли толк с этого проекта, но материал на очень уж хорошую статью соберу точно :-).
← →
Ihor Osov'yak © (2004-06-25 01:58) [22]Зы.
[10] Digitman © (23.06.04 16:20)
Мое графоманство немного cпровоцировано Вашим постигом :-). Был-бы признателен за маленький коментарий, если есть возможность.
2 GuAV ©
Вам спасибо за некоторые наводки.
Ps
> Собственно, для Invoke проблем не возникло.
Пока не возникало, для текущей реализации. Но там, естественно, также нужно ухо востро держать по отношению "локальных" параметров той процедуры, что вклиниваем перед вызовом оригинального метода. Собственно, в этом большой минус варианта от GuAV. Но впрочем, я не уверен, что откажусь от такого подхода. Все может быть.
← →
evvcom © (2004-06-25 08:22) [23]
> Но там, естественно, также нужно ухо востро держать по отношению
> "локальных" параметров той процедуры, что вклиниваем перед
> вызовом оригинального метода. Собственно, в этом большой
> минус варианта от GuAV.
Для пущей надежности перечислить параметры через const.
А вообще мне тоже вариант с cdecl понравился. Я делал перехваты функций из mfc42 (в терминологии Delphi методов классов, не знаю как это в Сях называется). Соглашения о вызове thiscall (во всяком случае IDA Pro их так обзвал). Там тоже надо было сохранять регистры, которые Delphi портил: ebx и ecx - указатель на объект класса. Пришлось извращаться с повторной передачей параметров в мою Hook процедуру, а вызов перехваченной функции из mfc тоже делал через jmp. А этот вариант с cdecl, конечно, намного элегантнее! Срочно переделывать! Спасибо за идею.
← →
Ihor Osov'yak © (2004-06-25 09:18) [24]> Для пущей надежности перечислить параметры через const.
Тогда некоторые параметры будут в стеке будут интрепритироваться как указатель. Для примера, в нашем случае вот такой зверь
name: OleVariant; - в стеке 4 32-битных, если же const name: OleVariant; - содержимое стека будет интерпритироваться как один поинтер (одно 32-битное). Но, впрочем, для тех параметров, где не происходит замены значения на поинтер в случае декларации их как const - вполне можно. Это, наверное, истинно для тех параметров, которые в стеке как одиночное 32- битное (integer, word, ets), нужно будет проверить и/или книжки почитать.
← →
evvcom © (2004-06-25 10:11) [25]Я не разбирался, как передается именно OleVariant, но другие сложные структуры данных, с которыми разбирался, все передавались как указатели. Только если не указывался var или const, в стеке выделялось место и копировалось туда содержимое по указателю. Дальнейшая работа естественно шла уже с локальной копией. Я не думаю, что OleVariant здесь будет исключением.
← →
GuAV © (2004-06-25 12:47) [26]
> Дело в том, что заглушка имеет все основания считать, что
> у нее в стеке - локальные параметры, и может делать с ними
> все, что ей заблагорассудится. Собственно
Ого! Ни фига себе! Какое он имеет право?
*****
А что если из cdecl-заглушки вызывать stdcall/pascal-процедуру? может тогда он перестанет трогать параметры cdecl.
Он же имхо должен сделать копию в стеке тока и всего. А для тех параметров, которые должны менятся в заглушке, дописать var.
← →
Ihor Osov'yak © (2004-06-25 13:07) [27]2 [25] evvcom © (25.06.04 10:11)
если OleVariant передается по значению - в стек загонится 4 32 битных значения, то есть тело OleVariant. Проверялось. Думаю, это не фича делфи, так как Olevariant не есть делфийский тип. Наверное здесь имеет место следование рекомендациям по работе с OleVariant. Фрейм начала процедуры анализирует тип OleVariant, видит, что там ссылка на строку. Раз ссылка - значит нужно снять копию (ведь мы работаеи по значению) с этой строки - что вполне правильно. Естественно, что тело OleVariant будет модифицироваться с целью указания на новую копию строки. И это правомерно, так как тело нашего OleVariant - в области копии фактических параметров, то есть в области, куда, по идее кроме кода процедуры никто не должен вклиниваться. Я сначала был немного в недоумении - но поразмыслив - все верно, в этом случае компилятор поступает абсолютно правомерно и абсолютно корректно. Это наша проблема, что мы делаем такие па вокруг стека.
2 [26] GuAV © (25.06.04 12:47)
> Ого! Ни фига себе! Какое он имеет право?
См. пред. абзац.
> А что если из cdecl-заглушки вызывать stdcall/pascal-процедуру? может тогда он перестанет трогать параметры cdecl.
Не думаю. Код для снятия копии генерируется даже для случая, когда соотв. параметр вообще не используется. Собственно, с этого повода можно было бы немного ворчать, но не более. Хотя даже и ворчать нельзя. А вдруг в нашем коде какие-то манипуляции вокруг указателей, ассемюлерных вставок, етс - и вследствии на этапе компиляции невозможно в общем случае определить, используется ли соотв. параметр. Так что тот факт что копия снимается по любому - наверное есть единственно правильным решением.
← →
GuAV © (2004-06-25 13:31) [28]Думал про cdecl. нда, таки есть проблемы. А что если другой подход:
type
T_HookParams=record
LongInt; DispID: Integer;
const IID: TGUID;
LocaleID: Integer;
Flags: Word;
var Params;
VarResult, ExcepInfo, ArgErr: Pointer
end;
procedure _Invoke_Hook(var Params: T_HookParams); register;
begin
end;
procedure _Invoke_Hook_Wraper; assembler;
asm
PUSH EBX
PUSH ECX
PUSH EDX
MOV EAX,ESP
ADD EAX,4+12
CALL _Invoke_Hook
POP EDX
POP ECX
POP EBX
jmp old__Entry
end;
← →
evvcom © (2004-06-25 15:21) [29]
> Ihor Osov"yak © (25.06.04 13:07) [27]
> 2 [25] evvcom © (25.06.04 10:11)
> если OleVariant передается по значению - в стек загонится
> 4 32 битных значения, то есть тело OleVariant. Проверялось.
Может я что-то упускаю из виду, сделал такой тест:
procedure test(Src: OleVariant);
begin
end;
procedure TForm1.Button1Click(Sender: TObject);
var local : OleVariant;
begin
local := CreateOleObject("Excel.Application");
test(local);
local := Unassigned;
end;
Для test(local); компилятор сгенерил
lea eax,[ebp-$18]
call test
т.е. передает ссылку, а во
...
mov esi,eax // указатель на источник Src
lea edi,[ebp-$10] // указатель на локальную копию
movsd
movsd
movsd
movsd // копирование тела OleVariant в локальную копию
lea eax,[ebp-$10]
call @VarAddRef // это, я так понимаю, увеличение счетчика ссылок
Т.е. при передаче по значению делается локальная копия сложной структуры и ее изменение вроде как никак не должно отразиться на оригинале. Что-то не так?
А... Сейчас изменил
procedure test(Src: OleVariant); stdcall;
Действительно в стек загоняется тело переменной (4 дв.слова), но во фрейме начала процедуры test все равно присутствует
call @VarAddRef
Дальнейшие рассуждения аналогичны. Может я неправильно понимаю работу @VarAddRef? Думаю, что при фактическом обращении на изменение локальной переменной произойдет то же, что и со стрингами (string), уменьшится счетчик ссылок, сделается реальная копия, и внесутся изменения.
← →
GuAV © (2004-06-25 15:41) [30]
> evvcom © (25.06.04 15:21) [29]
Вы кстати тоже [28] проверте. А то у меня проверять не на чем. (надо же - сколько ветка идет, а я в Дельфи ничего по этому поводу не писал/копировал)
← →
evvcom © (2004-06-25 17:11) [31]
> Вы кстати тоже [28] проверте.
Идею понял. Не проверяю, корректность смещений не считаю. Только всё это аналоги того, о чем уже писалось, т.е. все равно передаются указатели на те же параметры (var, const) или делается их копия (передача по значению).
← →
GuAV © (2004-06-25 17:17) [32]Да, но имхо это - через запись - намного проще и естественней, чем всякие cdecl/OleVariant. И тут никакой "логики компилятора", об которую можно споткнуться нет.
← →
evvcom © (2004-06-25 20:37) [33]
> GuAV © (25.06.04 17:17) [32]
Не спорю. С cdecl тоже довольно элегантно получилось. То, что в тексте пришлось перечислять довольно большое количество параметров, еще не означает увеличение машинного кода или усложнение чтения/понимания. Так что варианты на любителя, кому что понравится. Главное в этом всем, чтобы работало так, как нужно. Конечно, элегантность стоит не на последнем месте, но и не на первом.
А насчет правильной работоспособности Игорь выразил некоторые сомнения, которые мне не показались убедительными. Может я и не прав, но ответа пока нет.
С уважением...
← →
Ihor Osov'yak © (2004-06-25 21:05) [34]> А насчет правильной работоспособности Игорь выразил некоторые сомнения
да нет, я наоборот утверждал, что компилятор работает абсолютно корректно - компилятор он же не виноват, что мы пытаемся работать на грани фола, ну кроме мелкого ньюанса с заполнением хламом неиспользованые биты для 16-битного при передаче через стек по значению - да и то это нельзя считать некорректностью по большому счету..
Относительно OleVariant и передачи по значению - там немного интереснее и не только call @VarAddRef (во всяком случае на D6, под которым сие дело и проверялось), но сейчас нет времени подробно описывать - может и потом опишу .
Идея с записью тоже понравилась, само собою поэкспериментирую. Сейчас просто занят некоторой срочной работой, но через день - два возвращусь к этой теме.
← →
GuAV © (2004-06-25 21:05) [35]evvcom © (25.06.04 20:37) [33]
> Игорь выразил некоторые сомнения, которые мне не показались
> убедительными.
А мне - показались. Действительно, если параметры не var, то cdecl - подпрограмма не возвращает в них ничего. Т.е. изменяем их - а результат discarded. Если всё написать через var то это вообще бред будет. А в данном случае мы передаём запись по ссылке var - так она передается и в "нормальной ситуации" когда предполагается её изменение.
Вероятно или Вы не поняли, что Игорь хочет их менять или я чего-то не понял.
← →
Ihor Osov'yak © (2004-06-25 21:34) [36]2 GuAV
> Т.е. изменяем их - а результат discarded.
даже для cdecl - имхо, это есть нормальным, ведь саller обязан их очистить. Это наша проблема, что мы их не очищаем, а передаем дальше.
То есть я снова повторяю - я все же считаю, что в этом случае генерируется корректный код. Так как caller нам в стек помещает копию (так как передача параметра по значению) и по возвращению стек обязан очистить. Нигде не сказано, что caller будет использовать значения из этой уже отработанной с его точки зрения области стека. И если бы caller делал ставку на то, что эта область будет неизменной - он был бы слышком самоуверенным, хотя бы потому, чтобы пытался бы копию фактического параметра как-то обрабатывать. Ну не его это область, не его. А область памяти, где живут копии параметров, передаваемых по значению. И один их азов при передачи параметров по значению - параметр мы в процедуре меняем, как нам нужно, и эти изменения "не идут наружу". Так вот, стандартный фрейм входа изменил эти копии, как считал нужным. Без оглядки на caller. Ибо дело caller здесь - поместить в стек и забыть про то, что поместили. Ну, потом конечно вернуть указатель стека в нужное положение после возврати. А посколько caller должен забыть - то в процедуре мы, или компилятор делает с той копией все, что считаем нужным без оглядки на caller.
Другое дело, что в нашем решении вокруг cdecl мы делаем всякие танцы вокруг стека, которых с точки зрения компилятора быть не должно - так как мы немного нарушаем соглашения по правилам вызова. И соответственно - иногда нарываемся (для случая сложных данных, для которых делается "глубокая" копия). То есть прием с cdecl можно было бы применять для случая передачей параметров по ссылке, для случая передачи по значению - относительно смело для простых типов.. А вот со всякими сложными, для которых делается "глубокая" копия - есть ньюансы. Конечно, возникает вопрос с совместимостью с будущими версиями компиляторов, но учитывая, что для win32 новых компиляторов вероятнее всего не будет (для делфи - таки точно) - то и эту технику можно рискнуть использовать. И может бы и спользовал - но см. идею с рекордами - она как бы более "легальна". Поэтому попытаюсь применить ее.
← →
GuAV © (2004-06-26 14:05) [37]Еще мыслишки:
>ну и для конкретно случая вариант с конечным jmp
>на старый обработчик, кажется, едиственно приемлемый
Не единственный :) Есть второй вариант, избегающий лишней ступеньки стека:procedure SimpleHook2
asm
POP EAX
MOV DWORD PTR [@@_return_addr],EAX
..
CALL old__Entry
db 0e9h // JMP disp (4)
@@_return_addr: dd 0 // адрес возврата
end;
Этот вариант может быть полезен если нужно что-то делать после вызова старой процедуры.
************************************************
>procedure SimpleHook2;
>asm
В наиболее общем случае можно с помощью этой вставки процедуры привести любую модель вызова к любой при условии полной информации о этих моделях и передаваемых параметрах, т.к. никокой код не генерируется в её начале. Однако нужно приводить без информации о параметрах, чтобы унифицировать эту процедуру (cdecl и record способы подходят).
Унификация позволит избежать asm-ошибок. Кроме того возможна такая конструкцияtype
TSimpleHook2=record
code1: array[0..n1] of Byte;
InvokeHook: Pointer;
code2: array[0..n2] of Byte;
OldProc: Pointer;
code3: array[0..n3] of Byte;
end;
которая имхо может быть создана и заполнена в динамической памяти, что позволит уменьшить количество текста в исходном коде.
← →
GuAV © (2004-06-26 17:42) [38]Есть вариант по "тупиковому" направлению с cdecl. Его проблема в том, что компилятор использует стек, как счтитает нужным. Другими словами, осущствляет оптимизацию. А что если {$O-} ? Разумеется такой шаг приведет к определенным последствиям, чтобы их уменьшить из cdecl-процедуры можно вызывать другую процедуру у которой всё var и {$O+} ...
← →
GuAV © (2004-06-30 16:18) [39]обсуждение закрыто? тогда просьба выложить оеончательный результат...
← →
Ihor Osov'yak © (2004-07-01 00:26) [40]будет окончательный.. Через несколько дней..
Страницы: 1 2 вся ветка
Форум: "WinAPI";
Текущий архив: 2004.08.15;
Скачать: [xml.tar.bz2];
Память: 0.66 MB
Время: 0.054 c