Ассемблер. арифметические инструкции

Содержание

Конвенция вызова функций STDCALL (WinApi : Windows 95 — Windows 10).

Операционная система Windows содержит набор встроенных функций, которые обеспечивают удобство программирования, обслуживания и работы системы — так называемые WinApi — Windows Application programming interfaces.

Функций огромное количество. Они входят в стандартный пакет Windows любой версии и содержаться в библиотеках *.dll, расположенных в системных директориях Windows (System, System32, SysWOW64 — для 64 битной системы). Например, kernel32.dll содержит огромное количество функций, входящих в т.н. «Ядро операционной системы», например MoveFile — «переместить файл».

Для операционной системы Windows (WinApi) был разработана отдельная конвенция вызова функций. Она включила в себя преимущества PASCAL и С (Си) конвенций.

Конвенция STDCALL (WinApi) имеет следующие особенности.

Параметры загоняются в стек слева направо — снизу вверх, стек очищается вызываемая функция.

К слову можно сказать, что возвращаемое функцией WinApi значение содержиться в 32 битном регистре eax (нам он пока не известен, но это расширенный до 32 бит регистр ax).

Функция (процедура) содержит пять параметров:myFunc (a,b,c,d,e)

;Ассемблерный код:
push e; Пятый параметр — сверху
push d;
push c;
push b;Второй параметр
push a; Первый параметр (самый левый) — снизу
call myFunc

;———————————————————————-
;Функция содержит пять параметров:
myFunc:
push bp
mov bp,sp; Создаём стековый кадр. В bp — указатель на стековый кадр, регистр bp использовать нельзя!
e equ ; Последний параметр — сверху ()
d equ
c equ
b equ
a equ

;команды, которые могут использовать стек:
mov ax,e ; считать параметр «a» — . Можно и так, но это менее понятно: mov ax,
; Команды CALL при вызове функции, в стек поместили адрес возврата — 2 байта для процедуры типа NEAR (или 4 — для FAR), а потом еще и ВР — 2 байта (push bp — в начале нашей функции)
mov bx,с ; считать параметр «c» — . Можно и так, но это менее понятно: mov bx,
;…ещё команды

pop bp
ret 10 ; Из стека дополнительно извлекается 10 байт — стек освобождает вызываемая функция

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

;Ассемблерный код:

pushe; Пятый параметр — сверху

pushd;

pushc;

pushb;Второй параметр

pusha; Первый параметр (самый левый) — снизу

callmyFunc

;———————————————————————-
;Функция содержит пять параметров:

myFunc

pushbp

movbp,sp; Создаём стековый кадр. В bp — указатель на стековый кадр, регистр bp использовать нельзя!

eequbp+12; Последний параметр — сверху ()

dequbp+10

cequbp+8

bequbp+6

aequbp+4

;команды, которые могут использовать стек:

movax,e; считать параметр «a» — . Можно и так, но это менее понятно: mov ax,

; Команды CALL при вызове функции, в стек поместили адрес возврата — 2 байта для процедуры типа NEAR (или 4 — для FAR), а потом еще и ВР — 2 байта (push bp — в начале нашей функции)

movbx,с; считать параметр «c» — . Можно и так, но это менее понятно: mov bx,

;…ещё команды
 

popbp

ret10; Из стека дополнительно извлекается 10 байт — стек освобождает вызываемая функция

Директива Assume

Эта директива сообщает компилятору, что указанный в ней сегментный регистр необходимо связать с именем сегмента или группы. Она имеет следующий формат:

    ASSUME        сегм_регистр1:имя1 

В качестве сегментных регистров для базового Ассемблера принимаются уже известные нам регистры: CS, DS, ES или SS.

Для ОТМЕНЫ назначения для данного сегментного регистра используется ДРУГОЙ формат этой директивы:

    ASSUME        сегм_регистр1:NOTHING 

Чаще всего эта директива используется в начале модуля на Ассемблере.

Рассмотрим фрагмент листинга ассемблерного файла, который был получен компилятором Borland C++ 5.02:

_TEXT	segment byte public 'CODE'
_TEXT	ends
DGROUP	group	_DATA,_BSS
         			assume	cs:_TEXT,ds:DGROUP
_DATA	segment word public 'DATA'
               ………………………………………
_DATA	ends
_BSS	    segment word public 'BSS'
               ………………………………………
_BSS	    ends
_TEXT	segment byte public 'CODE'
	    	assume	cs:_TEXT,ds:DGROUP
               ………………………………………
_TEXT	ends
_DATA	segment word public 'DATA'
               ………………………………………
_DATA	ends
_TEXT	segment byte public 'CODE'
_TEXT	ends
 	    end

Ну, как? Все понятно? Вот мы и начинаем немного понимать, что же делают компиляторы с алгоритмических языков…

Регистры

Обычно операции с процессором включают в себя обработку данных. Эти данные могут быть как сохранены в памяти, так и извлечены оттуда. Однако процесс чтения данных из памяти и хранения данных в памяти замедляет работу процессора, так как это предполагает сложный процесс отправки запроса данных в блок памяти и получение данных обратно из блока по одному и тому же каналу — через шину управления.

Чтобы ускорить свою работу, процессор подключает определенные внутренние места хранения памяти, которые называются регистрами. Регистры хранят элементы данных для обработки без необходимости получать доступ к памяти. Ограниченное количество регистров встроено в чип процессора.

Схематичная структура кода на ассемблере.

Схематично структура исходного кода программы .COM выглядит так:

;Стандартный вид программы типа .COM
.model tiny ; for СОМ
.code ; code segment start
org 100h ; offset in memory = 100h (for COM)

start: ; точка входа

… код ; (.code — сегмент уже определён выше)
… данные
… константы
… стек
end start

1
2
3
4
5
6
7
8
9
10
11
12

;Стандартный вид программы типа .COM

.modeltiny; for СОМ

.code; code segment start

org100h; offset in memory = 100h (for COM)

start; точка входа

…код; (.code — сегмент уже определён выше)

…данные

…константы

…стек

endstart

Но для удобства программиста можно написать и так:

;Не стандартный вид программы типа .COM — кому-то покажется более удобным разбить сегменты на части
.model tiny ; for СОМ
.code ; code segment start
org 100h ; offset in memory = 100h (for COM)

start: ; точка входа

… код ; (.code — сегмент уже определён выше)
.const
… константы
.data
… данные
.code
… код
.data
… и снова данные
.code
… и снова код
.data

.code

.data?
… не инициализированные данные
.code

end start

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

;Не стандартный вид программы типа .COM — кому-то покажется более удобным разбить сегменты на части

.modeltiny; for СОМ

.code; code segment start

org100h; offset in memory = 100h (for COM)

start; точка входа

…код; (.code — сегмент уже определён выше)

.const

…константы

.data

…данные

.code

…код

.data

…исноваданные

.code

…исновакод

.data

.code

.data?

…неинициализированныеданные

.code

endstart

Опустимся по линии абстракции кода ниже. Мы уже знаем и можем назвать один из блоков кода, который имеет имя, параметры и возвращаемое значение. Этот «блок» может вызываться практически неограниченное число раз, будучи написанным один раз Вы уже догадались, что это функция.

В этой статье речь пойдёт о блоках данных. Мы рассмотрим, что такое константа, массив, структура в ассемблере, а также более редко встречающиеся: перечисление, объединение, запись с битовыми полями (запись).

Использование макросредств для генерации команд процессоров другой архитектуры

При выполнении работ по программированию RISC-процессора микроконтроллера AT90S2313 «штатный» транслятор с ассемблера показался автору после работы с RASM непривычным и поэтому неудобным. Возникла идея использовать специальные макросредства и для того, чтобы генерировать коды команд RISC-архитектуры в соответствии с документацией Atmel, но при этом остаться в привычной среде RASM. Дело упрощалось тем, что RASM имеет режим формирования загрузочного модуля сразу, без использования редактора связей.

Анализ показал, что имеется лишь три препятствия такого использования RASM для генерирования команд RISC-архитектуры: конфликт мнемоники команды ST с названием регистра FPU, форма записи инкремента указателя типа X+ и другой способ вычисления относительного адреса, делающий директиву RELW неподходящей.

Первые два препятствия были обойдены с помощью введения новых директив в RASM, позволяющих исключать из лексического анализа заданную лексему (в данном случае ST) и разрешать синтаксические конструкции инкремента типа X+.

