Russian English
   Basic.net
Среда, 23.09.2020, 02:33
Меню сайта
Категории раздела
Basic [44]
Помощь [0]
Облако тегов
Статистика

Онлайн всего: 1
Гостей: 1
Пользователей: 0

Форма входа
Главная » Статьи » Basic

Вызов функций по указателю в Visual Basic (часть 4)
Share |
Комментарии
Статья замечательная, однако, есть ряд неучтённых нюансов, которые делают предложенный метод не очень удобным.

Начнём с мелочей.
В качестве инструкции ветвления автор предлагает использовать инструкцию ветвления - безусловный переход по относительному адресу:
Код ASM
1
jmp  rel32
Однако, очевидно, что данный метод весьма неудобен по ряду причин. Если вдруг захочется изменить адрес, придётся заново обращаться к SetFunction, а это не очень удобно. Да и зачем это делать зазря , если можно и вовсе обойтись без повторного обращения к SetFunction? Достаточно лишь использовать безусловный переход по адресу, содержащемуся в таком-то месте адресного пространства. Под <таким-то местом адресного пространства> я подразумеваю наибанальнейшую (глобальную) переменную. Ладно, от разговора на языке жестов перейдём к инструкции:
Код Visual Basic
1
2
3
4
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 вместо находящихся там после линковки инструкций
Код ASM
1
2
xor  eax,eax
ret  4
которые тоже занимают пять байт. А наша инструкция занимает шесть байт. Но это очень просто поправить, достаточно переписать PlaceholderFor1ParamFunction в виде

Код Visual Basic
1
2
3
4
5
6
7
8
9
10
11
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
тогда при линковке туда будут записаны инструкции
Код Visual Basic
1
2
mov  eax,imm32
ret  4
занимающие восемь байт, теперь перезаписываемый фрагмент будет выглядеть
Код Visual Basic
1
2
3
4
5
6
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, а достаточно всего лишь:
Код Visual Basic
1
vCallAddress=vNewValue
и можно опять обращаться к PlaceholderFor1ParamFunction.

Это были мелкие замечания, так сказать, <по процедурному вопросу>. Теперь хотелось бы поговорить об упущенных нюансах, имеющих, тем не менее, глобальный характер.
Не очень верным является мажорное высказывание автора в адрес CallWindowProc. Да, безусловно, чем-то эта функция хороша, однако, мне не кажется, что это удобный метод переадресации, тем более под IDE.
Какие будут мои аргУменты? Да очень простые! Давайте сначала вспомним, как вообще процессор <понимает> из какого места адресного пространства необходимо исполнять инструкцию. Для указания адреса исполняемой инструкции используется регистр eip. Причём, нет инструкций явных чтения/записи из этого регистра. А в качестве неявно изменяющих содержимое регистра eip используются jmp, jxx и пары call-ret, enter-leave. VB'эшные компилятор и интерпретатор обычно используют jmp, jxx и пару call-ret. Последняя, как раз, нас и интересует. Выполнение инструкции
Код ASM
1
call operand
сводится.
Код ASM
1
2
push eip
jmp  operand
или ещё подробнее к
Код ASM
1
2
3
mov  dword ptr ds:[esp-4],eip
sub  esp,4
mov  eip,operand
После того, как старое значение eip сохраняется в стеке (в регистре esp находится указатель на начало стека), а значение operand'а загружается в eip, процессор <сам по себе> переходит к выполнению инструкции, находящейся по этому адресу.
Итак <на вершине стека> у нас находится указатель на то место, куда будет необходимо вернуться после выполнения вызываемой функции. Возврат осуществляется инструкцией
Код ASM
1
ret  optional imm16
optional imm16 используется для очистки стека, если это необходимо.
Развёрнутая интерпретация этой инструкции выглядит
Код ASM
1
2
add  esp,optional imm16+4
mov  eip,dword ptr ds:[esp-optional imm16-4]
Причём, если прочее содержимое стека не меняется, то место по адресу [esp-optional imm16-4] после этого уже не содержит адрес возврата. Теперь после маленького ликбеза <вернёмся к нашим баранам>.
В среде VB из любой процедуры, независимо от того IDE это или откомпилированный файл, возврат ВСЕГДА осуществляется с помощью инструкции
Код ASM
1
ret  imm16
Таким образом, если внутри нашей процедуры изменить значение <на вершине
стека>, то вместо возврата произойдёт передача управления на тот адрес,
который мы запишем по адресу, содержащемуся в esp. Но как узнать значение регистра esp, как узнать адрес начала стека? Заметьте, сам автор пишет:
<перед передачей управления в функцию : стек приобретает вид:>
Но как будто он ничего при этом не замечает. Заметьте, что первым параметром в стеке является адрес возврата! Тот самый, по которому происходит возврат из функции при выполнении инструкции
Код ASM
1
ret  imm16
А где у нас находится начало стека при передаче управления, скажем, в такую
функцию:
Код Visual Basic
1
2
3
Private Function Test(byval vP1 as Long)as Long
:
End Function
Ну, разумеется, в откомпилированном виде начало стека будет находиться по
адресу:
Код Visual Basic
1
vESP = VarPtr(vP1) - 4
А в IDE, спросите вы, где будет начало стека при вызове функции в IDE? Ну, здесь всё очень просто. По ряду причин, которые я не хотел бы здесь обсуждать, вызов процедуры <из под> IDE осуществляется почти так же, как осуществляется в откомпилированном виде вызов функции из чужого ActiveX (кстати, думаю, что самые сметливые уже догадались, почему так происходит в IDE). Что это значит? Да вот что: начало стека и первый параметр разделяет ещё один параметр (указатель на адреса ObjTable). То есть начало стека находится следующим образом:
Код Visual Basic
1
vESP = VarPtr(vP1) - 8
Теперь достаточно сохранить этот адрес в <какой-нибудь> (специально для этого предназначенной) переменной
Код Visual Basic
1
2
Public pRet as Long
CopyMem VarPtr(pRet), vESP, 4
после этого по адресу vESP можно записать новый (необходимый нам) адрес.
И далее, процессор <сам по себе> переходит на адрес, скажем, на следующий переключатель в маш. кодах:
Код Visual Basic
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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)=&H24: 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 и вместо адреса возврата записывался указатель на наш переключатель. Тело функции должно выглядеть следующим образом:
Код Visual Basic
1
2
3
4
5
6
7
8
9
10
11
12
13
Private Function Test(byval vP1 as Long)as Long
 #If ReleaseBuild Then
  'вычисляем адрес начала стека
  vESP = VarPtr(vP1) - 4
 #Else
  'вычисляем адрес начала стека
  vESP = VarPtr(vP1) - 8
 #End If
 'сохраняем адрес возврата в переменной pRet
 CopyMem VarPtr(pRet), vESP, 4
 'прописываем в начало стека новый адрес pRD
 PutMem4 vESP, pRD
End Function
И всё! И не нужны никакие CallWindowProc! Всё работает и под IDE, и в откомпилированном виде.


Категория: Basic | Добавил: Admin (14.06.2012)
Просмотров: 1992 | Рейтинг: 5.0/1
Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]
Поиск
Наш опрос
Какую версию Basic вы предпочитаете?
Всего ответов: 2028

© Basic.ucoz.net, 2020