Справочник от Автор24
Поделись лекцией за скидку на Автор24

«Чистый» язык ассемблера

  • 👀 278 просмотров
  • 📌 256 загрузок
Выбери формат для чтения
Загружаем конспект в формате docx
Это займет всего пару минут! А пока ты можешь прочитать работу в формате Word 👇
Конспект лекции по дисциплине ««Чистый» язык ассемблера» docx
Глава 4. «Чистый» язык ассемблера § 4.1. Директивы Инструкция (команда) - транслируется в исполняемый код. Директива - не приводит к появлению нового кода, а управляет работой самого ассемблера. Разные ассемблеры используют различные наборы директив!!! Определение данных имя_переменной dx значение dx: db  определить байт; dw  определить слово (2 байта); dd  определить двойное слово (4 байта); df определить 6 байт; dq  определить учетверенное слово (8 байт); dt определить 10 байт (типы данных, используемые FPU). text_string db 'Hello world!' message db "Hi, people!!!" number dw 7 table db 1,2,3,4,5,6,7,8,9,0Dh,0Ah,0 float_number dd 3.5e7 Имя переменной соответствует адресу первого из указанных значений. mov al, text_string ;al == 48h (код 'H') Переменная считается неинициализированной и ее значение на момент запуска программы может оказаться любым, если ее определить следующим образом: i db ? Если нужно заполнить участок памяти повторяющимися данными, используется специальный оператор dup: massiv db 512 dup(?) Создается массив из 512 неинициализированных байт, адрес первого байта хранится в переменной massiv. Модели памяти и объявление сегментов 1. Модель памяти задается директивой .model модель, язык, модификатор Модель: tiny  код, данные и стек размещаются в одном сегменте размером до 64 килобайт. small  код размещается в одном сегменте, а данные и стек — в другом. medium код размещается в нескольких сегментах, а все данные — в одном, поэтому для доступа к данным используется только смещение, а для вызова подпрограмм полные адреса; lage, huge  и код, и данные могут занимать несколько сегментов; flat то же, что и tiny, но регистры 32-битные. Язык (необязательный операнд): c, pascal, basic, fortran если язык указан, то процедуры рассчитаны на вызов из программ на соответствующем языке высокого уровня. Модификатор (необязательный операнд): nearstack (по умолчанию) farstack (сегмент стека не объединяется в одну группу с сегментами данных) 2. Сегмент кода описывается директивой .code 3. Сегмент стека описывается директивой .stack размер Необязательный параметр указывает размер стека. 4. Сегмент данных описывается директивой .data Структура программы ;Общая структура 16-ти разрядной программы на ;ассемблерае TASM/MASM имеет нижеследующий вид ;(для 32-разрядных приложений – другая модель памяти ;(например, flat)) .model small ;модель памяти .stack 256 ;объем стека 256 байт .data ;сегмент данных ;данные .code ;сегмент кода start: ;команды end start .model small ;модель памяти .stack 256 ;объем стека 256 байт .data ;начало сегмента данных message db "Hi, people!!!$" ;$  \0 .code ;начало сегмента кода start: ;ВЫВОД НА ЭКРАН ЗАДАННОЙ СТРОКИ mov ax, @data mov ds, ax ;в ds адрес сегмента данных lea dx, message ;адрес message в ds:dx mov ah, 9 ;9-я функция int 21h ;21-го прерывания ;вывод на консоль информации по адресу из dx mov ax, 4C00h int 21h ;завершение выполнения программы end start Процесс разработки программы Создание exe-файла: TASM: Трансляция: tasm.exe имя_файла.asm Компоновка: link16.exe имя_файла.obj ,,,,, MASM: Трансляция: ml.exe /c имя_файла.asm Компоновка: link16.exe имя_файла.obj ,,,,, § 4.2. Несколько решений одной задачи Задача: даны две строки s1 и s2. Скопировать содержимое s1 в s2 и вывести s2 на экран. Ассемблерная вставка в С++ (рассмотрена ранее) #include #include void main(){ char s1[] = "Hi, people!!!\n"; int i = strlen(s1) + 1; char s2[] = "123456789123456789\n"; _asm{ cld mov ecx, i lea esi, s1 lea edi, s2 rep movsb } std::cout << s2; } 16-разрядная программа на ассемблере TASM/MASM .model small .stack 256 .data s1 db 'Hi, people!!!',0Dh,0Ah,'$' len equ $-s1 ;equ - аналог #define ;len == адрес '$' минус адрес 'Н' ;в итоге len == длина строки s1 s2 db '123456789123456789',0Dh,0Ah,'$' adr_s1 dd s1 ;в adr_s1 адрес s1 adr_s2 dd s2 ;в adr_s1 адрес s2 .code start: mov ax, @data mov ds, ax ;теперь в ds адрес сегмента данных cld ;флаг направления df = 0 mov cx, len ;cx = len lds si, adr_s1 ;поместить адрес из adr_s1 в ds:si les di, adr_s2 ;поместить адрес из adr_s2 в es:di rep movsb ;rep повторяет команду movsb столько раз, ;сколько указано в cx ;movsb – копирование байта из si в di lea dx, s2 ;поместить адрес s2 в dx mov ah, 9 int 21h ;вывод на экран информации по адресу из dx mov ax, 4C00h int 21h ;завершение выполнения программы end start Графическое win32-приложениe на ассемблере MASM include def32.inc include kernel32.inc include user32.inc .386 .model flat .data S db "My message",0 s1 db "Hi, people!!!",0 len equ $-s1 s2 db "123456789123456789",0 .code _start: ;метка точки входа должна начинаться с подчеркивания cld mov ecx, len mov esi, offset s1 ;поместить адрес строки s1 в esi mov edi, offset s2 ;поместить адрес строки s2 в edi rep movsb push MB_ICONINFORMATION ;стиль окна push offset s ;адрес строки с заголовком окна push offset s2 ;адрес строки с сообщением push 0 ;идентификатор предка call MessageBox ;вызов системной функции ;«окно с указанным сообщением» push 0 ;код выхода call ExitProcess ;вызов системной функции ;«завершение программы» end _start 1. Чтобы вызвать системную функцию Windows, программа должна поместить в стек все параметры от последнего к первому и передать управление командой call. Все эти функции сами освобождают стек (завершаясь инструкцией ret n) и возвращают результат работы в регистре eax. Такая договоренность о передаче параметров называется STDCALL. Соглашение CDECL (принято по умолчанию в C/C++): параметры также перечисляются справа налево, но стек освобождает вызывающая процедура (в этом случае вызываемая процедура должна завершаться инструкцией ret без параметра). 2. Прежде чем скомпилировать asm-файл, нужно создать inc-файлы, в которые необходимо поместить директивы, описывающие вызываемые системные функции. Имена всех системных функций Windows модифицируются так, что перед именем функции ставится подчеркивание, а после - знак «@» и число байт, которое занимают параметры, передаваемые ей в стеке: ExitProcess()_ExitProcess@4()__imp__ExitProcess@4 Соглашение CDECL (принято по умолчанию в С/C++): ExitProcess()_ExitProcess() В примерах мы будем обращаться напрямую к __imp__ExitProcess@4. ;===def32.inc=== MB_ICONINFORMATION equ 40h ;включаемый файл с определениями констант и типов ;для программ под win32 из winuser.h ;===файл kernel32.inc=== includelib kernel32.lib ;включаемый файл с определениями функций ;из kernel32.dll extrn __imp__ExitProcess@4:dword ;истинные имена используемых функций ExitProcess equ __imp__ExitProcess@4 ;присваивания для облегчения читаемости кода ;===user32.inc=== includelib user32.lib ;включаемый файл с определениями функций ;из user32.dll ;это библиотека, в которую входят основные функции, ;отвечающие за оконный интерфейс extrn __imp__MessageBoxA@16:dword ;истинные имена используемых функций MessageBox equ __imp__MessageBoxA@16 ;присваивания для облегчения читаемости кода Для компиляции потребуются файлы kernel32.lib и user32.lib. Создание exe-файла на ассемблере MASM: Трансляция: ml /c /coff /Cp имя_файла.asm Компоновка: link имя_файла.obj /subsystem:windows Консольное win32-приложениe на ассемблере MASM include def32.inc include kernel32.inc .386 .model flat .data mes dd ? ;переменная для функции WriteConsole s1 db "Hi, people!!!",0Dh,0Ah,0 len equ $-s1 s2 db "123456789123456789",0Dh,0Ah,0 .code _start: ;метка точки входа должна начинаться с подчеркивания cld mov ecx, len mov esi, offset s1 ;поместить адрес строки s1 в esi mov edi, offset s2 ;поместить адрес строки s2 в edi rep movsb push STD_OUTPUT_HANDLE call GetStdHandle ;вызов системной функции ;«возврат идентификатора stdout в eax» mov ebx, len ;в ebx длина строки для вывода push 0 push offset mes ;адрес переменной, в которую будет занесено ;число байт, действительно выведенных на консоль push ebx ;сколько байт надо вывести на консоль push offset s2 ;адрес строки для вывода на консоль push eax ;идентификатор буфера вывода call WriteConsole ;вызов системной функции ;«вывод строки на консоль» push 0 ;код выхода call ExitProcess ;вызов системной функции ;«завершение программы» end _start 1. Все функции, работающие со строками (как, например, WriteConsole()), существуют в двух вариантах. Если строка рассматривается в обычном смысле, как набор символов ASCII, к имени функции добавляется «A» (WriteConsoleA()). Другой вариант функции, использующий строки в формате UNICODE (два байта на символ), заканчивается буквой «U». В примерах будем использовать обычные ASCII-функции. 2. Прежде чем скомпилировать asm-файл, нужно создать inc-файлы, в которые необходимо поместить директивы, описывающие вызываемые системные функции. ;===def32.inc=== STD_OUTPUT_HANDLE equ -11 ; включаемый файл с определениями констант и типов для ;программ под win32 из winbase.h ;===kernel32.inc=== includelib kernel32.lib ;включаемый файл с определениями функций из ;kernel32.dll extrn __imp__ExitProcess@4:dword extrn __imp__GetStdHandle@4:dword extrn __imp__WriteConsoleA@20:dword ;истинные имена используемых функций ExitProcess equ __imp__ExitProcess@4 GetStdHandle equ __imp__GetStdHandle@4 WriteConsole equ __imp__WriteConsoleA@20 ;присваивания для облегчения читаемости кода Создание exe-файла на ассемблере MASM: Трансляция: ml /c /coff /Cp имя_файла.asm Компоновка: link имя_файла.obj /subsystem:console Отладка win32-приложения в Microsoft Visual Studio 1. Создайте новый консольный проект. 2. Добавьте в него уже существующий asm-файл. 3. Заголовочные inc-файлы должны находиться в одной папке с asm-файлом. Процедуры ;процедура на ассемблере TASM/MASM имя_процедуры proc ;точка входа в процедуру ;имя_процедуры считается меткой ;... ret ;возврат в вызывающую процедуру имя_процедуры endp Если процедура должна возвращать результат, он помещается в регистр eax имя_процедуры proc ;... mov eax, результат ret имя_процедуры endp Как передать параметры процедуре? 1. Передача параметров процедуре через стек (этот способ рассмотрен ранее). 2. Передача параметров процедуре через регистры (см. пример). ;консольное win32-приложениe, демонстрирует передачу ;параметров процедуре через регистры _start: ... mov ebx, len ;в ebx длина строки для вывода lea esi, s2 ;в esi адрес строки s2 call output_string ;вызов нашей процедуры push 0 ;код выхода call ExitProcess ;вызов системной функции ;«завершение программы» ;процедура вывода строки на экран ;в eax - идентификатор буфера вывода ;в esi - адрес строки ;в ebx - длина строки output_string proc push 0 push offset mes ;адрес переменной, в которую будет занесено ;число байт, действительно выведенных на консоль push ebx ;сколько байт надо вывести на консоль push offset s2 ;адрес строки для вывода на консоль push eax ;идентификатор буфера вывода call WriteConsole ;вызов системной функции ;«вывод строки на консоль» ret output_string endp end _start Интерфейс с C/С++ В файле str.asm содержится код процедуры, копирующей содержимое одной строки в другую. Имя процедуры соответствует требованиям соглашения STDCALL («0» в конце имени означает, что процедуре параметры не передаются). ;===str.asm=== .386 .model flat .data s1 db "Hi, people!!!",0Dh,0Ah,0 len equ $-s1 s2 db "123456789123456789",0Dh,0Ah,0 .code _str2str@0 proc cld mov ecx, len mov esi, offset s1 ;поместить адрес строки s1 в esi mov edi, offset s2 ;поместить адрес строки s2 в edi rep movsb mov eax, offset s2 ret _str2str@0 endp end В заголовочном файле str.h содержится объявление функции, написанной на ассемблере. Ключевое слово extern означает, что функция является внешней; оператор "C" необходим для компилятора C/C++, директива __stdcall устанавливает соглашение об именовании. //===str.h=== extern "C" char* __stdcall str2str(); В файле main.cpp вызывается функция, реализованная на ассемблере. //===main.cpp=== #include #include "str.h" void main(){ printf("%s",str2str());} § 4.3. Пример процедуры с параметрами Реализуем теперь функцию intMint(int a, int b), которая имеет два целочисленных параметра и в качестве результата возвращает их разность (a – b). Первым помещается в стек параметр b; вторым – параметр a; A – адрес команды, следующей после вызова функции, это точка возврата. 1. Вызов функции intMint(int a, int b): Старшие адреса esp+8 esp Младшие адреса … … b a A esp+4 Так как указатель стека esp в процессе выполнения функции может изменяться, его значение сохраняется в регистре ebp (предварительно сохранив ebp в стеке), чтобы иметь возможность работать с параметрами функции: push ebp mov ebp,esp 2. Содержимое стека после push ebp и mov ebp,esp: Старшие адреса esp+12 esp+4 Младшие адреса … … b a A ebp esp+8 esp==ebp 3. Содержимое стека после завершения функции: Старшие адреса esp+4 Младшие адреса … … b a A ebp esp 4. Надо сместить esp на 8 байт в сторону старших адресов: либо ret 8 - в вызываемой процедуре (STDCALL) либо add esp 8 - в вызывающей процедуре (CDECL) Старшие адреса Младшие адреса … … b a esp ;======Файл functions.asm====== .386 .model flat .data .code _intMint@8 proc ;8 байт приходится на два целочисленных параметра push ebp mov ebp,esp ;если esp изменится, через ebp можно ;добраться до параметров mov eax,[ebp+8] ;eax = a sub eax,[ebp+12] ;eax = eax – b (eax == (a - b)) ;теперь в eax результат работы процедуры pop ebp ;вернули ebp в исходное состояние ret 8 ;очистка стека _intMint@8 endp //=====Файл functions.h===== extern "C" int __stdcall intMint(int a, int b); //=====Файл main.cpp===== #include #include "functions.h" int a = 10, b = 2; void main(){ printf("%i\n",intMint(a,b)); } В заключении реализуем аналогичную функцию, также вычисляющую разность, но уже вещественных чисел. Основное отличие заключается в том, что результат вычислений не входит в регистр eax (вещественное число занимает 8 байт), а значит функция должна вернуть адрес вещественного числа. ;======Файл functions.asm====== .386 .model flat .data rezult dq ? .code _doubleMdouble@16 proc ;16 байт приходится на два вещественных параметра push ebp mov ebp,esp ;если esp изменится, через ebp можно ;добраться до параметров finit ;инициализация сопроцессора fld qword ptr [ebp+8] ;st(0) = a fsub qword ptr [ebp+16] ;st(0) = st(0) – b теперь st(0) == (a - b) fstp rezult ;rezult = st(0) и вытолкнуть ;теперь rezult == (a - b) lea eax,rezult ;поместить адрес rezult в eax ;теперь в eax результат работы процедуры pop ebp ;вернули ebp в исходное состояние ret 16 ;очистка стека _doubleMdouble@16 endp //=====Файл functions.h===== extern "C" double* __stdcall doubleMdouble (double a, double b); //функция возвращает адрес вещественного числа //=====Файл main.cpp===== #include #include "functions.h" double x = 10.5, y = 2.5; void main(){ printf("%lf\n",*doubleMdouble(x,y)); //* - это операция разадресации } ТЕСТ № 2
««Чистый» язык ассемблера» 👇
Готовые курсовые работы и рефераты
Купить от 250 ₽
Решение задач от ИИ за 2 минуты
Решить задачу
Помощь с рефератом от нейросети
Написать ИИ

Тебе могут подойти лекции

Смотреть все 588 лекций
Все самое важное и интересное в Telegram

Все сервисы Справочника в твоем телефоне! Просто напиши Боту, что ты ищешь и он быстро найдет нужную статью, лекцию или пособие для тебя!

Перейти в Telegram Bot