Skip to content

Instantly share code, notes, and snippets.

@VerySweetBread
Last active February 24, 2024 20:54
Show Gist options
  • Save VerySweetBread/1e3bbf3ff3f9a322ba7e40da36a6439b to your computer and use it in GitHub Desktop.
Save VerySweetBread/1e3bbf3ff3f9a322ba7e40da36a6439b to your computer and use it in GitHub Desktop.

Основы ассемблера x86

Содержание

Предисловие и как использовать эту справку

Справка разделена на 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

Что это?

Как я уже говорил, ассемблер - компилятор кода, написанного на языке ассемблера. Компиляторы бывают разные: различаются синтаксисом и платформами. Один из них - 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

Присваивание 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
CMOVxx

Присваивание при условии xx

Аналлогично MOV

PUSH

Помещение в стек PUSH src[r, n, m]W+

POP

Взятие из стека POP dst[r, m]W+

PUSHA/PUSHAD

Перемещение в стек регистров общего назначения PUSHA

POPA/POPAD

Взятие из стека регистров общего назначения POP

Арифметические команды

ADD

Сложение 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

Вычитание 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

Ресурсы

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment