Полноценный вызов функций по указателю

Разговоры на любые темы: вы можете обсудить здесь какой-либо сайт, найти единомышленников или просто пообщаться...
GSerg
Шаман
Шаман
 
Сообщения: 14286
Зарегистрирован: 14.12.2002 (Сб) 5:25
Откуда: Магадан

Полноценный вызов функций по указателю

Сообщение GSerg » 12.08.2004 (Чт) 21:33

Громкое название, правда? :) Ну почти так оно и есть. Вдохновляют тут меня, понимаешь, всякие неспокойные элементы, не будем показывать пальцем... :)

Читали мою маленькую статейку про вызов по указателю, которая там где-то на www.vbstreets.ru? Так вот есть другой способ :) Я думал о нём при работе над способом первым, но тогда у меня ничего не получилось (теперь хоть понимаю почему), и этим путём я не пошёл.
Суть данного метода в том, что берётся реальная VB-функция из модуля (берётся – в смысле addressof ейный находится), и вот по этому самому адресу пишется команда безусловного перехода туда, куда нам нужно. В результате сих славных действий вызов этой функции (обычным VB-кодом) будет реально означать вызов чего-то другого. Скорость, опять же. От количества параметров не зависит вообще, потому что команда безусловного перехода не изменяет состояния стека, в котором уже запакованы (самим VB запакованы) нужные параметры и нужный адрес возврата.
Одна проблема: всё это не работает под IDE, но работает, будучи скомпилированным. Мне это не нравится :) И пришла в голову мысль: объединить эти два способа в один. Проблема была лишь в разной семантике вызовов функций, но эта проблема была снята условной компиляцией. Теперь все различия заключены в рамках одного модуля, и нужно просто менять константу #ReleaseBuild с false на true или наоборот. При false будет работать метод первый, через CallFunction, с ручной запаковкой параметров в стек. Это гораздо медленнее способа 2, но это работает под IDE, а значит, есть возможность отладки. А когда всё уже отлажено, заменяем на true, и в экзешник вместо CallFunction идёт jmp XXXXXXXX. При этом менять остальной код не нужно – с точки зрения внешнего пользователя вызовы двух способов неразличимы.
Единственное, что нужно сделать – создать необходимое количество плэйсхолдеров для функций (пустышек этих) в этом модуле. Они создаются один раз, потом уже о них особо не думаем, просто устанавливаем в нужные адреса (любое количество раз для каждой пустышки) и вызываем как обычные VB-функции. Делать их очень просто – копипастить, после чего изменить совсем чуть-чуть... Ну ладно, можно один и вручную, не надо :) К тому же, весьма легко создать VB-plugin, который будет генерировать код пустышки с нужным количеством параметров, просто мне сейчас неохота, но может завтра утром будет охота :) Но пока вручную. Формат – см. код.
Да! И обратите внимание на процедуру SetFunction. Видите там Select Case? Вот для каждой пустышки там должна быть строка. Дело в том, что для каждая пустышка (ReleaseBuild=False) хранит свой адрес в себе же, в Static-переменной. Я думал, а не пойти ли через коллекцию, но всё-таки так гораздо быстрее, на мой взгляд. Ну не 100 же у вас пустышек будет, правда? К тому же, может и этот код генерировать плагином :)

Прилагаемый пример вызывает GetWindowTextLength от своей формы. Просьба (уже в который раз) заценить и высказаться.


ЗЫ: ReleaseBuild = False работает и в IDE, и в exe. ReleaseBuild = True работает только в exe.
Вложения
Function pointer subst.zip
Вызов функций по указателю без потери отладки ;)
(2.72 Кб) Скачиваний: 214
Как только вы переберёте все варианты решения и не найдёте нужного, тут же обнаружится решение, простое и очевидное для всех, кроме вас

tyomitch
Пользователь #1352
Пользователь #1352
Аватара пользователя
 
Сообщения: 12822
Зарегистрирован: 20.10.2002 (Вс) 17:02
Откуда: חיפה

Сообщение tyomitch » 12.08.2004 (Чт) 22:18

Только одна мысль...

Под IDE мы или нет, проверяется довольно просто:
Код: Выделить всё
On Error Resume Next
Debug.Assert 1 / 0
If Err Then MsgBox "IDE" Else MsgBox "Compiled"
On Error GoTo 0

Почему бы не применить этот способ вместо условной компиляции? Имхо так проще.

GSerg
Шаман
Шаман
 
Сообщения: 14286
Зарегистрирован: 14.12.2002 (Сб) 5:25
Откуда: Магадан

Сообщение GSerg » 12.08.2004 (Чт) 22:21

Так в том и суть, чтобы не было в экзешнике всей этой фигни! :) Её там и не будет. Чистенькие пустышки с единственной командой возвращения нуля, никаких select case и вообще :)
Как только вы переберёте все варианты решения и не найдёте нужного, тут же обнаружится решение, простое и очевидное для всех, кроме вас

tyomitch
Пользователь #1352
Пользователь #1352
Аватара пользователя
 
Сообщения: 12822
Зарегистрирован: 20.10.2002 (Вс) 17:02
Откуда: חיפה

Сообщение tyomitch » 12.08.2004 (Чт) 22:34

Ну, тогда предлагаю (в аддоне?) сделать две опции - Optimize for Small Code и Optimize for Handy Code
;-)

GSerg
Шаман
Шаман
 
Сообщения: 14286
Зарегистрирован: 14.12.2002 (Сб) 5:25
Откуда: Магадан

Сообщение GSerg » 13.08.2004 (Пт) 7:43

Не, в данном случае я сторонник условной компиляции. Она правда рулит, поверь мне :)
Как только вы переберёте все варианты решения и не найдёте нужного, тут же обнаружится решение, простое и очевидное для всех, кроме вас

GSerg
Шаман
Шаман
 
Сообщения: 14286
Зарегистрирован: 14.12.2002 (Сб) 5:25
Откуда: Магадан

Сообщение GSerg » 14.08.2004 (Сб) 15:54

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

GSerg
Шаман
Шаман
 
Сообщения: 14286
Зарегистрирован: 14.12.2002 (Сб) 5:25
Откуда: Магадан

Сообщение GSerg » 31.08.2004 (Вт) 18:57

Обобщил всё сказанное сейчас и ранее, проапгрейдил add-in :)

http://www.vbstreets.ru/VB/Articles/66035.aspx
Как только вы переберёте все варианты решения и не найдёте нужного, тут же обнаружится решение, простое и очевидное для всех, кроме вас

Approximator
Постоялец
Постоялец
 
Сообщения: 572
Зарегистрирован: 26.06.2004 (Сб) 3:10

Сообщение Approximator » 03.09.2004 (Пт) 1:30

Фух, выдалась свободная минутка. Попробую изожить обещанные возражения.
Давно хотел сказать, что под IDE я не использую CallWindowProc, а оптимизацию в откомпилированном виде вижу в единожды сделанной локальной перелинковке в вспомогательной ActiveXDLL. Вам понравилась идея делать это каждый раз в Run-time... что ж, тоже вариант, но к нему есть пара замечаний:
во-первых, хотелось бы, чтобы адрес вызываемой функции был заменяемым;
во-вторых, если уж хотелось скорости, то надо было изменять совсем другое место.
Вот пример (вам нужны извраты? - их есть у меня!) в котором всё работает и в IDE, и в откомпилированном виде. Причем, ни под IDE, ни в откомпилированном виде не используется CallWindowProc, а WriteProcessMemory вызывается (для примера) только через маш.коды.

Ладно, не буду больше томить - любуйтесь. :)

Код: Выделить всё
Option Explicit

Private Declare Function FreeLibrary Lib "kernel32.dll" (ByVal hLibModule As Long) As Long
Private Declare Function GetProcAddress Lib "kernel32.dll" (ByVal hModule As Long, ByVal lpProcName As String) As Long
Private Declare Function LoadLibrary Lib "kernel32.dll" Alias "LoadLibraryA" (ByVal lpLibFileName As String) As Long
Private Declare Function GetCProc Lib "kernel32.dll" Alias "GetCurrentProcess" () As Long
Private Declare Function PutMem4 Lib "msvbvm60" (ByVal pDst As Long, ByVal NewValue As Long) As Long
Private Declare Sub CopyMem Lib "kernel32" Alias "RtlMoveMemory" _
(ByVal pDst As Long, ByVal pSrc As Long, ByVal ByteLen As Long)
Private Declare Function ArrPtr Lib "msvbvm60" Alias "VarPtr" (ByRef vArr() As Any) As Long

Private Type SAFEARRAYBOUND_L '8 bytes
p1_DSize As Long
p2_DLbound As Long
End Type

Private Type SAFEARRAY_L '24 bytes
p1_cDims As Integer
p2_fFeat As Integer
p3_ESize As Long
p4_cLocks As Long
p5_pvData As Long
Bounds As SAFEARRAYBOUND_L
End Type

Private MSsa As SAFEARRAY_L, MSa() As Byte, RDa() As Byte, WPMa() As Byte
Private pRet As Long, vESP As Long, kernel As Long, pWPM As Long, NCa() As Byte
Private pRD As Long, pT1 As Long, pT2 As Long

Private Sub Main()
MInit
CallT
MsgBox "Вернулись", vbOKOnly, "Sub Main"
FreeLibrary kernel
End Sub

Private Sub MInit()
InitSA
InitK
End Sub

Private Sub InitSA()
'Инициализируем массив
With MSsa
  .p1_cDims = 1
  .p2_fFeat = 128
  .p3_ESize = 1
  .p4_cLocks = 0
  .p5_pvData = GetPA(AddressOf Main) 'Сошлёмся на Sub Main "на всякий случай", поросто, чтобы не было неожиданностей...
  .Bounds.p1_DSize = 200
  .Bounds.p2_DLbound = 0
End With
PutMem4 ArrPtr(MSa), ByVal VarPtr(MSsa)
End Sub

Private Sub InitK()
kernel = LoadLibrary("kernel32.dll")
pWPM = GetProcAddress(kernel, "WriteProcessMemory")
pRD = GetPA(AddressOf ReDirect)
pT1 = GetPA(AddressOf Test1)
pT2 = GetPA(AddressOf Test2)
End Sub

Private Sub CallT()
Dim i As Long, lRes As Long
For i = 1 To 2
  If i = 1 Then
   lRes = ReDirect(pT1, 123456789)
  Else
   lRes = ReDirect(pT2, 123456789)
  End If
  MsgBox lRes, vbOKOnly, "CallT"
Next i
End Sub

Private Function GetPA(ByVal vProcAddress As Long) As Long
GetPA = vProcAddress
End Function

Private Function ReDirect(ByVal vNPA As Long, ByVal vVal As Long) As Long
Dim lNPA As Long
lNPA = vNPA
vESP = VarPtr(vVal) - 8
CopyMem VarPtr(pRet), vESP, 4
MSsa.p5_pvData = pRet - 5
If MSa(0) = &HE8 Then
 
  ReDim NCa(1 To 5)
  'pop  eax
  NCa(1) = &H67: NCa(2) = &H58
 
  'call eax
  NCa(3) = &H67: NCa(4) = &HFF: NCa(5) = &HD0
 
  WPM ByVal GetCProc, pRet - 5, VarPtr(NCa(1)), 5, ByVal 0&
   
  ReDim RDa(1 To 16)
  'push imm32=vVal
  RDa(1) = &H68: PutMem4 VarPtr(RDa(2)), vVal

  'push imm32=pRet
  RDa(6) = &H68: PutMem4 VarPtr(RDa(7)), pRet

  'jmp  dword ptr ds:[VarPtr(lNPA)]
  RDa(11) = &HFF: RDa(12) = &H25: PutMem4 VarPtr(RDa(13)), VarPtr(lNPA)
   
  PutMem4 vESP, VarPtr(RDa(1))

  MsgBox "Это откомпилированая программа" & vbCr & "все необходимые замены сделаны" & vbCr & _
         "сейчас пойдём в Test1 из ReDirect" & vbCr & _
         "ОДНАКО!!! Второй раз (у нас ведь цикл в Sub CallT), будет вызвана Test2" & vbCr & _
         "причём, прямо из Sub CallT!!!", vbOKOnly, "ReDirect"