Для вычисления относительного адреса команд RISC-архитектуры были доработаны директивы макро RELW и DBIT. В директиве RELW стало возможно указывать необязательные дополнительные параметры в виде «добавки» и «сдвига вправо», позволяющие не просто вычислить адрес относительно текущего места, но и пересчитать его к нужному виду прибавлением «добавки» и сдвигом на заданную величину. При этом новая форма директивы RELW сама уже не генерирует адрес, а запоминает его для последующего использования в директиве DBIT. Доработка DBIT заключалась в возможности использования адреса, вычисленного выше директивой RELW. Для указания такого адреса используется строка “S” вместо имени параметра.

Такие несложные доработки транслятора повысили универсальность макросредств. Все RISC-команды были легко описаны с их помощью, например:

И наконец стало можно программировать микроконтроллер AT90S2313 на RASM:

Пример на практике

Следующая программа создает и открывает файл с именем myfile.txt, и записывает текст в этот файл. Затем программа считывает данные из файла и сохраняет их в буфере с именем . Наконец, программа выводит текст, сохраненный в буфере , на экран:

section .text
global _start ; объявляем для использования gcc

_start: ; сообщаем линкеру входную точку
; Создаем файл
mov eax, 8
mov ebx, file_name
mov ecx, 0777 ; читать, писать и выполнять могут все
int 0x80 ; вызов ядра

mov , eax

; Записываем данные в файл
mov edx,len ; количество байтов
mov ecx, msg ; сообщение для записи в файл
mov ebx, ; файловый дескриптор
mov eax,4 ; номер системного вызова (sys_write)
int 0x80 ; вызов ядра

; Закрываем файл
mov eax, 6
mov ebx,
int 0x80 ; вызов ядра

; Выводим на экран сообщение, указывающее на конец записи в файл
mov eax, 4
mov ebx, 1
mov ecx, msg_done
mov edx, len_done
int 0x80

; Открываем файл для чтения
mov eax, 5
mov ebx, file_name
mov ecx, 0 ; доступ «Только для чтения»
mov edx, 0777 ; читать, писать и выполнять могут все
int 0x80

mov , eax

; Считываем данные из файла
mov eax, 3
mov ebx,
mov ecx, info
mov edx, 26
int 0x80

; Закрываем файл
mov eax, 6
mov ebx,
int 0x80

; Выводим на экран данные из буфера info
mov eax, 4
mov ebx, 1
mov ecx, info
mov edx, 26
int 0x80

mov eax,1 ; номер системного вызова (sys_exit)
int 0x80 ; вызов ядра

section .data
file_name db ‘myfile.txt’
msg db ‘Welcome to Ravesli!’
len equ $-msg

msg_done db ‘Written to file’, 0xa
len_done equ $-msg_done

section .bss
fd_out resb 1
fd_in resb 1
info resb 26

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74

section.text

   global_start; объявляем для использования gcc

_start; сообщаем линкеру входную точку

; Создаем файл

   moveax,8

   movebx,file_name

   movecx,0777; читать, писать и выполнять могут все

   int0x80; вызов ядра

   movfd_out,eax

; Записываем данные в файл

   movedx,len; количество байтов

   movecx,msg; сообщение для записи в файл

   movebx,fd_out; файловый дескриптор

   moveax,4; номер системного вызова (sys_write)

   int0x80; вызов ядра

; Закрываем файл

   moveax,6

   movebx,fd_out

   int0x80; вызов ядра

; Выводим на экран сообщение, указывающее на конец записи в файл

   moveax,4

   movebx,1

   movecx,msg_done

   movedx,len_done

   int0x80

; Открываем файл для чтения

   moveax,5

   movebx,file_name

   movecx,; доступ «Только для чтения»

   movedx,0777; читать, писать и выполнять могут все

   int0x80

   movfd_in,eax

; Считываем данные из файла

   moveax,3

   movebx,fd_in

   movecx,info

   movedx,26

   int0x80

; Закрываем файл

   moveax,6

   movebx,fd_in

   int0x80

; Выводим на экран данные из буфера info

   moveax,4

   movebx,1

   movecx,info

   movedx,26

   int0x80

   moveax,1; номер системного вызова (sys_exit)

   int0x80; вызов ядра

section.data

file_namedb’myfile.txt’

msgdb’Welcome to Ravesli!’

lenequ$-msg

msg_donedb’Written to file’,0xa

len_doneequ$-msg_done

section.bss

fd_outresb1

fd_inresb1

inforesb26

Результат выполнения программы:

