![]() |
| ||
|
|
Новости |
Поиск |
FAQ |
Статьи |
Книги |
Новости VCL
| | Форумы ЧАТ |
Орешник |
|
| |||
| |||
« предыдущая ветвь | форум | следующая ветвь »
Проблемы при перехвате IDispatch::Invoke
Ihor Osov'yak © (2004-06-22 23:22:00)При иследовании одной проблемы пришел к выводу о необходимости перехвата Invoke некоего наследника от IDispatch, если конкретно - IHtmlDocument2 (поначалу с целью протоколирования).
В общем-то с реализацией особых проблем не было, но.. После вызова старого обработчика Invoke, после выхода со своего перехватчика - довольно таки странный эксепшен. Причем корректноость постановки перехватика проверялась с помощью иследования стека вызовов - входные параметры старому методу передаются идентичные тем, что идут на вход перехватчика (обнаружен некий интересный момент при передачи word - старшие 16 бит в стеке захламляются - но это отношения к проблеме не имеет - делался ручной сброс). Проверка делалась и при установленом перехватчике, и при отключеном - в том и том случае на вход штатного метода Invoke идут одинаковые параметры.
Эксепшен возникает после выхода с кода перехватчика, где-то в глубине вызвавшего кода - довольно далеко, пока не хватило терпения детально протрассировать.
Пока есть две версии.
Первая, более вероятная. Возможно, что вызывавший код ставит более жеские требования по сохранению регистров, чем делфи. Вызывавший код, вероятнее всего написан в недрах майкрософт :-), может и на cипласплас, хотя возникают некие сомнения - уж очень не рыхлый там код, хотя вероятнее всего мною трасировалась пока только прокси соотв. ком-обьекта, а не сам обьект...
Итак, первый вопрос - какие требования по сохранению регистров в коде, написанном на msVC, с учетом того, что код имеет прямое отношение к COM, да и с поправкой на то, что разработчик мог делать предпосылку, что его вызов соотв. метода ну никак не будет перехвачвсться..
Вторая предпосылка, более с разряда фантастики. Используется что-то похожжее на технику вложенных процедур в паскале, то есть там делается попытка работать с локальными переменными в стеке обрамляющих процедур. Естественно, при этом делаются некоторые предпосылки о структуре стека. Мною же в стек добавлена одна ступень...
Ах, в чем вопрос. Хочу услышать критику этой второй версии как несостоятельной, чтобы не спешить ее прорабатывать..
Ihor Osov'yak (2004-06-23 05:37:00) [41]ну и для конкретно случая вариант с конечным jmp на старый обработчик, кажется, едиственно приемлемый, из-за того, что похоже штатный обработчик Invoke все же ожидает, что в стеке перед ним не появилась лишняя спупенька, правда, не из-за любви к вложенным а-ля паскаль процедурам, там немного другая история..
Ihor Osov'yak (2004-06-23 05:27:00) [42]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;
GuAV (2004-06-23 02:28:00) [43]
> 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 01:41:00) [44]Было
mov ax, [ebp+$18]
push ebx
Нужно
mov ax, [ebp+$18]
push eax
сори..
Ihor Osov'yak (2004-06-23 01:39:00) [45]> За счет старших бит не беспокойся, стек всегда 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 вызывается напрямую :-). Собственно, адресс этого метода подставляется в таблицу методов интерфейса..
Anatoly Podgoretsky (2004-06-23 00:44:00) [46]За счет старших бит не беспокойся, стек всегда 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
Burmistroff (2004-06-23 00:37:00) [47]Ну так можно сразу расставить все точки: попробуй запомнить какие регистры должны быть в "хорошем" случае, затем - запусти "плохой" (перехватываемый) случай и на выходе в дебаггере установи "хорошие" значения регистров. Если прокатит - значит в этом дело.
По поводу второй версии - а шут этих MS знает. Вариант со стеком очень даже возможен, причем, как мне кажется, не обязательно даже должны быть и вложенные процедуры...