Else
  vESP = vESP - 4
  CopyMem VarPtr(pRet), vESP, 4
  MSsa.p5_pvData = pRet - 2
  If MSa(0) = &HFF Then

   ReDim RDa(1 To 16)
   'push imm32=vVal
   RDa(1) = &H68: PutMem4 VarPtr(RDa(2)), vVal

   'push imm32=pRet
   RDa(6) = &H68: PutMem4 VarPtr(RDa(7)), pRet

   'jmp  dword ptr ds:[VarPtr(lNPA)]
   RDa(11) = &HFF: RDa(12) = &H25: PutMem4 VarPtr(RDa(13)), VarPtr(lNPA)

   PutMem4 vESP, VarPtr(RDa(1))
   
   MsgBox "Это IDE и этим всё сказано" & vbCr & "все необходимые замены сделаны" & vbCr & _
          "сейчас пойдём в Test1 из ReDirect" & vbCr & _
          "ОДНАКО!!! Второй раз (у нас ведь цикл в Sub CallT), будет вызвана Test2", vbOKOnly, "ReDirect"
  End If
End If
End Function

Private Sub WPM(ByVal vP1 As Long, ByVal vP2 As Long, ByVal vP3 As Long, ByVal vP4 As Long, ByVal vP5 As Long)

ReDim WPMa(1 To 14)

'sub   esp,imm8=20
WPMa(1) = &H83: WPMa(2) = &HEC: WPMa(3) = 20

'push  imm32=<адрес возврата>
WPMa(4) = &H68: CopyMem VarPtr(WPMa(5)), VarPtr(vP1) - 4, 4

'jmp   dword ptr ds:[VarPtr(pWPM)]
WPMa(9) = &HFF: WPMa(10) = &H25: PutMem4 VarPtr(WPMa(11)), VarPtr(pWPM)

PutMem4 VarPtr(vP1) - 4, VarPtr(WPMa(1))
End Sub

Private Function Test1(ByVal vVal As Long) As Long
MsgBox vVal, vbOKOnly, "Test1"
Test1 = vVal
End Function

Private Function Test2(ByVal vVal As Long) As Long
MsgBox vVal, vbOKOnly, "Test2"
Test2 = 987654321 - vVal
End Function
С уважением, Approximator.

GSerg
Шаман
Шаман
 
Сообщения: 14286
Зарегистрирован: 14.12.2002 (Сб) 5:25
Откуда: Магадан

Сообщение GSerg » 03.09.2004 (Пт) 17:31

Не разбираясь очень глубоко в ActiveXDll, отвечу сразу на замечания:

во-первых, SetFunction можно вызывать сколько угодно раз, меняя, таким образом, адрес, на который настроена функция.
во-вторых, скажи мне, что может быть быстрее записанного туда 1 (одного) jmp? :)
Как только вы переберёте все варианты решения и не найдёте нужного, тут же обнаружится решение, простое и очевидное для всех, кроме вас

Approximator
Постоялец
Постоялец
 
Сообщения: 572
Зарегистрирован: 26.06.2004 (Сб) 3:10

Сообщение Approximator » 04.09.2004 (Сб) 1:32

Не, Сергей, ты как-то неадекватно относишься к конструктивной критике.
GSerg писал(а):Не разбираясь очень глубоко в ActiveXDll, отвечу сразу на замечания:

Очень зря, что не разбираясь. Кстати, судя по ответам, извини за прямоту, ты ни в замечаниях, ни в примере СОВСЕМ НЕ РАЗОБРАЛСЯ.
Теперь подробно.
Во-первых, ты ничего не сказал, про то, что это всё работает и под IDE. Причём, безо всяких CallWindowProc. И могу поспорить, что столь же простыми способами хрен кто добъётся более быстрого и удобного способа переадресации в произвольное место адресного пространства программы. Оценил?
во-первых, SetFunction можно вызывать сколько угодно раз, меняя, таким образом, адрес, на который настроена функция.

И сколько это займёт времени? Каждый раз юзать WriteProcMemory, если из одного и того же места нужно многократно вызывать разные "функции" (в случае с передачей управления в произвольные маш.кодовые инструкции у меня такая ситуация возникает не так уж редко)? Уж, куда ни шло, если бы ты поставил туда не
Код: Выделить всё
jmp rel32

а хотя бы
Код: Выделить всё
jmp dword ptr ds:[VarPtr(SomeVariable)]

Тогда перадресацию твоего переключателя можно было бы осуществлять простым действием
Код: Выделить всё
SomeVariable=vNewAddress

во-вторых, скажи мне, что может быть быстрее записанного туда 1 (одного) jmp? :)

Ты забылся. Ничего смешного в твоём вопросе нет. Он лишь показывает, что ты либо совсем не смотрел мой пример, либо просто его не понял.
На самом деле у тебя идёт последовательность
Код: Выделить всё
Call rel32
jmp rel32

Если бы ты внимательно посмотрел мой пример, то заметил бы, что я всего лишь предлагаю заменить значение в
Код: Выделить всё
Call rel32

И всё, нет здесь НИКАКОГО ДОПОЛНИТЕЛЬНОГО jmp.
Что может быть быстрее jmp? - Когда его ВОВСЕ НЕТ.
Единственное, что я дополнил, так это совместил адресацию в произвольное место с быстрым вызовом.
И вместо
Код: Выделить всё
call rel32

я записал (прямо в то место из которого твой джамп вызывается!)
Код: Выделить всё
pop eax
call eax

Скажу тебе по-секрету, что даже в среде обычного ассемблера быстрее вызывать по произвольному адресу НЕВОЗМОЖНО.


P.S. :) Ещё раз. Убедительная просьба, посмотри пример внимательно. Ты пропустил очень много того, что является "очень вкусным". И, на мой взгляд, гораздо более вкусным, чем всё, что было предложено здесь (в этом топике) ранее. Особливо обрати внимание на переадресацию под IDE. :)
С уважением, Approximator.

Approximator
Постоялец
Постоялец
 
Сообщения: 572
Зарегистрирован: 26.06.2004 (Сб) 3:10

Сообщение Approximator » 04.09.2004 (Сб) 2:05

P.P.S. :) Ладно, если всё же не пронял и конструктив не пойдёт, тогда, как ещё выдстся свободная минутка напишу подробный "разбор полётов" в комментарии к твоей статье. :) Без обид?
С уважением, Approximator.