CALL (CALL).

Вызов процедуры с запоминанием в стеке точки возврата:

call адрес перехода.

Примеры для CALL:

woman proc
mov ah,09h;аргумент для прерывания int 21h. 09h в ah — вывод на экран, адрес выводимой строки — в dx
mov dx,offset mes_womn; адрес выводимой строки
int 21h;вызов прерывания
ret;
woman endp

call woman; прямой вызов процедуры
mov addrs,offset woman; указатель на процедуру в addrs
call word ptr addrs; косвенный вызов процедуры

addrs dw 0; для указателя на процедуру
mes_womn db 0Dh,0Ah,»Hello, Beautyful Woman!»,0Dh,0Ah,’$’ ; строка для вывода

1
2
3
4
5
6
7
8
9
10
11
12
13

womanproc

movah,09h;аргумент для прерывания int 21h. 09h в ah — вывод на экран, адрес выводимой строки — в dx

movdx,offsetmes_womn; адрес выводимой строки

int21h;вызов прерывания

ret;

womanendp

callwoman; прямой вызов процедуры

movaddrs,offsetwoman; указатель на процедуру в addrs

callwordptraddrs; косвенный вызов процедуры

addrsdw; для указателя на процедуру

mes_womndb0Dh,0Ah,»Hello, Beautyful Woman!»,0Dh,0Ah,’$’; строка для вывода

7.27 .if АБСОЛЮТНОЕ_ВЫРАЖЕHИЕ

.if отмечает начало секции кода, котоpая является сyщественной
частью исходной пpогpаммы только в том слyчае, если
АБСОЛЮТHОЕ_ВЫPАЖЕHИЕ не pавно нyлю. Конец yсловной части должен быть
обозначен.endif (смотрите раздел 7.18 .endif); вы можете также
включить код для альтернативного случая, поставив.else (смотрите
раздел 7.16.else).

Также поддерживаются следующие варианты .if:

.ifdef СИМВОЛ

Ассемблирует следующую секцию кода, если данный СИМВОЛ был
определен.

.ifndef СИМВОЛ
ifnotdef СИМВОЛ

Ассемблирует следующую секцию кода, если данный СИМВОЛ не был
определен. Оба варианта написания эквивалентны.

Указатель — адрес ячейки памяти.

Указатель в программировании на ассемблере — адрес ячейки памяти, содержащей определённые последовательности цифр — блоки кода и данных. Блоки кода и данных называются значениями указателя, например: строка, массив, структура, функция, переменная, константа.

Указатель — адрес ячейки памяти, содержащей блоки кода и данных.

Указателю можно присвоить условное обозначение (const_a, const_b, my_mass_1, MY_STRUCT_1, BitMask, my_prnt_func), определив тип данных или кода, на которые он указывает (db, dd, STRUC, RECORD, proc). Практически мы уже проделывали эти операции, создавая исходники наших простейших программ:


const_a db 5h
const_b dd 15h

my_mass_1 db 10 dup(8)

MY_STRUCT_1 STRUC;В отличие от MASM — STRUC, а не STRUCT
member_1 dw ?;
member_2 db ?;
MY_STRUCT_1 ENDS

BitMask RECORD f0:4=1,f1:4=1,f2:4=0,f3:4=0

my_prnt_func proc PASCAL pMessage1:WORD, pMessage2:WORD
local tmp:WORD
mov ax,pMessage2
mov tmp,ax
mov ah,09h
mov dx,pMessage1
int 21h
mov ah,09h
mov dx,tmp
int 21h
ret
my_prnt_func endp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

const_adb5h

const_bdd15h

my_mass_1db10dup(8)

MY_STRUCT_1STRUC;В отличие от MASM — STRUC, а не STRUCT

member_1dw?;

member_2db?;

MY_STRUCT_1ENDS

BitMaskRECORDf04=1,f14=1,f24=,f34=

my_prnt_funcprocPASCALpMessage1WORD,pMessage2WORD

localtmpWORD

movax,pMessage2

movtmp,ax

movah,09h

movdx,pMessage1

int21h

movah,09h

movdx,tmp

int21h

ret

my_prnt_funcendp

Организация данных в ассемблере.

Прежде, чем переходить к рассмотрению вопроса что такое константа, массив, структура в ассемблере, поговорим о понятии абстракции.

