Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
labs&konspeckts / SP_ukr / asm_16.docx
Скачиваний:
11
Добавлен:
12.05.2015
Размер:
84.68 Кб
Скачать

Теоретичні відомості

Основні відомості про роботу з Visual Studio

Далі використовується Visual Studio 2010, проте всі дії в інших версіях лишаються аналогічними.

Середовище розробки VS призначене для розробки програмного забезпечення на різних мовах, на кшталт C++, C#, Visual Basic чи F#. Серед підтримуваних мов нас цікавить C++. Програми, написані на ній у VS, можуть використовувати так звані «асемблерні вставки». Передусім вони використовуються в тих місцях коду C++, де необхідна оптимізація через те, що компілятор C++ не в змозі згенерувати достатньо ефективну за швидкістю виконання або використанням ресурсів послідовність машинних команд. Це не завжди необхідно, бо сучасні компілятори виконують значну оптимізацію.

Робота в VS базується на Рішеннях (Solution), котрі можуть складатися з кількох проектів, які в свою чергу містять кілька файлів різного призначення. Серед них: вихідні коди (у мові С++ мають розширення *.cpp), файли заголовків (*.h), ресурси (*.res). Чим відрізняються рішення від проектів? Всі файли в проекті, образно кажучи, «перетворюються» в одну програму з однією точкою входу чи в бібліотеку. Рішення ж можуть складатися з багатьох таких програм, які можуть працювати одночасно чи послідовно, залежати одна від іншої або складати певний комплекс програм. В принципі такий спосіб поділу введений хіба що для зручності, тож на цьому зараз не варто зосереджувати увагу: головне- в VS програміст працює не з одним вихідним файлом, а з декількома зв’язаними між собою: той самий принцип, що і в IDE типу Eclipse, NetBeans, IntelliJ IDEA тощо, лише трохи інша реалізація.

Щоб почати написання програми необхідно створити новий проект – а рішення створиться разом із ним автоматично. Це можна зробити як через стартову сторінку VS, так і через головне меню (або взагалі Ctrl + Shift +N). Для цієї лабораторної роботи необхідним типом проекту є консольний проект, точніше Win32 Console Application - очевидно, що мовою має бути С++. Щоб створити проект треба ще й задати ім’я проекту та місце його розташування, якщо стандартне не подобається. Після цього всі налаштування у майстрі створення проекту можна лишити стандартними – одразу натиснути Finish.

Основні файли, що при цьому створюються:

<project_name>.cpp – «головний» вихідний файл, який містить точку входу – функцію _tmain.

// test.cpp : Defines the entry point for the console application.

//

#include "stdafx.h"

int _tmain(int argc, _TCHAR* argv[])

{

return 0;

}

stdafx.h – файл заголовка, який під’єднує системні файли заголовків та інші нечасто змінювані файли заголовків. Ці умови не обов’язково виконувати, це лише «хороший тон».

// stdafx.h : include file for standard system include files,

// or project specific include files that are used frequently, but

// are changed infrequently

//

#pragma once

#include "targetver.h"

#include <stdio.h>

#include <tchar.h>

// TODO: reference additional headers your program requires here

Програма запускається для відлагодження за командою F5. При цьому середовище змінює розташування деяких панелей – тож якщо потрібні закриються, можна їх повернути через меню View… Що й без пояснень очевидно. Суттєвих відмінностей від інших – в тому числі й Java – IDE для виконання цієї лабораторної роботи немає. Хіба що інші клавіатурні комбінації і назви меню. Тому перейдемо до власне асемблерних вставок.

Використання асемблерних вставок (inline-асемблер) С (С++).

Перед вставкою пишемо зарезервоване слово __asm. Його можна використовувати для вставленняя як однієї команди:

__asm mov eax, 1

Так і кількох підряд, об’єднавши їх фігурними дужками:

__asm

{

mov ecx, count;

mov ebx, arr;

sumall:

add eax, [ebx + 4*ecx - 4];

loop sumall;

}

Як можна помітити, основний синтаксис зовсім не відрізняється від звичайних програм, написаних на асемблері, за винятком хіба того, що можна використовувати змінні та параметри, передані в функцію, описані за межами самої вставки. Також користуватися можна

  • константами та членами множин (enum)

  • макросами та директивами препроцесора

  • коментарями

  • іменами типів та typedef