Approximator
Постоялец
Постоялец
 
Сообщения: 572
Зарегистрирован: 26.06.2004 (Сб) 3:10

Сообщение Approximator » 04.09.2004 (Сб) 5:12

Ну что, я уже разразился обещанным разбором полётов.
Для начала приведу его здесь

Комментарий к статье Сергея Гергерта «Вызов функции по указателю»
(http://www.vbstreets.ru/VB/Articles/66035.aspx)
Статья замечательная, однако, есть ряд неучтённых нюансов, которые делают предложенный метод не очень удобным.
Начнём с мелочей.
В качестве инструкции ветвления автор предлагает использовать безусловный переход по относительному адресу:
Код: Выделить всё
jmp   rel32

Однако, очевидно, что данный метод весьма неудобен по ряду причин. Если вдруг захочется изменить адрес, придётся заново обращаться к SetFunction, а это не очень удобно. Да и зачем это делать зазря :), если можно и вовсе обойтись без повторного обращения к SetFunction? Достаточно лишь использовать безусловный переход по адресу, содержащемуся в таком-то месте адресного пространства. Под «таким-то местом адресного пространства» я подразумеваю наибанальнейшую (глобальную) переменную. Ладно, :) от разговора на языке жестов перейдём к инструкции:
Код: Выделить всё
Public vCallAddress as Long, Ia() as Byte
ReDim Ia(1 to 6)
'jmp   dword ptr ds:[imm32=VarPtr(vCallAddress)]
Ia(1)=&HFF: Ia(2)=&H25: PutMem4 VarPtr(Ia(3)), VarPtr(vCallAddress)

Массив объявлен условно, лишь для того, чтобы показать, как это выглядит в машинных кодах.
На самом деле, чтобы адекватно заменить инструкцию в примере Сергея необходимо учитывать, что его инструкция занимает пять байт. Она устанавливается из SetFunction в PlaceholderFor1ParamFunction вместо находящихся там после линковки инструкций
Код: Выделить всё
xor   eax,eax
ret   4

которые тоже занимают пять байт. А наша инструкция занимает шесть байт. Но это очень просто поправить, достаточно переписать PlaceholderFor1ParamFunction в виде
Код: Выделить всё
Public Function PlaceholderFor1ParamFunction(ByVal p As Long) As Long
  #If ReleaseBuild Then
    PlaceholderFor1ParamFunction = 1&
  #Else
    Dim a As Long
    On Error Resume Next
    a = mCol(CStr(ReturnMe(AddressOf modFuncCall.PlaceholderFor1ParamFunction)))
    On Error GoTo 0
    If a Then PlaceholderFor1ParamFunction = CallFunction(a, 1, p)
  #End If
End Function

тогда при линковке туда будут записаны инструкции
Код: Выделить всё
mov   eax,imm32
ret   4

занимающие восемь байт, теперь перезаписываемый фрагмент будет выглядеть
Код: Выделить всё
ReDim Ia(1 to 8)
'jmp   dword ptr ds:[imm32=VarPtr(vCallAddress)]
Ia(1)=&HFF: Ia(2)=&H25: PutMem4 VarPtr(Ia(3)), VarPtr(vCallAddress)
'nop
'nop
Ia(7)=&H90: Ia(8)=&H90

Теперь, чтобы сделать переадресацию, не нужно больше обращаться к SetFunction, а достаточно всего лишь:
Код: Выделить всё
vCallAddress=vNewValue

и можно опять обращаться к PlaceholderFor1ParamFunction.

Это были мелкие замечания, так сказать, «по процедурному вопросу». Теперь хотелось бы поговорить об упущенных нюансах, имеющих, тем не менее, глобальный характер.
Не очень верным является мажорное высказывание автора в адрес CallWindowProc. Да, безусловно, чем-то эта функция хороша, однако, мне не кажется, что это удобный метод переадресации, тем более под IDE.
Какие будут мои аргУменты? Да очень простые! Давайте сначала вспомним, как вообще процессор «понимает» из какого места адресного пространства необходимо исполнять инструкцию. Для указания адреса исполняемой инструкции используется регистр eip. Причём нет инструкций явных чтения/записи из этого регистра. А в качестве неявно изменяющих содержимое регистра eip используются jmp, jxx и пары call–ret, enter–leave. VB’эшные компилятор и интерпретатор обычно используют jmp, jxx и пару call–ret. Последняя, как раз, нас и интересует. Выполнение инструкции
Код: Выделить всё
call operand

сводится.
Код: Выделить всё
push   eip
jmp   operand

или ещё подробнее к
Код: Выделить всё
mov   dword ptr ds:[esp–4],eip
sub   esp,4
mov   eip,operand

После того, как старое значение eip сохраняется в стеке (в регистре esp находится указатель на начало стека), а значение operand’а загружается в eip, процессор «сам по себе» переходит к выполнению инструкции, находящейся по этому адресу.
Итак «на вершине стека» у нас находится указатель на то место, куда будет необходимо вернуться после выполнения вызываемой функции. Возврат осуществляется инструкцией
Код: Выделить всё
ret   optional imm16

optional imm16 используется для очистки стека, если это необходимо. Развёрнутая интерпретация этой инструкции выглядит
Код: Выделить всё
add   esp,optional imm16+4
mov   eip,dword ptr ds:[esp–optional imm16–4]

Причём, если прочее содержимое стека не меняется, то место по адресу [esp–optional imm16–4] после этого уже не содержит адрес возврата. Теперь после маленького ликбеза «вернёмся к нашим баранам».
В среде VB из любой процедуры, независимо от того IDE это или откомпилированный файл, возврат ВСЕГДА осуществляется с помощью инструкции
Код: Выделить всё
ret   imm16

Таким образом, если внутри нашей процедуры изменить значение «на вершине стека», то вместо возврата произойдёт передача управления на тот адрес, который мы запишем по адресу, содержащемуся в esp. Но как узнать значение регистра esp, как узнать адрес начала стека? Заметьте, сам автор пишет: «перед передачей управления в функцию … стек приобретает вид…»
Но как будто он ничего при этом не замечает. Заметьте, что первым параметром в стеке является адрес возврата! Тот самый, по которому происходит возврат из функции при выполнении инструкции
Код: Выделить всё
ret   imm16

