Название | Разрядность | Основное назначение |
EAX | Аккумулятор | |
EBX | База | |
ECX | Счётчик | |
EDX | Регистр данных | |
EBP | Указатель базы | |
ESP | Указатель стека | |
ESI | Индекс источника | |
EDI | Индекс приёмника | |
EFLAGS | Регистр флагов | |
EIP | Указатель инструкции (команды) | |
CS | Сегментный регистр | |
DS | Сегментный регистр | |
ES | Сегментный регистр | |
FS | Сегментный регистр | |
GS | Сегментный регистр | |
SS | Сегментный регистр |
Регистры EAX, EBX, ECX, EDX – это регистры общего назначения. Они имеют определённое назначение (так уж сложилось исторически), однако в них можно хранить любую информацию.
Регистры EBP, ESP, ESI, EDI – это также регистры общего назначения. Они имеют уже более конкретное назначение. В них также можно хранить пользовательские данные, но делать это нужно уже более осторожно, чтобы не получить «неожиданный» результат.
Регистр флагов и сегментные регистры требуют отдельного описания и будут более подробно рассмотрены далее.
Пока для вас здесь слишком много непонятных слов, но со временем всё прояснится)))
Когда-то процессоры были 16-разрядными, и, соответственно, все их регистры были также 16-разрядными. Для совместимости со старыми программами, а также для удобства программирования некоторые регистры разделены на 2 или 4 «маленьких» регистра, у каждого из которых есть свои имена. В таблице 2.2 перечислены такие регистры.
Вот пример такого регистра.
Из этого следует, что вы можете написать в своей программе, например, такие команды:
MOV AX, 1
MOV EAX, 1
Обе команды поместят в регистр AX число 1. Разница будет заключаться только в том, что вторая команда обнулит старшие разряды регистра EAX, то есть после выполнения второй команды в регистре EAX будет число 1. А первая команда оставит в старших разрядах регистра EAX старые данные. И если там были данные, отличные от нуля, то после выполнения первой команды в регистре EAX будет какое-то число, но не 1. А вот в регистре AX будет число 1. Сложно? Ну это пока… Со временем вы к таким вещам привыкните.
Мы пока не говорили о разрядах (битах). Эту тему мы обсудим в разделах, посвящённых системам счисления. А сейчас пока вам достаточно знать, что нулевой разряд (бит) – это младший бит. Он крайний справа. Старший бит – крайний слева. Номер старшего бита зависит от разрядности числа/регистра. Например, в 32-разрядном регистре старшим битом является 31-й бит (потому что отсчёт начинается с 0, а не с 1).
Ниже приведён список регистров общего назначения, которые можно поделить описанным выше способом и при этом к «половинкам» и «четвертинкам» этих регистров можно обращаться в программе как к отдельному регистру.
Регистр | Старшие разряды | Имена 16-ти и 8-ми битных регистров |
31…16 | 15…8 | 7…0 |
EAX | ... | AX |
AH | AL | |
EBX | ... | BX |
BH | BL | |
ECX | ... | CX |
CH | CL | |
EDX | ... | DX |
DH | DL | |
ESI | ... | SI |
EDI | ... | DI |
EBP | ... | BP |
ESP | ... | SP |
EIP | ... | IP |
Циклы.
Синтаксис объявления меток
Метка представляет собой символическое имя, вместо которого компилятор подставляет адрес. В программе на ассемблере можно присвоить имя любому адресу в коде или данных. Обычно метки используются для организации переходов, циклов или каких-то манипуляций с данными. По сути имена переменных, объявленных с помощью директив объявления данных, тоже являются метками. Но с ними компилятор дополнительно связывает размер переменной. Метка объявляется очень просто: достаточно в начале строки написать имя и поставить двоеточие. Например:
m1: mov ax,4C00h int 21h |
Теперь вместо имени m1 компилятор везде будет подставлять адрес комады mov ax,4C00h. Можно объявлять метку на пустой строке перед командой:
exit_app: mov ax,4C00h int 21h |
Имя метки может состоять из латинских букв, цифр и символов подчёркивания, но должно начинаться с буквы. Имя метки должно быть уникальным. В качестве имени метки нельзя использовать директивы и ключевые слова компилятора, названия команд и регистров (в этом случае FASM покажет сообщение об ошибке). FASM различает регистр символов в именах меток. Можно также объявлять несколько меток на один адрес. Например:
no_error: exit_app: m1: mov ax,4C00h |
Подробнее о синтаксисе объявления меток рассказывается в части 27.
Команда LOOP
Для организации цикла предназначена команда LOOP. У этой команды один операнд — имя метки, на которую осуществляется переход. В качестве счётчика цикла используется регистр CX. Команда LOOPвыполняет декремент CX, а затем проверяет его значение. Если содержимое CX не равно нулю, то осуществляется переход на метку, иначе управление переходит к следующей после LOOP команде.
Содержимое CX интерпретируется командой как число без знака. В CX нужно помещать число, равное требуемому количеству повторений цикла. Понятно, что максимально может быть 65535 повторений. Ещё одно ограничение связано с дальность перехода. Метка должна находиться в диапазоне -127…+128 байт от команды LOOP (если это не так, FASM сообщит об ошибке).
Пример цикла
В качестве примера я приведу простую программу, которая будет печатать все буквы английского алфавита. ASCII-коды этих символов расположены последовательно, поэтому можно выводить их в цикле. Для вывода символа на экран используется функция DOS 02h (выводимый байт должен находиться в регистре DL).
use16 ;Генерировать 16-битный код org 100h ;Программа начинается с адреса 100h mov ah,02h ;Для вызова функции DOS 02h - вывод символа mov dl,'A' ;Первый выводимый символ mov cx,26 ;Счётчик повторений цикла metka: int 21h ;Обращение к функции DOS inc dl ;Следующий символ loop metka ;Команда цикла mov ah,09h ;Функция DOS 09h - вывод строки mov dx,press ;В DX адрес строки int 21h ;Обращение к функции DOS mov ah,08h ;Функция DOS 08h - ввод символа без эха int 21h ;Обращение к функции DOS mov ax,4C00h ;\ int 21h ;/ Завершение программы ;------------------------------------------------------- press: db 13,10,'Press any key...$' |
Условные операторы.
Условный оператор if-else используется для принятия решения о дальнейшем пути
исполнения программы. Синтаксис условного оператора в нотации языков С и C++:
if (выражение) оператор_1;
else оператор_2
Алгоритм работы условного оператора — вычисляется логическое значение выражения: если оно истинно, то выполняется оператор_1, в противном случае — оператор_2.
В общем случае условный оператор может состоять из одного блока if (без блока else):
if (выражение) оператор_1;
В программе на ассемблере данные варианты условного оператора можно реализовать следующим образом:
короткий вариант оператора if (выражение) оператор_1; cmp opl,op2 вычисление выражения jne endif
;... последовательность команд, соответствующая оператор_1
endif:;конец коротково условново оператора
Строго говоря, использование команды СМР при реализации условного оператора не является обязательным. В данном случае она скорее обозначает место вычисления некоторого условия в программе, по результатам которого принимается решение о ветвлении. Вместо данной команды можно использовать любую команду, изменяющую флаг, который будет анализироваться последующим оператором условного перехода. Эти же рассуждения касаются и команды JNE, вместо которой может стоять требуемая в данном вычислительном контексте команда условного перехода:
:полный вариант оператора if (выражение) оператор_1; else оператор_2 cmp opl,op2 вычисление выражения jne elsel
;... последовательность команд, соответствующая оператор і
jmp endif elsel:
;... последовательность команд, соответствующая оператор_2
endif:;конец полного условного оператора.
Процедуры.
Команды CALL и RET
Для работы с процедурами предназначены команды CALL и RET. С помощью команды CALL выполняется вызов процедуры. Эта команда работает почти также, как команда безусловного перехода (JMP), но с одним отличием — одновременно в стек сохраняется текущее значение регистра IP. Это позволяет потом вернуться к тому месту в коде, откуда была вызвана процедура. В качестве операнда указывается адрес перехода, который может быть непосредственным значением (меткой), 16-разрядным регистром (кроме сегментных) или ячейкой памяти, содержащей адрес.
Возврат из процедуры выполняется командой RET. Эта команда восстанавливает значение из вершины стека в регистр IP. Таким образом, выполнение программы продолжается с команды, следующей сразу после команды CALL. Обычно код процедуры заканчивается этой командой. Команды CALL и RET не изменяют значения флагов (кроме некоторых особых случаев в защищенном режиме). Небольшой пример разных способов вызова процедуры:
use16 ;Генерировать 16-битный код org 100h ;Программа начинается с адреса 100h mov ax,myproc mov bx,myproc_addr xor si,si call myproc ;Вызов процедуры (адрес перехода - myproc) call ax ;Вызов процедуры по адресу в AX call [myproc_addr] ;Вызов процедуры по адресу в переменной call word [bx+si] ;Более сложный способ задания адреса;) mov ax,4C00h ;\ int 21h ;/ Завершение программы ;---------------------------------------------------------------------- ;Процедура, которая ничего не делает myproc: nop ;Код процедуры ret ;Возврат из процедуры ;---------------------------------------------------------------------- myproc_addr dw myproc ;Переменная с адресом процедуры |