Top.Mail.Ru
Мастера DELPHI, Delphi programming community Титульная страница Поиск, карта сайта Написать письмо 
| Новости | Поиск | FAQ | Статьи | Книги | Новости VCL |
| Форумы ЧАТ | Орешник | KOL и MCK |
Форумы -» Основная («Начинающим»)/ Базы / WinAPI / Компоненты / Сети / Media / Игры / Corba и COM / KOL / FreePascal / .Net / Прочее / RSDN.ORG

« предыдущая ветвь | форум | следующая ветвь »

Проблемы при перехвате IDispatch::Invoke


Страницы:   1 2 3

Ihor Osov'yak ©   (2004-06-22 23:22:00) 

При иследовании одной проблемы пришел к выводу о необходимости перехвата Invoke некоего наследника от IDispatch, если конкретно - IHtmlDocument2 (поначалу с целью протоколирования).

В общем-то с реализацией особых проблем не было, но.. После вызова старого обработчика Invoke, после выхода со своего перехватчика - довольно таки странный эксепшен. Причем корректноость постановки перехватика  проверялась  с помощью иследования стека вызовов - входные параметры старому методу передаются  идентичные тем, что идут на вход перехватчика (обнаружен некий интересный момент при передачи word - старшие 16 бит в стеке захламляются - но это отношения к проблеме не имеет - делался ручной сброс). Проверка делалась и при  установленом перехватчике, и при отключеном - в том и том случае на вход штатного метода Invoke идут одинаковые параметры.

Эксепшен возникает после выхода с кода перехватчика, где-то в глубине вызвавшего кода - довольно далеко, пока не хватило терпения детально протрассировать.
Пока есть две версии.
Первая, более вероятная. Возможно, что вызывавший код ставит более жеские требования по сохранению регистров, чем делфи. Вызывавший код, вероятнее всего написан в недрах майкрософт :-), может и на cипласплас, хотя возникают некие сомнения - уж очень не рыхлый там код, хотя вероятнее всего мною трасировалась пока только прокси соотв. ком-обьекта, а не сам обьект...
Итак, первый вопрос - какие требования по сохранению регистров в коде, написанном на msVC, с учетом того, что код имеет прямое отношение к COM, да и с поправкой на то, что разработчик мог делать предпосылку, что его вызов соотв. метода ну никак не будет перехвачвсться..

Вторая предпосылка, более с разряда фантастики. Используется что-то похожжее на технику вложенных процедур в паскале, то есть там делается попытка работать с локальными переменными в стеке обрамляющих процедур. Естественно, при этом делаются некоторые предпосылки о структуре стека. Мною же в стек добавлена одна ступень...
Ах, в чем вопрос. Хочу услышать критику этой второй версии как несостоятельной, чтобы не спешить ее прорабатывать..


GuAV   (2004-07-07 00:58:00) [1]


> В 9x вроде так пройдет

У меня не пошло. Юзаю 98 - консерватизьм. записал, но выполнился старый код.
Это видел в паскале процедура кажется IntNo в unit dos...
имхо это кэш проца - я в этом еще не особо разобрался...


evvcom   (2004-07-07 00:11:00) [2]


