Автор вопроса: CyRax | Web-сайт:basicproduction.nm.ru | ICQ: 204447456
Оцените описание к новой версии. Может найдёте какие либо неточности или что то сильно заумное:
=============
Введение
1.Что такое Local Assembler
В Visual Basic 6 существует возможность вызова машинного кода как локальной процедуры с помощью API-функции CallWindowProc.
В VB6 это выглядело бы так:
Call MyFunction(p1,p2,p3,p4)
Function MyFunction(p1 As Long, p2 As Long, p3 As Long, p4 As Long) As Long
End Function
Отличие от кода VB в содержимом процедуры и ограничении в 4 параметра.
Local Assembler является транслятором из языка ассемблера в машинные коды.
Он не делает исполняемых файлов или библиотек. Машинные коды не вставляются внутрь исполняемого файла. Они могут храниться в строчной переменной или ресурсах.
Выходным файлом является текстовый модуль VB6 с раширением BAS.
Кроме того возможно сохранение результата ассемблирования на диске в виде двоичных данных (BIN) и текстового файла с шестнадцатиричным представлением двоичного кода (HEX).
2. Как работать в Local Assembler
Инструментами вычисления Local Assembler являются регистры, а местом хранения данных - стек.
Передаваемые функции параметры тоже хранятся в стеке. Для управления стеком используются регистры EBP и ESP.
При написании программы вы должны помнить что вы находитесь внутри обычной процедуры.
Однако вы должны знать что если в VB вы сразу же можете начать писать код процедуры, то в ассемблере необходимо сначала проделать подготовительные работы.
Local Assembler добавляет в процедуру код, обслуживающий свой вызов при помощи CallWindowProc с 4-мя параметрами.
Перед просмотром этого кода желательно прочесть главу Введение в язык Ассемблера.
Регистр EBP является нулевым адресом памяти процедуры, а регистр ESP меняет своё значение тогда, когда в память процедуры записываются данные.
Если в памяти процедуры нет данных, то ESP=EBP. При добавлении данных в память процедуры значение ESP уменьшается на их размер, а при удалении увеличивается.
Например если вы добавили в память процедуры 4 байта данных, то значение ESP будет равно EBP-4. Ещё 4 байта - тогда ESP=EBP-8 и т.д.
PUSH EBP <- Данная команда сохраняет предыдущий адрес начала локальной памяти (стека)
MOV EBP,ESP <- Изменяет адрес начала локальных данных на текущий адрес локальных данных
PUSH EBX <- Сохранение регистров EBX, ESI и EDI. Обусловлено способом работы ОС Windows
PUSH ESI
PUSH EDI
...
Дальше идёт код, который вы пишете в окне редактора.
...
При выходе из процедуры восстанавливаются все сохранённые ранее регистры
SUB ESP,12 <- Установить указатель стека на 3 числа (DWORD) от базы стека.
POP EDI <- Восстановить сохранённые ранее регистры
POP ESI
POP EBX <- После этой операции ESP будет равен EBP
POP EBP <- Восстановить предыдущую базу стека
RET 16 <- Выход из функции, имеющей 4 параметра (по 4 байта на каждую).
Для того чтобы отключить добавление этого кода напишите директиву #MANUAL в начале программы.
3. Передаваемые процедуре параметры.
В функцию содержащую машинный код можно передать только 4 параметра.
Они сохраняются в локальной памяти. Как говорилось выше для управления локальной памятью используются регистры EBP и ESP.
Для того чтобы понять почему параметры хранятся именно по этим адресам посмотрите как происходит передача параметров.
VB выполняет команду за командой пока не доходит до функции CallWindowProc, которая вызывает наш код. Это происходит так
PUSH Параметр4 <- Указатель стека увеличился на 4 байта (ESP=ESP-4)
PUSH Параметр3
PUSH Параметр2
PUSH Параметр1
CALL Адрес <- В нашем случае адрес является абсолютным. Это адрес первого байта массива, содержащего машинный код.
После команды CALL изменяется значение счётчика команд и он смещается к началу нашей процедуры.
Если вы не используете директиву #MANUAL, то автоматически происходит следующее:
Сохраняется текущее значение EBP. Если вы помните, то EBP является началом отсчёта (базой) локальной памяти. Для простоты понимания можно сравнить его с нулевым элементом массива.
PUSH EBP
Далее EBP приравнивается ESP. Если продолжить сравнение с массивом, то это будет выглядеть так:
DIM Array(100) 'EBP=0:ESP=0
FOR Counter=LBOUND(Array) To 50
ESP=ESP+1
NEXT Counter
LBOUND(Array)=ESP 'EBP=50
Т.е. нижняя граница массива теперь не ноль, а 50.
MOV EBP,ESP
Посмотрите ещё раз на цикл. ESP=ESP+1 происходит тогда, когда в стек помещаются данные. А последние 4 помещённых в стек числа и есть наши параметры. В нашем примере они будут хранится с 46 по 49 элемент. Т.е. параметер 1 = Array(50-1); 2 - Array(50-2) и т.д.
Но в ассемблере это будет выглядеть наоборот. Так как счётчик стека (ESP) не увеличивается, а уменьшается при добавлении данных, то и знаки будут инвертированными. Параметр1 хранится по адресу EBP+8, Параметр2 - хранится в EBP+12, Параметр3 - хранится в EBP+16 и , Параметр4 - хранится в EBP+20. Плюс не должен вводить вас в заблуждение. На самом деле эти адреса ниже текущей точки отсчёта. Стоит отметить что пропущенные два байта (EBP+0 и EBP+4) предназначены для самообслуживания процедуры и изменять их не рекомендуется. Так например в EBP+0 хранится предыдущее значение EBP (исключая использование директивы #MANUAL), а в EBP+4 адрес возврата при выходе из процедуры.
Теперь осталось выяснить как получить значения этих параметров.
Вернёмся к циклу. Значения EBP и ESP можно сравнить со значением счётчика цикла Counter. Т.е. они только указывают на индекс (адрес) элемента. Для получечения значения необходимо считать данные по этому адресу. В VB это выглядит так Variable=Array(EBP-1). В ассемблере так:
MOV EAX,[EBP+8] <- Переслать в аккумулятор значение по адресу EBP+8
4. Local Assembler в расширенном режиме.
Включается директивой #API. В этом режиме первые три параметра используются как служебные, а 4-й служит указателем на первый элемент массива с параметрами. Описание.
EBP+8 - Точка входа в процедуру
EBP+12 - Адрес функции LoadLibrary
EBP+8 - Адрес функции GetProcAddress
EBP+8 - Адрес первого элемента в массиве параметров.
5. Результат работы функции.
5.1 В ассемблере функция возвращает значение в регистре EAX (аккумулятор).
Напримеh такой VB код
Function MyFunction() As Long
MyFunction=5
End Function
в ассемблере будет выглядеть так:
MOV EAX,5
5.2 Результат может возвращать не только функция, а и параметры. Если вы передадите например переменную по ссылке (ByRef) в первом параметре, то в ассемблере станет доступным адрес этой переменной.
Изменить его можно так
MOV EAX,[EBP+8] <- Раз передавалось по ссылке, то в параметре хранится не значение переменной, а её адрес;
MOV EAX,[EAX] <- Так можно получить значение переменной;
MOV [EAX],10 <- А так изменить.
6. Локальные переменные.
Такие переменные существуют внутри локальной процедуры и при выходе из неё их значения теряются. Локальные переменные необходимо объявить перед использованием. Для этого предназначен метаоператор #DIM.
Пример:
#DIM A,B <- Local Assembler выделит память в стеке для этих переменных и вместо них подставит смещение относительно EBP. Для двух переменных это будет выглядеть как SUB ESP,8
MOV A,5 <- MOV [EBP-4],5
MOV EAX,5
ADD EAX,A
MOV B,EAX <- MOV [EBP-8],EAX
При отведении памяти будут учитываться автоматически сохранённые в начале процедуры регистры. Если вы хотите использовать локальные переменные вместе с директивой #MANUAL, то вы должны указать количество пропускаемых 4-х байтовых чисел с помощью директивы #DIM BASE,Число.