А где у нас находится начало стека при передаче управления, скажем, в такую функцию:
Код: Выделить всё
Private Function Test(byval vP1 as Long)as Long

End Function

Ну, разумеется, в откомпилированном виде начало стека будет находиться по адресу:
Код: Выделить всё
vESP = VarPtr(vP1) – 4

А в IDE, спросите вы, где будет начало стека при вызове функции в IDE? Ну, здесь всё очень просто. По ряду причин, которые я не хотел бы здесь обсуждать, вызов процедуры «из под» IDE осуществляется почти так же, как осуществляется в откомпилированном виде вызов функции из чужого ActiveX (кстати, думаю, что самые сметливые уже догадались, почему так происходит в IDE). Что это значит? Да вот что: начало стека и первый параметр разделяет ещё один параметр (указатель на адреса ObjTable). То есть начало стека находится следующим образом:
Код: Выделить всё
vESP = VarPtr(vP1) – 8

Теперь достаточно сохранить этот адрес в «какой-нибудь» (специально для этого предназначенной :)) переменной
Код: Выделить всё
Public pRet as Long
CopyMem VarPtr(pRet), vESP, 4

после этого по адресу vESP можно записать новый (необходимый нам) адрес.
И далее, процессор «сам по себе» переходит на адрес, скажем, на следующий переключатель в маш. кодах:
Код: Выделить всё
Public RDa() as Byte, pRD as Long
Private Sub InitRD()
ReDim RDa(1 to 16)
'восстанавливаем значение регистра esp
'sub   esp,imm8=4
RDa(1)=&H83: RDa(2)=&HEC: RDa(3)=4
'заново прописываем адрес возврата, предварительно сохранённый нами в pRet
'push   dword ptr ds:[imm32=VarPtr(pRet)]
RDa(4)=&HFF: RDa(5)=&H34: RDa(6)=&H25: PutMem4 VarPtr(RDa(7)), VarPtr(pRet)
'ну, теперь, наконец, можно перейти по интересующему нас адресу
'предварительно записанному нами в vCallAddress
'jmp   dword ptr ds:[imm32=VarPtr(vCallAddress)]
RDa(11)=&HFF: RDa(12)=&H25: PutMem4 VarPtr(RDa(13)), VarPtr(vCallAddress)
pRD=VarPtr(RDa(1))
End Sub

А тело функции Test необходимо оформить так, чтобы у нас внутри неё вычислялось значение pRet и вместо адреса возврата записывался указатель на наш переключатель. Тело функции должно выглядеть следующим образом:
Код: Выделить всё
Private Function Test(byval vP1 as Long)as Long
#If ReleaseBuild Then
  vESP = VarPtr(vP1) - 4
#Else
  vESP = VarPtr(vP1) - 8
#End If
CopyMem VarPtr(pRet), vESP, 4
PutMem4 vESP, pRD
End Function

И всё! И не нужны никакие CallWindowProc! Всё работает и под IDE, и в откомпилированном виде.

Теперь поговорим немного «о Сусанине». Если по каким-либо причинам переадресация, предложенная мною в откомпилированном виде кажется нежелательной (например, требуется большая скорость и не хочется терять даже десятка полтора тактов процессора на приведенный выше вариант, которые при частом обращении (например, в цикле!) могут сыграть значительную роль). Тогда можно пойти как тем поправленным вариантом предложенным выше мною, так и авторским. И всё же. Если уж многократное обращение происходит в цикле (а «потерянное» время бывает критичным только в этом случае), тогда можно извратиться несколько больше, чем предложил автор.
Дело в том, что для обращения к какой-либо «собственной» функции используется стандартная инструкция:
Код: Выделить всё
call   rel32

Если взять авторский вариант, то у нас получится последовательность вызовов
Код: Выделить всё
call   rel32=Offset PlaceholderFor1ParamFunction
jmp   rel32=Offset <Вызываемая функция>

или в поправленном мною выше варианте
Код: Выделить всё
call   rel32=Offset PlaceholderFor1ParamFunction
jmp   dword ptr ds:[imm32=VarPtr(vCallAddress)]

Ну, почему бы нам вообще не избавиться от «лишнего» jmp, коли уж так сильно поджимает нас время? И избавимся! Да ещё и будем ходить по условному адресу. Для этого напишем функцию переключателя
Код: Выделить всё
#If ReleaseBuild Then
Private Ra() as Byte
Public Function ReDirect(Optional ByVal vP0 As Long, Optional ByVal vP1 As Long) As Long
vESP=VarPtr(vP0)-4
CopyMem VarPtr(pRet), vESP, 4
ReDim Ra(1 to 7)
'call   dword ptr ds:[imm32=VarPtr(vCallAddress]
Ra(1)=&H3E: Ra(2)=&HFF: Ra(3)=&H15: PutMem VarPtr(Ra(4)), VarPtr(vCallAddress)
WriteProcessMemory GetCurProcess, pRet-7, VarPtr(Ra(1)), 7, 0&
PutMem4 vESP, pRD
End Function
#End If

а обращаться к этой функции будем
Код: Выделить всё
vCallAddress=vNewAddress
vReturnValue=ReDirect(,vParamValue)

«концовка» подобного обращения (из любого места VB-программы!) в маш. кодах выглядит
Код: Выделить всё
push   imm8=0
call   rel32

что занимает семь байт. Но инструкция
Код: Выделить всё
call   dwrod ptr ds:[imm32]

На самом деле занимает шесть байт. Что делать? Не вставлять же nop’ы в конце? Ведь это опять получится дополнительная, хотя и пустая (один такт) операция процессора. Есть простое решение указанная выше инструкция по умолчанию адресуется относительно регистра ds, достаточно приписать перед этой инструкцией «уточняющий» префикс &H3E, который будет делать тоже самое, но в явном виде. :) Что в итоге? В итоге имеем те самые семь байт.
Что же на самом деле делает функция ReDirect? При обращении к этой функции, она вычисляет адрес возврата уже описанным выше способом. Затем меняет предшествующие адресу возврата семь байт. Таким образом, если обращение из этого места происходит либо в цикле, либо просто часто, то в следующий раз обращение по нужному адресу (vCallAddress) будет происходить прямо оттуда! Кстати говоря, несмотря на то, что функция ReDirect, вроде бы имеет два параметра, при повторных обращениях задержки из-за пустого параметра всё равно не происходит, так как
Код: Выделить всё
push   imm8=0