> Не пойдет, в код писать в винде оказывается нельзя :(

В 9x вроде так пройдет, а в NT можно, если снять защиту через VirtualProtect(Ex). И если не получится прямая запись после этого, то через WriteProcessMemory я уж точно писал.


GuAV   (2004-07-06 23:21:00) [3]

<off>
Уезжаю завтра. Просьба прислать окончательный результат на мыло (векта имхо не доживет до моего приезда)


GuAV   (2004-07-01 17:07:00) [4]

или даже так
procedure Something2;
begin
 ShowMessage('s2');
end;

procedure Something3;
begin
 ShowMessage('s3');
end;

procedure SimpleHook2; assembler;
asm
 PUSH EBX
 PUSH ECX
 PUSH EDX
 CALL Something
 POP EDX
 POP ECX
 POP EBX
 jmp Something1
end;

type
 PSimpleHook2=^TSimpleHook2;
 TSimpleHook2=packed record
   code1: array[1..4] of Byte;
   InvokeHook: Pointer;
   code2: array[1..4] of Byte;
   OldProc: Pointer;
 end;

procedure TForm1.Button1Click(Sender: TObject);
type TProc=procedure;
var Hook: PSimpleHook2;
begin
 New(Hook);
 Move((@SimpleHook2)^,Hook^,SizeOf(TSimpleHook2));
 Hook.InvokeHook:=Pointer(Integer(@Something2)-Integer(@Hook.InvokeHook)-4);
 Hook.OldProc:=Pointer(Integer(@Something3)-Integer(@Hook.OldProc)-4);

 TProc(Hook);
 Dispose(Hook);
end;


GuAV   (2004-07-01 16:56:00) [5]

...или даже так:
procedure Something2;
begin
 ShowMessage('s2');
end;

procedure Something3;
begin
 ShowMessage('s3');
end;

procedure SimpleHook2; assembler;
asm
 PUSH EBX
 PUSH ECX
 PUSH EDX
 CALL Something
 POP EDX
 POP ECX
 POP EBX
 jmp Something1
end;

type
 PSimpleHook2=^TSimpleHook2;
 TSimpleHook2=packed record
   code1: array[1..4] of Byte;
   InvokeHook: Pointer;
   code2: array[1..4] of Byte;
   OldProc: Pointer;
 end;

procedure TForm1.Button1Click(Sender: TObject);
type TProc=procedure;
var Hook: PSimpleHook2;
begin
 New(Hook);
 Move((@SimpleHook2)^,Hook^,SizeOf(TSimpleHook2));
 Hook.InvokeHook:=Pointer(Integer(@Something2)-Integer(@Hook.InvokeHook)-4);
 Hook.OldProc:=Pointer(Integer(@Something3)-Integer(@Hook.OldProc)-4);

 TProc(Hook);
 Dispose(Hook);
end;


GuAV   (2004-07-01 16:18:00) [6]

Рабочий код - пример создания процедуры в памяти (по крайней мере у меня рабочий :)

procedure Something;
begin
 ShowMessage('s');
end;

procedure Something1;
begin
 ShowMessage('s1');
end;

procedure Something2;
begin
 ShowMessage('s2');
end;

procedure Something3;
begin
 ShowMessage('s3');
end;

procedure SimpleHook2; assembler;
asm
 JMP @Start
 @Something_Addr: DD Something
 @Something1_Addr: DD Something1
 @Start:
 PUSH EBX
 PUSH ECX
 PUSH EDX
 CALL dword ptr [@Something_Addr]
 POP EDX
 POP ECX
 POP EBX
 jmp dword ptr [@Something1_Addr]
end;

type
 PSimpleHook2=^TSimpleHook2;
 TSimpleHook2=packed record
   code1: array[1..2] of Byte;
   InvokeHook: Pointer;
   OldProc: Pointer;
   code2: array[1..5] of Byte;
   InvokeHook__addr: Pointer;
   code3: array[1..5] of Byte;
   OldProc__addr: Pointer;
 end;

procedure TForm1.Button1Click(Sender: TObject);
type TProc=procedure;
var Hook: PSimpleHook2;
begin
 New(Hook);
 Move((@SimpleHook2)^,Hook^,SizeOf(TSimpleHook2));
 Inc(Integer(Hook.InvokeHook__addr), Integer(Hook) - Integer(@SimpleHook2));
 Inc(Integer(Hook.OldProc__addr), Integer(Hook) - Integer(@SimpleHook2));

 TProc(Hook);
 Hook.InvokeHook:=@Something2;
 Hook.OldProc:=@Something3;
 TProc(Hook);

 Dispose(Hook);
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
 SimpleHook2;
end;


GuAV   (2004-07-01 00:40:00) [7]


> procedure SimpleHook2
> asm
> POP EAX
> MOV DWORD PTR [@@_return_addr],EAX
> ..
> CALL old__Entry
> db 0e9h  // JMP disp (4)
> @@_return_addr: dd 0  // адрес возврата
> end;

Не пойдет, в код писать в винде оказывается нельзя :(

2 Ihor Osov'yak ©
Да, я понимаю, но не хочется чтобы ветка в архив упала, так что поддерживаем-с. :)
модераторы могут удалить [39] (создание путых сообщений) :)


Ihor Osov'yak   (2004-07-01 00:26:00) [8]

будет окончательный.. Через несколько дней..


GuAV   (2004-06-30 16:18:00) [9]

обсуждение закрыто? тогда просьба выложить оеончательный результат...


GuAV   (2004-06-26 17:42:00) [10]

Есть вариант по "тупиковому" направлению с cdecl. Его проблема в том, что компилятор использует стек, как счтитает нужным. Другими словами, осущствляет оптимизацию. А что если {$O-} ? Разумеется такой шаг приведет к определенным последствиям, чтобы их уменьшить из cdecl-процедуры можно вызывать другую процедуру у которой всё var и {$O+} ...


GuAV   (2004-06-26 14:05:00) [11]

Еще мыслишки:

>ну и для конкретно случая вариант с конечным 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;


которая имхо может быть создана и заполнена в динамической памяти, что позволит уменьшить количество текста в исходном коде.


Ihor Osov'yak   (2004-06-25 21:34:00) [12]