Для упрощения написания кода необходимо преобразовать его в понятный для человека вид, желательно не в ущерб для машины. Для этого используют условности и обобщения — определённую степень абстракции. Один из простейших способов абстракции — разбивка кода и данных на части — блоки по определённым правилам и с определёнными особенностями. Затем эти блоки обзывают понятным для человека языком.

Первоначально программа дробиться на сегменты — наиболее глобальные блоки. В исходнике данной статьи вы столкнётесь с сегментами: .CODE (сегмент кода), .DATA (сегмент инициализированных данных), .DATA? (сегмент неинициализированных данных), .CONST (сегмент констант). Инициализированные данные — те, которым присвоено значение по умолчанию, не инициализированные — значение не присвоено, но память для данных выделена (реально заполнена символами «0» или «?» — в зависимости от компилятора), константы — постоянные, не изменяемые данные. Ещё один из сегментов — .STACK. Если сегменты в коде не указаны, структура программы будет создана согласно настройкам по умолчанию.

Как мы уже знаем, в программах типа .COM, с которыми мы непосредственно работаем, данные, код и стек находятся в одном и том же 16-битном сегменте (в отличие от программ типа .EXE). Не смотря на это, для демонстрации принципа «блочности» и особенностей написания исходного кода на ассемблере, мы «раздробили» исходник на условные части кода (.CODE) и данных (.DATA, .DATA?). TASM проигнорирует наше условное дробление и  ругаться не будет.

О компиляторах

Какую операционную систему вы бы хотели использовать?

Windows DOS Linux BSD QNX MacOS, работающий на
процессоре Intel/AMD
FASM x x x x
GAS x x x x x x
GoAsm x
HLA x x
MASM x x
NASM x x x x x x
RosAsm x
TASM x x

Качество документации