мы затираем при первом обращении.
Есть ещё один не менее изящный, на мой взгляд, вариант.
Код: Выделить всё
#If ReleaseBuild Then
Private REa() as Byte
Public Function REx(ByVal vP0 As Long, ByVal vP1 As Long) As Long
vESP=VarPtr(vP0)-4
CopyMem VarPtr(pRet), vESP, 4
ReDim REa(1 to 5)
‘pop      eax
REa(1)=&H67: REa(2)=&H58
'call   eax
REa(3)=&H67: REa(4)=&HFF: REa(5)=&HD0
WriteProcessMemory GetCurProcess, pRet-5, VarPtr(REa(1)), 5, 0&
PutMem4 vESP, pRD
End Function
#End If

В этом случае обращение к функции REx должно выглядеть следующим образом:
Код: Выделить всё
vReturnValue=REx(vCallAddress,vParamValue)

То есть теперь инструкция
Код: Выделить всё
call   rel32

заменяется инструкциями
Код: Выделить всё
pop   eax
call   eax

Как мы помним инструкция
Код: Выделить всё
call   rel32

занимает пять байт, в то время, как наша замена – всего три. Как и выше, «недостающие байты» мы «добираем» за счёт «уточняющих» префиксов. В данном случае это префикс &H67. Его «безболезненно» можно ставить перед любой из наших инструкций. В итоге получаем искомые пять байт. Заметьте, что последний вариант при передаче управления по условному адресу выполняется максимально быстро. Если же, мы знаем, что обращение будет происходить на один и тот же адрес, то максимально быстро будет работать вот такой вариант
Код: Выделить всё
#If ReleaseBuild Then
Public vNewAddress as Long

Private Function GetPA(ByVal vProcAddress as Long)as Long
GetPA=vProcAddress
End Function

Public Function ReD(ByVal vP1 As Long) As Long
vESP=VarPtr(vP0)-4
CopyMem VarPtr(pRet), vESP, 4
'до обращения в vNewAddress должен содержаться «абсолютный» адрес
'после вычисления там будет содержаться относительный
vNewAddress=pRet-GetPA(AddressOf ReD)+vNewAddress
WriteProcessMemory GetCurProcess, pRet-4, VarPtr(vNewAddress), 4, 0&
PutMem4 vESP, pRD
End Function
#End If

Теперь обращение к этой функции обычное.
Код: Выделить всё
vNewAddress=<адрес вызываемой процедуры>

vReturnValue=ReD(vParamValue)

А что происходит в функции ReD? Просто в инструкции
Код: Выделить всё
call   rel32

перезаписывается само значение rel32.

Вот, собственно говоря, и все замечания. В прочих случаях, не требующих столь изощрённых методов оптимизации предлагаю пользоваться переадресацией альтернативной (если честно, то не очень-то поворачиваются пальцы на клавиатуре :) их сравнивать) CallWindowProc.
Последний раз редактировалось Approximator 05.09.2004 (Вс) 4:06, всего редактировалось 3 раз(а).
С уважением, Approximator.

GSerg
Шаман
Шаман
 
Сообщения: 14286
Зарегистрирован: 14.12.2002 (Сб) 5:25
Откуда: Магадан

Сообщение GSerg » 04.09.2004 (Сб) 11:00

Что я могу сказать? Только "не люблю глобальные переменные" :wink:

А вообще, для моего асм-уровня это слишком круто :)
Как только вы переберёте все варианты решения и не найдёте нужного, тут же обнаружится решение, простое и очевидное для всех, кроме вас

Approximator
Постоялец
Постоялец
 
Сообщения: 572
Зарегистрирован: 26.06.2004 (Сб) 3:10

Сообщение Approximator » 05.09.2004 (Вс) 1:33

GSerg писал(а):Что я могу сказать? Только "не люблю глобальные переменные" :wink:

Ну, не такие уж они и глобальные, чтобы ты сам такими же не пользовался. :)
А вообще, для моего асм-уровня это слишком круто :)

Я это понял. И решил переписать комментарий с более чёткими пояснениями. :) попробую заменить тот, что был выше, перечитай его потом ещё раз. Должно стать более понятным.
Да и вообще, ты просто скажи - заинтересовал ли тебя этот пример или нет. Если да, то имеет смысл это всё пожевать.
Кстати, ты правда не понял, как я переадресуюсь под IDE не используя CallWindowProc?
С уважением, Approximator.

Approximator
Постоялец
Постоялец
 
Сообщения: 572
Зарегистрирован: 26.06.2004 (Сб) 3:10

Сообщение Approximator » 05.09.2004 (Вс) 3:54

Позволю себе маленький наезд :)
:) А что, "генератору идей" Тёмычу нечего сказать по данному вопросу? Вы же оба хотели копактный и эффективный переключатель под IDE. И что теперь? GSerg, надеюсь, разберётся, а что, Тёмычу стало не интересно? :)
:) Кончай отсиживаться и высказывай свою точку зрения на пример и на "разбор полётов". :)
С уважением, Approximator.

tyomitch
Пользователь #1352
Пользователь #1352
Аватара пользователя
 
Сообщения: 12822
Зарегистрирован: 20.10.2002 (Вс) 17:02
Откуда: חיפה

Сообщение tyomitch » 05.09.2004 (Вс) 12:08

"Не шалю, никого не трогаю, починяю примус" (c)
Я ещё не разобрался детально, но про подмену адреса возврата понял. Типичный такой эксплойт :-)
Чё, прикольно конечно :-) Немножко освобожусь - разберусь подробнее.
Изображение

Approximator
Постоялец
Постоялец
 
Сообщения: 572
Зарегистрирован: 26.06.2004 (Сб) 3:10

Сообщение Approximator » 06.09.2004 (Пн) 1:24

Договорились. Тогда остальное (:) есть ведь ещё :)) тоже апосля разберём.
С уважением, Approximator.

