- •Лабораторна робота №4-1 Організація взаємозв'язку програм мовою Асемблера з програмами мовою Паскаль
- •Перше заняття
- •Друге заняття
- •Лабораторна робота №4-2
- •1) Оголошення зовнішніх асемблерних процедур
- •2) Опис глобальних змінних
- •1) Директива завдання набору допустимих команд процесора
- •2) Директива public
- •3) Модель пам’яті
- •6) Локальні параметри
- •7) Повернення значень з асемблерної процедури
- •8) Організація повернення з підпрограми
- •9) Поле операндів директиви end
- •1) Створення проекту
- •2) Створення *.Asm файлу
- •3) Налагодження програми
- •Перше заняття
- •Друге заняття
- •Варіанти завдань
- •Додаткові експерименти
1) Оголошення зовнішніх асемблерних процедур
Прототипи асемблерних процедур (С++ функцій) повинні бути описані за правилами мови С/С++. Крім того, для забезпечення зв’язку в стилі С як стандартному, потрібно доповнити опис процедур ключовим словом extern з додатком "C". Тоді функції будуть відповідним чином сформовані і зможуть легко під’єднатися до асемблерної програми. Додаткові можливості зв’язку в стилі С++ можуть бути розглянуті студентами самостійно. Приклад опису прототипів:
extern "C"
{
void BigShowN(byte* p1, short p2);
byte* AsmFunc(byte* m1, byte* m2, short sz);
}
2) Опис глобальних змінних
Глобальні змінні, що можуть використовуватися в асемблерній програмі, потрібно також описати в extern з додатком "C", наприклад:
extern "C"{
short number = 255;
bool flag;
}
3) Передавання параметрів з програми мовою С++ в асемблерну процедуру
Згідно з вищезазначеними принципами організації зв’язку С++– Асемблер, передавання параметрів в асемблерну процедуру здійснюється через стек у зворотному порядку. Першим у стек записується останній параметр, відповідно останнім – перший параметр функції. Потім у стек заноситься адреса повернення до функції. Наприклад, маємо наступний прототип функції мовою С++:
void pp(char* arg1, char arg2, short arg3, short arg4);
Тоді загальний вигляд виклику:
pp(&arg1, arg2, arg3, arg4);
Першим у стек записується значення параметра arg4, далі arg3, arg2 і останньою – адреса arg1.
Мовою Асемблера реалізація виклику мала би наступний вигляд:
push arg4 ; запис у стек значення arg4
push arg3 ; запис у стек значення arg3
push arg2 ; запис у стек значення arg2
push offset arg1 ; запис у стек адреси arg1
call pp ; запис у стек адреси повернення та виклик процедури рр
add sp, сonst ; вивільнення стеку від параметрів, сonst=розмір параметрів
4-2.2.3. Вимоги до програм мовою Асемблера
1) Директива завдання набору допустимих команд процесора
У Visual Studio для асемблерних програм за замовчуванням встановлена директива .686, яка визначає набір інструкцій процесора (аж до 686), які можуть бути використані у вихідному коді програми. Проте, для виконання даної лабораторної роботи, де мова йдеться про звичайну користувацьку програму, достатньо вказати директиву .386.
2) Директива public
Звичайно для забезпечення беззаперечної видимості (загальнодоступності) асемблерної процедури у програмі мовою С++ потрібно у програмі мовою Асемблера використати директиву public з іменем процедури, наприклад:
рublic рр
Проте, Visual Studio забезпечує абсолютну видимість асемблерних процедур, що входять до складу проекту, і без використання директиви public, тобто, у даному випадку, її використання не є обов’язковим.
3) Модель пам’яті
Для 32-розрядних програм використовується тільки пласка модель пам’яті FLAT, яка підтримується компілятором С++. Модель FLAT передбачає організацію пам’яті у вигляді неперервного лінійного (несегментованого) адресного простору. Є один великий (з максимальним розміром 4 Гбайти) сегмент пам’яті, а в якості адрес використовуються тільки 32-бітні зміщення відносно базової адреси. Таким чином, будь-яка адреса займає 4 байти.
Програма мовою Асемблера може мати наступні складові (хоча усі логічні сегменти, крім .code, є необов’язковими):
.data
; ініціалізовані дані
.data?
; неініціалізовані дані
.const
; константи
.code
; код програми
Сегменти автоматично об’єднуються в групу з ім’ям flat, а сегментні регістри CS, DS, SS відповідно настроюються на усю цю групу.
Щоб застосувати модель пам’яті flat з урахуванням угод мови високого рівня щодо виклику процедур, потрібно на початку програми записати директиву model з модифікатором мови C:
.model flat, C
4) Доступ до переданих С++ функцією параметрів в асемблерній процедурі
Згідно з вищезазначеними правилами передавання параметрів і виклику асемблерної процедури з С++ програми, стек при входженні в асемблерну процедуру буде мати наступний стан (рис. ):
-
Більша адреса пам'яті (початкове значення ESP)
Останній параметр
---
i- ий параметр
---
2-ий параметр
1-ий параметр
Адреса повернення
Менша адреса пам'яті (значення ESP після виклику підпрограми)
Рис. Стан стеку після виклику процедури.
За меншою адресою, на яку вказує покажчик стеку ESP, знаходитиметься адреса повернення в С++ програму, потім перший параметр, далі другий і т.д. Адреса повернення займатиме 4 байти.
Можливі два варіанти запису параметрів в стек:
в стек записується зміщення параметра в сегменті – 2 слова;
в стек записується значення параметра.
Перший варіант (зміщення в сегменті) використовується, якщо параметр передається за посиланням.
Якщо ж параметр передається за значенням і займає в пам'яті один або два байти, то в стек записуються 2 байти. Якщо розмір параметру більший за два байти, в стек записується кількість байтів, що кратна 4.
Для доступу до параметрів процедур використовується базовий регістр стеку EBP, який настроюється на область стеку. Попередньо поточний вміст EBP зберігають, а потім записують в нього вміст покажчика стеку – регістра ESP:
push EBP
mov EBP,ESP
Визначити позиції аргументів у стеку нескладно, оскільки відомий порядок їх запису в стек. Звідси для адресації першого аргументу можна використати адресний вираз [EBP+8] (4 байти – адреса повернення і ще 4 байти – попередній вміст регістра EBP). Для адресації наступних аргументів (пробігаючи від першого до останнього) необхідно кожний раз збільшувати константу на 2 або 4 (у загальному випадку), в залежності від розмірності попереднього аргументу.
Наприклад, вищезазначена С++ функція рр має чотири аргументи. Аргументи arg2, arg3, arg4 передаються за значенням (по 2 байти кожне), arg1 – за адресою (4 байти). Тоді адреса у стеку для аргумента arg1 буде визначатися виразом [EBP+8], аргумента arg2 – [EBP+12], аргумента arg3 – [EBP+14], аргумента arg4 – [EBP+16].
При багаторазових звертаннях до аргументів в асемблерній програмі доцільно задати наступну послідовність константних адресних виразів за допомогою директив EQU:
Arg1 EQU [EBP+8]
Arg2 EQU [EBP+12]
Arg3 EQU [EBP+14]
Arg4 EQU [EBP+16]
або
Arg1 EQU dword ptr [EBP+8]
Arg2 EQU byte ptr [EBP+12]
Arg3 EQU word ptr [EBP+14]
Arg4 EQU word ptr [EBP+16]
Існує ще одна можливість роботи з даними завдяки використанню директиви model. Вона дозволяє описати аргументи процедури безпосередньо в директиві proc. При описі процедури після ключового слова proc додаються параметри з асемблерним типом. Для процедури рр це матиме наступний вигляд:
pp proc @Arg1: dword, Arg2:byte, Arg3: word Arg2: word
…
pp endp
А для представленої нижче процедури BigShowN:
BigShowN proc @mas:dword, @len:word
…
ret
BigShowN endp
Тоді параметри @mas та @len не потрібно вираховувати, як рознайменовані адреси у стеку (хоча вони так само будуть зберігатися у стеку за адресами [EBP+8], [EBP+12]), і з програми відповідно можна виключити рядки:
@mas equ [ebp+8]
@len equ [ebp+12]
Вивільняти параметри із стеку наприкінці процедури за допомогою ret const, де const – загальний розмір цих параметрів у байтах – також непотрібно.
Середовище Visual Studio дозволяє передавати параметри без використання стеку явним чином в асемблерній процедурі (див. процедуру BigShowN). Крім того, при такому описі взагалі не потрібні ніякі дії з регістрами ebp, esp. Команди на початку
push ebp
mov ebp,esp
та наприкінці процедури
pop ebp
виконуватимуться автоматично.
5) Доступ до змінних C++ програми
Глобальні змінні програми мовою С++ можна використовувати в асемблерній програмі. Для цього в асемблерній програмі необхідно визначити ідентифікатори цих змінних у директиві Extrn C з наступним форматом:
Extrn C ім'я:тип, ..., ім'я:тип
Звичайно тип – це асемблерний тип (byte, word, dword), тому необхідно знати кількість байтів, що відводиться для змінних C++.