Документация Комментарии
FASM Хорошая Большую часть свободного времени автор отдает в разработку инновационного FASMG. Тем не менее, автор обеспечивает поддержку FASM время от времени обновляет мануалы, а новые функции описывает на собственном форуме. Документацию можно считать достаточно хорошей. Веб-страница документации.
Gas Плохая документирован слабо и документация, скорее, имеет «общий вид». gas ― это ассемблер, который был разработан, чтобы можно было легко писать код для разных процессоров. Документация, которая существует, в основном описывает псевдо коды и ассемблерные директивы. В режиме работы «intel_syntax» документация практически отсутствует. Книги, в которых используется синтаксис «AT&T»: «Программирование с нуля» Джонатона Бартлетта и «Профессиональный язык ассемблера» Ричарда Блюма, Konstantin Boldyshev asmutils — Linux Assembly.
GoAsm Слабая Большая часть синтаксиса описана в руководстве, и опытный пользователь найдет то, что ищет. Множество руководств и размещено на сайте (http://www.godevtool.com/). Несколько учебников GoAsm:
  • Bill Aitken’s tutorials for using GoAsm and the IDE
  • мануал Роберта Cordonnier на французском
  • справочник Патрика Ruiz
HLA Обширая HLA имеет справочное руководство на 500 страниц. Сайт содержит десятки статей и документацию по HLA.
MASM Хорошая Компанией Microsoft написано значительное количество документацию для MASM, существует большое количество справочников написанных для этого диалекта.
NASM Хорошая Авторы NASM больше пишут программное обеспечение для этого диалекта, оставляя написание руководства на «потом». NASM существует достаточно долго, поэтому несколько авторов написали руководство для NASM Джефф Дунтеман (Jeff Duntemann) «Assembly Language Step-by-Step: Programming with Linux», Jonathan Leto «Writing A Useful Program With NASM», на русском языке есть книга Столярова (Сайт А.В. Столярова).
RosAsm Слабая не очень интересные «онлайновые учебники».
TASM Хорошая Компания Borland в свое время выпускала отличные справочные руководства, для TASM были написаны справочные руководства авторами-энтузиастами не связанными с фирмой Borland. Но Borland больше не поддерживает TASM, поэтому большая часть документации, предназначенная для TASM, не печатается и ее становится всё труднее и труднее найти.

Учебники и учебные материалы

Комментарии
FASM Несколько учебников, в которых описывается программирование на FASM:
  • FASM на asmworld
  • Цикл статей «Ассемблер под Windows для чайников»
  • Сайт на narod’е
  • Уроки Iczelion’а от Sulaiman Chang на диалекте FASM
  • Понимание FASM
  • Программирование на языке Assembler в FASM
  • Создание заплаток на ассемблере FASM
  • Норсеев С.А. «Разработка оконных приложений на FASMе»
  • Руслан Аблязов «Программирование на ассемблере на платформе x86-64»
Gas Учебник с использованием синтаксиса AT&TУчебник Ассемблер в Linux для программистов C
HLA 32-разрядная версия «The Art of Assembly Language Programming» (существует и в электронной, и в печатной форме), программирование под Windows или Linux
MASM большое количество книг по обучению программированию под DOS. Не очень много книг о программировании под Win32/64 Пирогов, Юров, Зубков, Фленов
NASM много книг, посвященных программированию в DOS, Linux, Windows. В книге Джеффа Дунтемана «Assembly Language Step-by-Step: Programming with Linux» используется NASM для Linux и DOS. Учебник Пола Картера использует NASM (DOS, Linux).
TASM Как и для MASM, для TASM было написано большое количество книг на основе DOS. Но, так как Borland больше не поддерживает этот продукт, писать книги об использовании TASM перестали. Том Сван написал учебник, посвященный TASM, в котором было несколько глав о программировании под Windows.

Параметры функции.

Функции в ассемблере — это часть кода, которая решает конкретную задачу или несколько, объединённых одной целью задач. Функция может вызываться без дополнительного дублирования кода. Человек способен помнить, воспринимать  и использовать ограниченное число информации. Для облегчения понимания и создания кода его структурируют — дробят на определенные части. Функция — один из вариантов дробления кода — первый шаг к абстракции программы, упрощающий её структуру. Функция включает параметры.

Параметры функции:

  • Параметры ввода (Input Parameters или просто In) — может быть сколько угодно.
  • Параметры вывода (Output Parameters или просто Out) — может быть сколько угодно.
  • Возвращаемое значение (Return value или просто Return) — только одно.

Таким образом, «стандартная» функция «MyFunc» имеет вид:

return MyFunc (In,  In, Out, Out, In, Out,…);

Указанная (Си-подобная) структура функции условна. Любой из параметров может отсутствовать. Функция вообще может не иметь параметров.

Записи с битовыми полями (запись).

Бит — единица данных, может содержать значение 1 или 0. Записи с битовыми полями (records) используют эту возможность.

Каждое битовое поле имеет заданную длину (в битах) и начальное значение. Размер данных типа записи равен сумме длин всех полей

Запись с битовыми полями (запись) — 32 бита.

Опять таки — удобно, экономит место и вычислительное время. Например, чтобы задать цвет точки в изображении (совокупность различных оттенков красного, зелёного, синего (RGB) или свойств окна в операционке Windows.

Не будем подробно разбирать тему, приведём пример кода.

BitMask RECORD f0:4=1,f1:4=1,f2:4=0,f3:4=0

  • xor ax,ax;ax==0
  • mov ax,BitMask;ax==257

Структура в ассемблере.

Структура в ассемблере (structure) — это совокупность переменных, объединенных одним именем. Переменные называются полями и могут быть разными по размеру. Очень удобно обращаться к данным по именам полей. Структура — основа абстракции, «блочности» кода. Понятие КЛАСС в языках высокого уровня есть не что иное, как разновидность структуры. Только в качестве полей в классе кроме данных присутствуют ещё и функции. В качестве поля в структуру может входить структура (пример — в коде).

MY_STRUCT_1 STRUC; структура в ассемблере объявляется словом STRUC
member_1 dw ?;
member_2 db ?;
MY_STRUCT_1 ENDS;

….

  • my_struct MY_STRUCT_1 <?>; структура в ассемблере, созданная на основе объявления.
  • mov my_struct.member_1 ,33h;используем конкретный экземпляр в коде.

Константы, массив и структура в ассемблере — наиболее часто используемые организованные виды данных, однако есть и другие. Если посмотреть на структуру повнимательнее, со стороны нашего подхода к программированию, как к совокупности кода и данных, то остальные виды сгруппированных данных : массив, перечисление, объединение, битовые поля — фактически являются разновидностями структуры, реализованные в целях экономии процессорного времени и объёма памяти.

Директива контроля сегментов NOSEGFIX

Директива имеет параметры в виде имени сегментного регистра и имени формального параметра. Она не генерирует кода, а проверяет, что обращение к данному параметру идет с использованием указанного сегментного регистра, иначе сообщает об ошибке. Эта директива требуется лишь в общих формах команд CMPS и MOVS, где один из операндов может адресоваться только через ES.

Данная директива была расширена для управления префиксами размера и адреса 66H/67H. В этом случае в директиве указывается параметр-число: 0 – нет префиксов, 1- может быть префикс 66H, 2 – может быть префикс 67H, 3 – могут быть оба префикса, 4 – всегда есть оба, 5 – никогда нет 66H, 6 – никогда нет 67H и т.п.

Такими простыми средствами удается описать все множество команд IA-32, например:

Некоторое исключение из стройной системы описаний составляют команды FPU, имеющие операнд в памяти. Для простоты в RASM разрядность таких команд указывается прямо в мнемонике, а не определяется по размеру операнда в памяти. Поэтому в RASM есть, например, команды FIST16, FIST32 и FIST64. Однако на практике, с точки зрения ясности текста, указание разрядности операнда прямо в имени команды FPU оказалось вполне приемлемым.

Строковые инструкции

Каждая строковая инструкция может требовать исходного операнда и операнда назначения. Для 32-битных сегментов строковые инструкции используют регистры ESI и EDI, чтобы указать на операнды источника и назначения, соответственно.

Однако для 16-битных сегментов, чтобы указать на источник и место назначения, используются другие регистры: SI и DI.

Существует 5 основных инструкций для работы со строками в Ассемблере:

    — эта инструкция перемещает 1 byte, word или doubleword данных из одной ячейки памяти в другую;

    — эта инструкция загружается из памяти. Если операндом является значение типа byte, то оно загружается в регистр AL, если типа word — загружается в регистр AX, если типа doubleword — загружается в регистр EAX;

    — эта инструкция сохраняет данные из регистра (AL, AX или EAX) в память;

    — эта инструкция сравнивает два элемента данных в памяти. Данные могут быть размера byte, word или doubleword;

    — эта инструкция сравнивает содержимое регистра (AL, AX или EAX) с содержимым элемента, находящегося в памяти.

Каждая из вышеприведенных инструкций имеет версии byte, word или doubleword, а строковые инструкции могут повторяться с использованием префикса повторения.

Эти инструкции используют пары регистров и , где регистры DI и SI содержат валидные адреса смещения, которые относятся к байтам, хранящимся в памяти. SI обычно ассоциируется с DS (сегмент данных), а DI всегда ассоциируется с ES (дополнительный сегмент).

Регистры (или ESI) и (или EDI) указывают на операнды источника и назначения, соответственно. Предполагается, что операндом-источником является (или ESI), а операндом назначения — место в памяти, на которое указывает пара (или EDI).

Для 16-битных адресов используются регистры SI и DI, а для 32-битных адресов используются регистры ESI и EDI.

В следующей таблице представлены различные версии строковых инструкций и предполагаемое место операндов:

Основная инструкция Операнды в: Операция byte Операция word Операция doubleword
MOVS ES:DI, DS:SI MOVSB MOVSW MOVSD
LODS AX, DS:SI LODSB LODSW LODSD
STOS ES:DI, AX STOSB STOSW STOSD
CMPS DS:SI, ES:DI CMPSB CMPSW CMPSD
SCAS ES:DI, AX SCASB SCASW SCASD

Передача параметров через регистры.

Самым очевидным, быстрым и простым способом передачи параметров функции в ассемблере может показаться способ передачи параметров через регистры:

Функция (процедура) содержит два параметра:myFunc (a,b)

;Ассемблерный код:
mov ax, a; Первый параметр (самый левый) — сверху
mov bx, b; Второй параметр
call myFunc

myFunc:

(команды, которые могут использовать стек):
add ax,1 ; используем первый параметр
; Его адрес в сегменте стека ВР + 4, потому что при выполнении
; команды CALL при вызове функции, в стек поместили адрес возврата — 2 байта для процедуры
; типа NEAR (или 4 — для FAR), а потом еще и ВР — 2 байта (push bp — в начале нашей функции)
mov bx,ax ; используем второй параметр
(ещё команды)

ret ; Возвращаемся к коду вызова функции (call) из основного кода

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

;Ассемблерный код:

movax,a; Первый параметр (самый левый) — сверху

movbx,b; Второй параметр

callmyFunc

myFunc

(команды,которыемогутиспользоватьстек)

addax,1; используем первый параметр

; Его адрес в сегменте стека ВР + 4, потому что при выполнении
; команды CALL при вызове функции, в стек поместили адрес возврата — 2 байта для процедуры
; типа NEAR (или 4 — для FAR), а потом еще и ВР — 2 байта (push bp — в начале нашей функции)

movbx,ax; используем второй параметр

(ещёкоманды)

ret; Возвращаемся к коду вызова функции (call) из основного кода

Такой код имеет несколько недостатков, основной из которых — ограничение количества параметров (не больше, чем регистров). Есть и другие, менее очевидные сложности, которые мы не будем обсуждать в рамках этой статьи.

Из плюсов передачи параметров (и возврата значений) функции в ассемблере через регистры можно назвать скорость работы кода.

Теперь перейдём к рассмотрению более совершенных методов организации работы функций, которые используются языками высокого уровня.

Конвенция Передача параметров Освобождение стека
CDECL загоняются в стек слева направо — снизу вверх вызывающая программа
PASCAL загоняются в стек слева направо — сверху вниз сама процедура
STDCAL загоняются в стек слева направо — снизу вверх сама процедура

Функции WinAPI.

Переходим к актуальному вопросу — написанию прикладных программ для Windows. Одним из составляющих основ программирования в операционной среде Windows продолжает оставаться активное использование функций WinAPI, встроенных в любую операционную систему Windows, начиная от первой версии и заканчивая последней на настоящий момент десятой версией. Функции WinAPI писались на Си и были созданы для использования при написании прикладных программ на языке программирования Си. Программирование для Windows с использованием WinAPI и макросов на ассемблере и Си имеют 90% внешнего и  100% смыслового сходства.

Конвенция вызова функций CDECL (Си, С++ и др.).

Параметры загоняются в стек слева направо — снизу вверх, стек очищается вызывающая функция:

Функция (процедура) содержит пять параметров:myFunc (a,b,c,d,e)

;Ассемблерный код:
push e; Пятый параметр — сверху
push d;
push c;
push b;Второй параметр
push a; Первый параметр (самый левый) — снизу
call myFunc
add sp,10; Стек освобождает вызывающая функция

;Функция содержит пять параметров:
myFunc:
push bp
mov bp,sp; Создаём стековый кадр. В bp — указатель на стековый кадр, регистр bp использовать нельзя!
e equ ; Последний параметр — сверху ()
d equ
c equ
b equ
a equ

;команды, которые могут использовать стек:
mov ax,e ; считать пятый параметр — . Можно и так, но это менее понятно: mov ax,
; Его адрес в сегменте стека ВР + 4, потому что при выполнении
; команды CALL при вызове функции, в стек поместили адрес возврата — 2 байта для процедуры
; типа NEAR (или 4 — для FAR), а потом еще и ВР — 2 байта (push bp — в начале нашей функции)
mov bx,с ; считать третий параметр — . Можно и так, но это менее понятно: mov bx,
;… ещё команды

pop bp
ret ; Из стека дополнительные байты не извлекаются — стек освободит код вызывающей программы (add sp,10)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

;Ассемблерный код:

pushe; Пятый параметр — сверху

pushd;

pushc;

pushb;Второй параметр

pusha; Первый параметр (самый левый) — снизу

callmyFunc

addsp,10; Стек освобождает вызывающая функция

 
;Функция содержит пять параметров:

myFunc

pushbp

movbp,sp; Создаём стековый кадр. В bp — указатель на стековый кадр, регистр bp использовать нельзя!

eequbp+12; Последний параметр — сверху ()

dequbp+10

cequbp+8

bequbp+6

aequbp+4

;команды, которые могут использовать стек:

movax,e; считать пятый параметр — . Можно и так, но это менее понятно: mov ax,

; Его адрес в сегменте стека ВР + 4, потому что при выполнении
; команды CALL при вызове функции, в стек поместили адрес возврата — 2 байта для процедуры
; типа NEAR (или 4 — для FAR), а потом еще и ВР — 2 байта (push bp — в начале нашей функции)

movbx,с; считать третий параметр — . Можно и так, но это менее понятно: mov bx,

;… ещё команды

popbp

ret; Из стека дополнительные байты не извлекаются — стек освободит код вызывающей программы (add sp,10)