Approximator
Постоялец
Постоялец
 
Сообщения: 572
Зарегистрирован: 26.06.2004 (Сб) 3:10

Сообщение Approximator » 09.09.2004 (Чт) 3:22

Хм... никто ничего не ответил.
Кстати, возвращаясь к своему изначальному предложению, я хотел бы показать, наконец, пример того, как я это делал до нашего с вами разговора.
Код: Выделить всё
Private Declare Function Call0 Lib "Asm.dll" Alias "CallEx" _
(ByVal pProc As Long) As Long
Private Declare Function Call1 Lib "Asm.dll" Alias "CallEx" _
(ByVal pProc As Long, ByVal vP1 As Long) As Long
Private Declare Function Call9 Lib "Asm.dll" Alias "CallEx" _
(ByVal pProc As Long, ByVal vP1 As Long, ByVal vP2 As Long, ByVal vP3 As Long, ByVal vP4 As Long, _
  ByVal vP5 As Long, ByVal vP6 As Long, ByVal vP7 As Long, ByVal vP8 As Long, ByVal vP9 As Long) As Long

Private Sub Main()
Dim vLR0 As Long, vLR1 As Long, vLR9 As Long

'способ первый и самый правильный
vLR0 = Call0(AddressOf Test0)
vLR1 = Call1(AddressOf Test1, 1122334455)
vLR9 = Call9(AddressOf Test9, 1, 2, 3, 4, 5, 6, 7, 8, 9)

MsgBox vLR0 & ", " & vLR1 & ", " & vLR9, vbOKOnly, "Результаты"

End
End Sub

Private Function Test0() As Long
Test0 = 1234567890
End Function

Private Function Test1(ByVal vP1 As Long) As Long
Test1 = vP1
End Function

Private Function Test9(ByVal vP1 As Long, ByVal vP2 As Long, ByVal vP3 As Long, ByVal vP4 As Long, ByVal vP5 As Long, _
                       ByVal vP6 As Long, ByVal vP7 As Long, ByVal vP8 As Long, ByVal vP9 As Long) As Long
Test9 = vP1 + vP2 + vP3 + vP4 + vP5 + vP6 + vP7 + vP8 + vP9
Test9 = Test9 / 9
End Function

На мой взгляд, самый верный и самый быстрый из надёжных способов.
Asm.dll прилагается :)

Может, хотя бы об этом примере, кто-нибудь скажет что-нибудь конкретное? :)
Вложения
Asm.zip
(538 байт) Скачиваний: 231
С уважением, Approximator.

tyomitch
Пользователь #1352
Пользователь #1352
Аватара пользователя
 
Сообщения: 12822
Зарегистрирован: 20.10.2002 (Вс) 17:02
Откуда: חיפה

Сообщение tyomitch » 09.09.2004 (Чт) 9:57

Так, к слову...
В Win9x в kernel32 есть все нужные функции - Callback4, Callback8, Callback12, и так далее примерно до Callback48 (могу ошибиться с верхней границей).
Т.е. там можно и без Asm.dll
Изображение

Approximator
Постоялец
Постоялец
 
Сообщения: 572
Зарегистрирован: 26.06.2004 (Сб) 3:10

Сообщение Approximator » 10.09.2004 (Пт) 1:33

tyomitch писал(а):Так, к слову...
В Win9x в kernel32 есть все нужные функции - Callback4, Callback8, Callback12, и так далее примерно до Callback48 (могу ошибиться с верхней границей).
Т.е. там можно и без Asm.dll


Не, Тёмыч. Скажи, ну неужели так непонятно написан пример? Не, правда, что совсем не ясно? Я ВСЁ ВРЕМЯ использую ТОЛЬКО ОДНУ функцю из Asm.dll. Просто, если мне нужно послать десять параметров я объявляю её с десятью параметрами, если нужно послать без параметров объявляю опять её же, но без параметров. Упомянутые тобой функции из kernel32 так позволяют?
С уважением, Approximator.

tyomitch
Пользователь #1352
Пользователь #1352
Аватара пользователя
 
Сообщения: 12822
Зарегистрирован: 20.10.2002 (Вс) 17:02
Откуда: חיפה

Сообщение tyomitch » 10.09.2004 (Пт) 2:08

Да я их так, к слову упомянул, а вовсе не как "конкуренцию" для Asm.dll :silent:
Кстати, а какая принципиальная разница, одна там функция, или много? Всё равно по одному объявлению на каждое число параметров, так?
Изображение

Approximator
Постоялец
Постоялец
 
Сообщения: 572
Зарегистрирован: 26.06.2004 (Сб) 3:10

Сообщение Approximator » 10.09.2004 (Пт) 4:42

tyomitch писал(а):Да я их так, к слову упомянул, а вовсе не как "конкуренцию" для Asm.dll :silent:
Кстати, а какая принципиальная разница, одна там функция, или много? Всё равно по одному объявлению на каждое число параметров, так?

:) Ты шутишь? Ведь в откомпилированном виде уже будет плевать сколько было объявлений VB их использует только для того, чтобы заполнить стек, а дальше всё зависит от того, куда шлёшь...
А что ещё проще простого объявления функции можно придумать? "Универсальная функция" в VB либо будет работать медленнее, либо потребует перенесения на её пользователя заботу о предварительной подготовке данных. А так это ОДИНАКОВО быстро работает под IDE и в откомпилированном виде. Вспомни с чего начался весь этот "сыр-бор". С того, что у Сергея "асмовские" функции сдвига работали медленнее. Он нашёл выход, но под IDE эта проблема так и осталась им неразрешённой. Когда я ознакомился с его методом, меня, конечно, обуял азарт на поиски всякого "изврата" работающего быстро и под IDE и в откомпилированном виде....
Но если не извращаться и вернуться к высказанной мною первоначальной идее, то вот она. Сама простота и эффективность. Разве нет?

P.S. Если вам обоим порой кажется, что я докалупываюсь, так это вовсе не из вредности (хотя, профессиональная вредность место быть имеет). А по простой причине, если я чувствую, что человек роет и ему это нравится, а мне тема его изысканий небезынтересна, то со своей стороны, считаю необходимым предложить что-нибудь, если то кто роет что-то пропустил... и всё.
С уважением, Approximator.