Але при цьому не вийде скористатися, наприклад, директивами “db”, “dw”, ”dd” і т.д., асемблерними процедурами “name PROC … name ENDP”, а також тими директивами TASM , що не мають ніякого сенсу, знаходячись у вставках, на кшталт “.386” або “.data”.

Використання змінних та вказівників в inline-асемблері

Змінні, як сказано вище, можна використовувати одразу, без будь-яких додаткових конструкцій:

mov ecx, count;

З вказівниками ситуація трохи інша, використати операцію розіменування не вийде. Тому вони обрамляються в таку конструкцію:

mov ebx, arr;

add eax, [ebx + 4*ecx];

де arr – вказівник (масив), а в ecx знаходиться індекс елемента, до якого треба звернутися.

В С можуть використовуватися 16-бітні near вказівники (лише зміщення в межах поточного сегменту), 32-бітні far-вказівники (містять пару сегмент:зміщення, як у Pascal) та 32-бітні huge-вказівники (містять 20-бітну суму, до якої можна застосувати арифметичні операції). Це стосується як вказівників, так і посилань (які на низькому рівні ідентичні до вказівників).

Передача параметрів у функції та повернення значень із неї

У більшості випадків параметри – будь то значення, посилання чи вказівники – передаються через стек. Винятком є конвенція FASTCALL: параметри передаються в регістрах загального призначення, якщо в них достатньо місця. Проте цю конвенцію розглядати не будемо, зосередившись на C-конвенції (також PASCAL, яка вважається застарілою, і STDCALL – для порівняння).

Для початку слід зауважити, що всі параметри з розміром меншим за 4 байти розширюються до 32 біт при передачі. Так само розширюються значення, що повертаються з функції. Ці значення мають бути розміщенні в регістрі eax, за виключенням, наприклад, 64-бітного типу long, що повертається у парі edx:eax.

Конвенція pascal

Ця конвенція використовується у мові Pascal, а також BASIC, FORTRAN, ADA тощо. В цій конвенції аргументи перед викликом функції поміщаються в стек у прямій послідовності, а очищує стек сама функція. Дані при цьому розташовані в пам’яті в зворотньому порядку.

Таким чином такий код на паскалі:

procedure proced(a, b, c : integer);

begin

end;

proced(x, y, z);

перетвориться на:

proced proc

push ebp ;стандартний пролог

mov ebp, esp

a equ [ebp + 16] ;аргументи розташовані в зворотньому порядку

b equ [ebp + 12]

c equ [ebp + 8]

pop ebp ;стандартний епілог

ret 12 ;зі звільненням 12 байтів стеку зайнятих аргументами

proced endp

push x ;прямий порядок занесення аргументів

push y

push z

call proced

Недоліком є велика складність при написанні процедур та функцій зі змінною кількістю параметрів, оскільки функції не так просто визначити адресу першого аргументу, що розташований в самому кінці.

Конвенції c та stdcall

В цих конвенціях параметри заносяться до стеку в зворотньому порядку. Різниця між C та STDCALL полягає в тому, що в останній підпрограми мають власноруч звільняти стек.

Код, написаний на C++

void func(a, b, c)

{

}

func(x, y, z)

Транслюється в

func proc

push ebp ;стандартний пролог

mov ebp, esp

a equ [ebp +8] ; в пам’яті аргументи розміщені в прямій послідовності

b equ [ebp + 12] ; ми точно знаємо адресу першого з них

c equ [ebp + 16]

pop ebp ;стандартний епілог

ret ;повернення без очищення стеку

func endp

push z ;зворотній порядок занесення

push y

push x

call func ;виклик функції

add esp, 12 ;очистка стеку – її виконує той, хто викликає функцію (конвенція С)

В конвенції С дуже легко викликати різні функції з одними й тими ж параметрами. Функція не звільняє стек власноруч, тобто параметри лишаються в стеку і їх можна використати знову.

Як видно із текстів на асемблері, компілятор додає так звані «пролог» та «епілог» всередину кожної функції. Вони зберігають значення вказівника стеку, щоб звернення до параметрів (як [ebp + 12]) не залежали від змін у самому стеку, наприклад, через створення локальних змінних. Якщо необхідно написати власний пролог та епілог, перед функцією треба додати __declspec(naked):

__declspec(naked) int func(int i)

{

__asm

{

… ;власний пролог

}

__asm

{

… ;власний епілог

}

}

Використання return в naked-функціях не дозволяється через зрозумілі причини.

Соседние файлы в папке SP_ukr