Справка разделена на 2 части: туториал и таблицы. В таблицах, пока что, самое важно и то, что было мне не лень скопировать и расписать настолько подробно, насколько я мог. Все остальное в ресурсах
Язык Ассемблера лишь перевод комманд из байткода, который умеет читать процессор на английский. MOV же говорит чуть больше. чем 0x8E. Ассемблер же - компилятор, переводящий наш код в формате ASCII-текста в байткод
Из базового курса информатики известно, что у процессора есть доступ к оперативной памяти (ОЗУ). Оттуда мы можем считывать и туда записывать данные по определенному адресу. Доступ к Постоянным Запоминающим Устройствам (ПЗУ) выполняется чуть хитрее - через прерывания BIOS или Операционной Системы
⚠️ Что код, что данные - одно и тоже. Будьте аккуратны и не позвольте процессору пытаться выполнять то, что задумывалось как данные
Регистры - ячейки памяти небольшого размера (1-4 байта) в процессоре для вычислений. То есть все вычисления (сложение, умножение, логические операции...) происходят именно в регистрах.
Если есть некие числа в оперативной памяти, с которыми надо выполнить какие-либо операции, например, сложить, надо обязательно одно из них переместить в регистр.
Регистры бывают разные, о чем можно видеть в таблице. Работу с некоторыми будет в дальнейшем показано
Стек - форма хранения данных, движащаяся со старших, заканчивая младшими адресами.
В переводе - стопка. Представьте стопку тарелок: сначала красная, потом положили зеленую, потом синюю. Если мы начнем доставать тарелки, то в обратном порядке - синяя, зеленая, красная.
Стек играет важную роль в программировании на Ассемблере, ведь такая структура удобна для:
- Временного хранения данных (локальные переменные)
- Передачи данных другим кускам кода
Допустим, если в регистрах хранятся важные данные, но регистры нужны для выполнения каких-то операций, мы можем просто положить в стек значения регистров, положить в регистры новые данные, обработать, вернуть значения из стека:
; Сохраняем значения
push eax ; Стек: (другие данные), eax
push ebx ; Стек: (другие данные), eax, ebx
; Делаем какие-то операции
mov eax, 5
mov ebx, 4
add eax, ebx
; Возвращаем на место значения
pop ebx ; Стек: (другие данные), eax
pop eax ; Стек: (другие данные)
Точнее, их реализация на ассемблере.
Вот есть функция func
делающая какие-то важные операции:
func: ; Это метка, о них далее в разделе про FASM
mov eax, 5
mov ebx, 4
add eax, ebx
ret
Чтобы вызвать функцию используется call <метка/адрес>
. Процессор перейдет на начало функции и начнет его выполнять, а адрес следующей после call
команды поместится в стек. Зачем? Мы же хотим вернуться обратно после выполнения функции, а для этого используется команда ret
в конце функции - вытаскивает из стека адрес и заставляет процессор вернуться туда
ℹ️ "Функция" просто для упрощения объяснения. Естественно в ассемблере функций нет
В x86 существует команда CMP
(compare). Смысл в ней в том, чтобы сравнивать 2 цисла. Результат записывается во флаги.
mov eax, 4
mov ebx, 5
cmp eax, ebx
По сути это тоже будет абстракцией, ведь в памяти все данные одинаковы
db "Meow!", 0
dw 1, 43, 510
В памяти будет 4D656F7700
и 01002B00FE01
. Во втором каждый "элемент" занимает 2 байта, поэтому обращение к ним будет по базовому адресу (адресу первого элемент) + 2*индекс. Из-за этого индекс первого элемента - 0
For цикл:
mov ecx, 10
@@: ; Это анонимная метка, о них потом тоже раскажу
call some_func
loop @b ; Прыжок по объявленной метке выше
Будет вызывать функцию 10 раз
Foreach цикл:
str db "Meow", 0
mov eax, str
; eax - значение регистра eax
; [eax] - значение ячейки памяти по адресу eax
@@:
add byte [eax], 2 ; Сдвигаем символ на 2 (M -> 0...)
inc eax ; Переходим к следующему символу
cmp [eax], 0 ; Сравниваем символ по адресу eax с 0
jne @b ; Если до конца не дошли - производим следующую итерацию
While цикл:
mov eax, 1
@@:
imul eax, eax, 2
cmp eax, 1000
jng @b
Как я уже говорил, ассемблер - компилятор кода, написанного на языке ассемблера. Компиляторы бывают разные: различаются синтаксисом и платформами. Один из них - FASM (flat assembler)
Тип | префикс | размер в байтах |
размер в битах |
---|---|---|---|
byte | b | 1 | 8 |
word | w | 2 | 16 |
double word | d | 4 | 32 |
d<префикс>
- объявление
db 34, 5
dw 1024
в бинарном файле станет 22050004
r<префикс>
- займет место
rb 5
rd 2
в бинарнике станет 00000000000000000000000000
Метки есть 3х типов:
start:
mov eax, ...
@@:
mov ebx, ...
cool_string db "Hello world!", 0
Метка ассемблируется в просто адрес, где мы его поставили. Например:
start:
...
jmp start:
ассемблируется в E848
, где 48, в моем случае, адрес start
Анонимные метки нужны для тех случаев, когда, например, лень придумывать имя, да оно и не будет более одного раза использовать.
@@
- объявление анонимной метки@f
- следующая в коде анонимная метка@b
- предыдущая в коде анонимная метка
Константы, очевидно, некие значения, которые являются постоянными. Чтобы не сохранять их в памяти, как переменные, но были чем-то одним в разных частях программы, но при изменении константы не пришлось значения изменять во всем коде, можно сделать так:
G equ 667430
...
mov eax, G
Не совсем константы, но стоит упомянуть, что можно заставить FASM на этапе препроцессинга не только подставлять контстанты, но и вычислять математические выражения:
mov eax, 4*45+3
ℹ️ Все вычисления в правильном порядке
macro macro_name {
mov eax, 5
mov ebx, 4
add eax, ebx
}
, то macro_name
на этапе препроцессинга заменится на содержимое макроса
.inc
файлы, из которых берутся константы и макросы на этапе препроцессинга
Я не буду пересказывать всю историю сей ОСи. Вкратце: написанная на ассемблере ОСь, в котором просто можно делать графические приложения на, собственно, ассемблере, что очень подходит для обучения
Для взаимодействия в ОСью существуют системные вызовы, которые описаны на Вики
use32 ; Используем 32 бита
org 0 ; Адресация с нуля
db "MENUET01" ; Индентификатор
dd 1 ; Версия заголовка
dd _start ; Адрес начала программы
dd _end ; Адрес конца программы
dd _stack ; Адрес конца стека
dd _mem ; Адрес конца доступной памяти
dd 0 ; Адрес буфера для параметров
dd 0 ; Так надо (зарезервировано)
_start:
mov eax, 0 ; Системный вызов для создания окна
mov ebx, 200 ; Ширина в 200 пикселей
mov ecx, 100 ; Высота в 100 пикселей
mov edx, 0x321E1E1E ; 1й тип окна, размеры от начала экрана, есть заголовок, цвет залития - 1E1E1E
mov esi, 0x002A2E32 ; без градиента, перемещаемое, цвет заголовка - 2A2E32
int 40h ; Вызываем
mov eax, 4 ; Системный вызов для вывода на окно строки
mov ebx, 5*65536+5 ; Отступ в 5 пк по X и Y
mov ecx, 0xB0FFFFFF ; Строка заканчиается нулем, не закрашивать фон, UTF-8, рисовать в окно. размер шрифта - самый мелкий
mov edx, hw ; Сама строка
int 40h ; Вызываем
mov eax, 10 ; Ждем события (просто чтобы не закрывалось окно сразу)
int 40h
mov eax, -1 ; Закрываемся
int 40
hw db "Hello, Kolibri!", 0
_end:
_stack:
_mem:
Та же программа, но со стандартным инклудом:
include "marcos.inc"
KOS_APP_START
CODE ; Секция кода
_start:
mcall 0, 200, 100, 0x321E1E1E, 0x002A2E32 ; Создать окно 200х100
mcall 4, 5*65536+5, 0xB0FFFFFF, hw ; Вывести текст [hw] по координатам (5, 5)
mcall 10 ; Подождать какого-либо события (чтоб окно не закрылось сразу)
mcall -1 ; Корректно завершить работу программы
DATA ; Секция инициализированных данных
hw db "Hello, Kolibri!", 0
UDATA ; Секция неинициализированных данных... наверно...
; Да, объявлять эту секцию обязательно
KOS_APP_END
32-битные | 16-битные | 8-битные |
---|---|---|
EAX | AX | AH, AL |
EBX | BX | BH, BL |
ECX | CX | CH, CL |
EDX | DX | DH, DL |
ESI | SI | - |
EDI | DI | - |
ESP | SP | - |
EBP | BP | - |
- AX - Accumulator
- BX - Base Register
- CX - Count Register
- DX - Data Register
- SI - Source Index
- DI - Destination Index
- SP - Stack Pointer
- BP - Base Pointer
Байты:
|3 |2 |1 |0 |
| |AH|AL|
| | AX |
| EAX |
Т.е. если EAX = 0x01020304, то AX = 0x0304, AH = 0x03, AL = 0x04
Аналогично с другими регистрами
Флаги - это набор битов, объединенные в единый регистр (E)FLAGS, необходимыые для определения условий
В отличие от РОН к флагам нет прямого доступа (мы не можем напрямую записать какое-либо значение), но флаги влияют на поведение некоторых комманд, о которых далее
Перечислю самое необходимое. Остальное на Вики
Бит | Обозначение | Название | На русском |
---|---|---|---|
0 | CF | Carry Flag | Флаг переноса |
2 | PF | Parity Flag | Флаг четности |
6 | ZF | Zero Flag | Флаг нуля |
11 | OF | Overflow Flag | Флаг переполнения |
- EIP - указатель команды, исполняемая на следующем шаге
- MMX (MM0-MM7) - регистры расширения MMX. По версии Вики считаются устаревшими
- ST0-ST7 - регистры, образующие стек модуля математического сопроцессора для операций с плавающей точкой (FPU). Короче: регистры для вычисления дробных чисел
Условные обозначения:
r
- регистрm
- память (т.е. некая информация, находящаяся в оперативной памяти, дост к которому получаем по его адресу)n
- числоW+
- значение размером word и большеD+
- значение размером dword и больше[r][m], [r, m, n][r, n]
-[r]
с[r, m, n]
,[m]
с[r, n]
Присваивание
MOV dst[r][m], src[r, m, n][r, n]
dst
- Куда пересылаются данныеsrc
- Откуда берутся данные
mov EAX, 4 ; EAX = 4
mov EBX, 5 ; EBX = 5
mov EAX, EBX ; EAX = 5, EBX = 5
mov EBX, 3
mov [20h], ebx ; 20h: 0x3
mov EAX, [20h] ; EAX = 3
Присваивание при условии xx
Аналлогично MOV
Помещение в стек
PUSH src[r, n, m]W+
Взятие из стека
POP dst[r, m]W+
Перемещение в стек регистров общего назначения
PUSHA
Взятие из стека регистров общего назначения
POP
Сложение
ADD dst[r][m], src[r, m, n][r, n]
dst
- Первое слагаемое и суммаsrc
- Второе слагаемое
mov eax, 4 ; EAX = 4
mov ebx, 5 ; EBX = 5
add eax, ebx ; EAX = 9, EBX = 5
; EAX - 4 байта - dword
mov [20h], eax ; [20h] = 0x9
add dword [20h], 3 ; [20h] = 0xC
Вычитание
SUB dst[r][m], src[r, m, n][r, n]
dst
- Уменьшаемое и разностьsrc
- Вычитаемое
остальное потом допишу
Мнемоника | Условие | Флаг |
---|---|---|
о | переполнение | OF=1 |
no | нет переполнения | OF=0 |
c b nae |
перенос ниже не выше, не равно |
CF=1 |
nc ae nb |
нет переноса выше или равно не ниже |
CF=0 |
e z |
равно ноль |
ZF=1 |
ne nz |
неравно не ноль |
ZF=0 |
be na |
ниже или равно не выше |
CF or ZF = 1 |
a nbe |
выше не ниже, не равно |
CF or ZF = 0 |
s | отрицательное число | SF=1 |
ns | положительное число | SF=0 |
p pe |
четное четное число ненулевых битов |
PF=1 |
np po |
нечетное | PF=0 |
l nge |
меньше не больше, не равно |
SF xor OF = 1 |
ge nl |
больше или равно не меньше |
SF xor OF = 0 |
le ng |
меньше или равно не больше |
(SF xor OF) or ZF = 1 |
g nle |
больше не меньше, не равно |
(SF xor OF) or ZF = 0 |