tyomitch
Пользователь #1352
Пользователь #1352
Аватара пользователя
 
Сообщения: 12822
Зарегистрирован: 20.10.2002 (Вс) 17:02
Откуда: חיפה

Сообщение tyomitch » 10.09.2004 (Пт) 12:37

Approximator, я не шучу, я просто не догоняю сразу :-)
Объясни пожалуйста, почему одна функция - лучше чем много?
"Для успеха два ореха лучше, чем один" (с) :-)
Изображение

GSerg
Шаман
Шаман
 
Сообщения: 14286
Зарегистрирован: 14.12.2002 (Сб) 5:25
Откуда: Магадан

Сообщение GSerg » 10.09.2004 (Пт) 18:14

Одна функция, наверное, лучше потому, что меньше места занимает :)
Роющим ради процесса привет и понимание :)
А у Сергея, хоть и медленно работает под IDE, но какое это имеет значение, под IDE-то? Нет, это не повод, чтобы не рыть :) Но потому я на это и забил, что под IDE не так уж это и важно.
Как только вы переберёте все варианты решения и не найдёте нужного, тут же обнаружится решение, простое и очевидное для всех, кроме вас

Approximator
Постоялец
Постоялец
 
Сообщения: 572
Зарегистрирован: 26.06.2004 (Сб) 3:10

Сообщение Approximator » 11.09.2004 (Сб) 1:15

tyomitch писал(а):Approximator, я не шучу, я просто не догоняю сразу :-)
Объясни пожалуйста, почему одна функция - лучше чем много?
"Для успеха два ореха лучше, чем один" (с) :-)


GSerg писал(а):Одна функция, наверное, лучше потому, что меньше места занимает
Роющим ради процесса привет и понимание
А у Сергея, хоть и медленно работает под IDE, но какое это имеет значение, под IDE-то? Нет, это не повод, чтобы не рыть Но потому я на это и забил, что под IDE не так уж это и важно.


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

P.S. Рыть НЕ ТОЛЬКО ради результата, но и ради процесса? Приветствую всячески. Рыть вообще ИСКЛЮЧИТЕЛЬНО ради процесса, потому что нравится рыть и не внимать альтернативным вариантам НИКАК. Смахивает на созерцание пупка у себя любимого...
С уважением, Approximator.

GSerg
Шаман
Шаман
 
Сообщения: 14286
Зарегистрирован: 14.12.2002 (Сб) 5:25
Откуда: Магадан

Сообщение GSerg » 11.09.2004 (Сб) 9:04

Да нет, ну почему сразу хуже... Никто же не критикует :)

ЗЫ: Если рыть - то это как раз внимать альтернативным вариантам. Даже если только ради процесса :) Кстати, потом часто оказывается, что нарытое в процессе рытья ради процесса (:D) весьма полезно и применимо...
Как только вы переберёте все варианты решения и не найдёте нужного, тут же обнаружится решение, простое и очевидное для всех, кроме вас

Approximator
Постоялец
Постоялец
 
Сообщения: 572
Зарегистрирован: 26.06.2004 (Сб) 3:10

Сообщение Approximator » 12.09.2004 (Вс) 1:43

GSerg писал(а):Да нет, ну почему сразу хуже... Никто же не критикует :)

ЗЫ: Если рыть - то это как раз внимать альтернативным вариантам. Даже если только ради процесса :) Кстати, потом часто оказывается, что нарытое в процессе рытья ради процесса (:D) весьма полезно и применимо...


Если серьёзно, Сергей, то конечной целью у меня было предложить написать тебе совместную статью на VBStreets, посвящённую этому и не только вопросам. В частности, я не удовлетворён результатом вашей с Дмитрием Кудицким беседы. У меня есть контрагрументы и замечания по некорректности самой постановки некоторых его вопросов. В силу того, что моя основная деятельность находится, мягко говоря, в стороне от частных вопросов программирования, я вижу необходимым написать статью в соавторстве с тобой, как человеком более заинтересованным.


Потому-то мне хотелось бы более пристального внимания к предложенным вариантам. Нужно было обкатать и понять общее в наших взглядах на эту проблему... Название-то топика с разумной претензией "Полноценный вызов..." А раз так - нужно держать марку. :)

P.S. Представляете, как мы с моими "товарищами по оружию" "пинаем" друг друга при "обкатке" кем-либо новой темы на семинарах? :)
С уважением, Approximator.

GSerg
Шаман
Шаман
 
Сообщения: 14286
Зарегистрирован: 14.12.2002 (Сб) 5:25
Откуда: Магадан

Сообщение GSerg » 12.09.2004 (Вс) 11:15

О, пинание товарищей в процессе совместного творчества мне хорошо известено :)

Писать? Напишем :) Моё мыло видишь? Видишь :)

Кудицкий? Да, мне тоже не все вопросы нравятся. Но их задавал, и я пытался отвечать.
Как только вы переберёте все варианты решения и не найдёте нужного, тут же обнаружится решение, простое и очевидное для всех, кроме вас

gaidar
System Debugger
System Debugger
 
Сообщения: 3152
Зарегистрирован: 23.12.2001 (Вс) 13:22

Сообщение gaidar » 12.09.2004 (Вс) 12:53

Approximator писал(а):P.S. Представляете, как мы с моими "товарищами по оружию" "пинаем" друг друга при "обкатке" кем-либо новой темы на семинарах? :)


Аж страшно становиться :) Но нам не привыкать, саим такие же.

ВОт читаю топик и думаю, куда это все приведет :)
The difficult I’ll do right now. The impossible will take a little while. (c) US engineers in WWII
I don't always know what I'm talking about, but I know I'm right. (c) Muhammad Ali

GSerg
Шаман
Шаман
 
Сообщения: 14286
Зарегистрирован: 14.12.2002 (Сб) 5:25
Откуда: Магадан

Сообщение GSerg » 12.09.2004 (Вс) 13:35

А чё, мы ничё, мы по мелочи :D
VB8 напишем и успокоимся :)
Как только вы переберёте все варианты решения и не найдёте нужного, тут же обнаружится решение, простое и очевидное для всех, кроме вас

След.

Вернуться в Народный треп

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 51

    TopList