2 GuAV  
>  Т.е. изменяем их - а результат discarded.
даже для cdecl  - имхо, это есть нормальным, ведь саller обязан их очистить.  Это наша проблема, что мы их не очищаем, а передаем дальше.
То есть я снова повторяю - я все же считаю, что в этом случае генерируется корректный код. Так как caller нам в стек помещает копию(так как передача параметра по значению) и по возвращению стек обязан очистить.  Нигде не сказано, что caller будет использовать значения из этой уже отработанной с его точки зрения области стека. И если бы caller делал ставку на то, что эта область будет неизменной - он был бы слышком самоуверенным, хотя бы потому, чтобы пытался бы копию фактического параметра как-то обрабатывать. Ну не его это область, не его. А область памяти, где живут копии параметров, передаваемых по значению. И один их азов при передачи параметров по значению - параметр мы в процедуре меняем, как нам нужно, и эти изменения "не идут наружу". Так вот, стандартный фрейм входа изменил эти копии, как считал нужным. Без оглядки на caller. Ибо дело caller здесь - поместить в стек и забыть про то, что поместили. Ну, потом конечно вернуть указатель стека в нужное положение после возврати. А посколько caller должен забыть - то в процедуре мы, или компилятор делает с той копией все, что считаем  нужным без оглядки на caller.

Другое дело, что в нашем решении вокруг cdecl мы делаем всякие танцы вокруг стека, которых с точки зрения компилятора быть не должно - так как мы немного нарушаем соглашения по правилам вызова. И соответственно - иногда нарываемся (для случая сложных данных, для которых делается "глубокая" копия). То есть прием с cdecl можно было бы  применять для случая передачей параметров по ссылке, для случая передачи по значению - относительно смело для простых типов.. А вот со всякими сложными, для которых делается "глубокая" копия - есть ньюансы. Конечно, возникает вопрос с совместимостью с будущими версиями компиляторов, но учитывая, что для win32 новых компиляторов вероятнее всего не будет (для делфи - таки точно) - то и эту технику можно рискнуть использовать. И может бы и спользовал - но см. идею с рекордами - она как бы более "легальна". Поэтому попытаюсь применить ее.


GuAV   (2004-06-25 21:05:00) [13]

evvcom ©   (25.06.04 20:37) [33]


> Игорь выразил некоторые сомнения, которые мне не показались
> убедительными.

А мне - показались. Действительно, если параметры не var, то cdecl - подпрограмма не возвращает в них ничего. Т.е. изменяем их - а результат discarded. Если всё написать через var то это вообще бред будет. А в данном случае мы передаём запись по ссылке var - так она передается и в "нормальной ситуации" когда предполагается её изменение.
Вероятно или Вы не поняли, что Игорь хочет их менять или я чего-то не понял.


Ihor Osov'yak   (2004-06-25 21:05:00) [14]

> А насчет правильной работоспособности Игорь выразил некоторые сомнения

да нет, я наоборот утверждал, что компилятор работает абсолютно корректно - компилятор он же не виноват, что мы пытаемся работать на грани фола, ну кроме мелкого ньюанса с заполнением хламом неиспользованые биты для 16-битного при передаче через стек по значению - да и то это нельзя считать некорректностью по большому счету..

Относительно  OleVariant и передачи по значению - там немного интереснее и не только  call @VarAddRef (во всяком случае на D6, под которым сие дело и проверялось), но сейчас нет времени подробно описывать - может и потом опишу .  

Идея с записью тоже понравилась, само собою поэкспериментирую. Сейчас просто занят некоторой срочной работой, но через день - два возвращусь к этой теме.


evvcom   (2004-06-25 20:37:00) [15]


> GuAV ©   (25.06.04 17:17) [32]

Не спорю. С cdecl тоже довольно элегантно получилось. То, что в тексте пришлось перечислять довольно большое количество параметров, еще не означает увеличение машинного кода или усложнение чтения/понимания. Так что варианты на любителя, кому что понравится. Главное в этом всем, чтобы работало так, как нужно. Конечно, элегантность стоит не на последнем месте, но и не на первом.
А насчет правильной работоспособности Игорь выразил некоторые сомнения, которые мне не показались убедительными. Может я и не прав, но ответа пока нет.
С уважением...


GuAV   (2004-06-25 17:17:00) [16]

Да, но имхо это - через запись - намного проще и естественней, чем всякие cdecl/OleVariant. И тут никакой "логики компилятора", об которую можно споткнуться нет.


evvcom   (2004-06-25 17:11:00) [17]


> Вы кстати тоже [28] проверте.

Идею понял. Не проверяю, корректность смещений не считаю. Только всё это аналоги того, о чем уже писалось, т.е. все равно передаются указатели на те же параметры (var, const) или делается их копия (передача по значению).


GuAV   (2004-06-25 15:41:00) [18]


> evvcom ©   (25.06.04 15:21) [29]

Вы кстати тоже [28] проверте. А то у меня проверять не на чем. (надо же - сколько ветка идет, а я в Дельфи ничего по этому поводу не писал/копировал)


evvcom   (2004-06-25 15:21:00) [19]


> 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 13:31:00) [20]

Думал про 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;


Страницы:   1 2 3

Наверх

Титульная страница Поиск, карта сайта Написать письмо