Полный справочник ключевых концепций x86-64 ассемблера с ссылками на развернутые статьи курса для углубленного изучения каждой темы.
🏛️ 1. Структура программы: Секции и Директивы
📖 Контекст: Как настроить инструменты для работы с секциями и запустить первый код, читайте в статье «Настройка NASM x86-64 и VS Code».
Секции и Директивы
| Элемент | Назначение | Пример |
|---|---|---|
section .text |
Исполняемый код (инструкции) | _start: mov rax, 1 |
section .data |
Инициализированные данные | msg db "Hello", 10 |
section .bss |
Неинициализированные данные (резервируется место) | buffer resb 256 |
align N |
Выравнивание адреса следующего данного на N байт | align 8, align 16 |
global, extern |
Управление видимостью меток | global _start, extern printf |
db, dw, dd, dq |
Определить данные (1, 2, 4, 8 байт) | my_array dw 10, 20, 30 |
resb, resw,… |
Зарезервировать N байт, слов, … | big_buffer resq 1024 |
equ |
Определить константу | EXIT_CODE equ 60 |
times |
Повторить инструкцию/данные N раз | times 100 db 0 |
ПРОБЛЕМА
Использование .data для больших пустых буферов увеличивает размер исполняемого файла.
section .data
; ❌ Этот буфер займет 1 МБ на диске!
buffer times 1000000 db 0
ПОСЛЕДСТВИЯ
- Размер исполняемого файла: 1+ МБ
- Медленная загрузка программы
- Напрасная трата дискового пространства
РЕШЕНИЕ
Используйте .bss для резервирования неинициализированной памяти.
section .bss
; ✅ Размер файла не изменится!
buffer resb 1000000
ПРЕИМУЩЕСТВА
- Размер исполняемого файла: несколько КБ
- Быстрая загрузка
- Память выделяется только при запуске
🐛 Частая ошибка: Подробнее о последствиях неправильного использования секций и других типичных ошибках читайте в статье «Топ ошибок в NASM x86-64».
📄 Практический пример структуры программы
; Секция констант
%define STDOUT 1
%define SYS_WRITE 1
%define SYS_EXIT 60
section .data
; Инициализированные данные
msg db "Hello, World!", 10 ; 10 = символ новой строки
msg_len equ $ - msg ; $ = текущий адрес, вычисляем длину
numbers dq 10, 20, 30, 40 ; Массив из 4 qword
pi dd 3.14159 ; Число с плавающей точкой (float)
section .bss
; Неинициализированные данные (только резервирование места)
buffer resb 1024 ; Буфер на 1024 байта
user_input resq 1 ; Одно 64-битное число
section .text
global _start ; Точка входа видна компоновщику
_start:
; Вывод сообщения
mov rax, SYS_WRITE
mov rdi, STDOUT
lea rsi, [msg] ; Адрес строки
mov rdx, msg_len ; Длина строки
syscall
; Завершение программы
mov rax, SYS_EXIT
xor rdi, rdi ; Код возврата 0
syscall
Команды для сборки и запуска:
nasm -f elf64 -g -F dwarf program.asm -o program.o
ld program.o -o program
./program
Суффиксы размера данных
| Суффикс | Название | Размер (бит) | Размер (байт) | Пример директивы | Пример инструкции |
|---|---|---|---|---|---|
| b | Byte | 8 | 1 | db, resb |
movsb |
| w | Word | 16 | 2 | dw, resw |
movsw |
| d | Dword | 32 | 4 | dd, resd |
movsd |
| q | Qword | 64 | 8 | dq, resq |
movsq |
🧠 2. Архитектура регистров x86-64
📖 Контекст: Как установить NASM, Make и GDB, читайте в статье «Настройка NASM x86-64 и VS Code».
Обзор категорий регистров
| Категория | Регистры | Размер (бит) | Основное назначение |
|---|---|---|---|
| Регистры общего назначения (GPR) | RAX, RBX, RCX, RDX, RSI, RDI, RBP, RSP, R8–R15 |
64 | Целочисленная арифметика, адреса, счетчики |
| Указатель инструкций | RIP |
64 | Адрес следующей исполняемой инструкции. Не изменяется напрямую |
| Регистр флагов | RFLAGS |
64 | Состояние процессора: ZF, CF, SF, OF и др. |
| Векторные/SIMD регистры | XMM0–XMM15 (расширения YMM, ZMM) |
128+ | Плавающая точка, параллельная обработка данных |
| Сегментные регистры | CS, DS, SS, ES, FS, GS |
16 | В 64-битном режиме: специализированные (FS, GS для Thread-Local Storage) |
Регистр флагов RFLAGS
Флаги изменяются процессором после арифметических и логических операций и критичны для условных переходов.
| Флаг | Название | Установлен (=1), когда… | Используется в… |
|---|---|---|---|
| ZF | Zero Flag | Результат операции равен нулю | je, jz, jne, jnz |
| CF | Carry Flag | Перенос/заём (для беззнаковых) | jb, ja, adc, sbb |
| SF | Sign Flag | Результат отрицательный (старший бит = 1) | js, jns, jl, jg |
| OF | Overflow Flag | Переполнение (для знаковых) | jo, jno, jl, jg |
| DF | Direction Flag | Направление строк (0=вперёд, 1=назад) |
movs*, stos*, cld, std |
Пример работы флагов:
mov al, 0xFF
add al, 1 ; al = 0x00, ZF=1 (результат ноль), CF=1 (был перенос)
mov rax, 5
cmp rax, 10 ; Вычитание 5-10 = -5
; ZF=0 (не ноль), SF=1 (отрицательный), CF=1 (заём)
jl less_label ; Переход, так как 5 < 10 (знаковое сравнение)Роли регистров (System V ABI)
| Регистр | Мнемоника | Основная роль при вызове функций¹ | Кто сохраняет² |
|---|---|---|---|
rax |
Accumulator | Возвращаемое значение, номер syscall | Вызывающий |
rdi |
Destination Index | 1-й аргумент функции / syscall | Вызывающий |
rsi |
Source Index | 2-й аргумент функции / syscall | Вызывающий |
rdx |
Data | 3-й аргумент функции / syscall | Вызывающий |
rcx |
Counter | 4-й аргумент функции, счетчик loop |
Вызывающий |
r8/r9 |
Extended | 5-й / 6-й аргумент функции | Вызывающий |
rsp |
Stack Pointer | Указатель стека (всегда на вершине) | Вызываемый |
rbp |
Base Pointer | Указатель базы стека | Вызываемый |
rbx |
Base | Регистр общего назначения (callee-saved) | Вызываемый |
¹ Вызывающий сохраняет: Если нужно значение после
call, используйтеpushперед вызовом.
² Вызываемый сохраняет: Если меняете этот регистр, сохраните в начале и восстановите в конце.
📖 Подробнее: Разбор стекового кадра и ABI при взаимодействии с C — в статье «Связь C и NASM: Конвенции вызовов (ABI) и Оптимизация».
Анатомия регистров: Иерархия и части
Биты: 63 56 55 48 47 40 39 32 31 24 23 16 15 8 7 0
┌─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┐
│ │ │ │ │ │ │ AH │ AL │
│ │ │ │ │ │ │─────────┴─────────┤
│ │ │ │ │ │ │ AX │
│ │ │ │ ├─────────┴─────────┴───────────────────┤
│ │ │ │ │ EAX │
├─────────┴─────────┴─────────┴─────────┴───────────────────────────────────────┤
│ RAX │
└───────────────────────────────────────────────────────────────────────────────┘
│◄──── обнуляется при записи в EAX ────►│📌 Ключевые правила работы с частями регистров
; ✔ Запись в 32-битную часть обнуляет старшие биты
mov eax, 0x12345678 ; → rax = 0x0000_0000_1234_5678 (биты 63:32 = 0)
; ✗ Запись в 16-битную часть НЕ изменяет остальные биты
mov ax, 0xFFFF ; → rax = 0xXXXX_XXXX_XXXX_FFFF (биты 63:16 без изменений)
; ✗ Запись в младший байт НЕ изменяет остальные биты
mov al, 0x42 ; → rax = 0xXXXX_XXXX_XXXX_XX42 (биты 63:8 без изменений)
; ⚠ Запись в старший байт изменяет только биты 15:8
mov ah, 0x99 ; → rax = 0xXXXX_XXXX_XXXX_99XX (биты 15:8 изменяются)Практическое применение:
; Наиболее эффективный способ обнулить ВЕСЬ 64-битный регистр
xor eax, eax ; rax автоматически становится 0x0000_0000_0000_0000
; Изменение только младшего байта, сохраняя остальное
and al, 0x0F ; Очистить старшие 4 бита ALТаблица именования частей регистров
64-бит (qword) |
32-бит (dword) |
16-бит (word) |
8-бит (старший)¹ | 8-бит (младший) |
|---|---|---|---|---|
rax |
eax |
ax |
ah |
al |
rbx |
ebx |
bx |
bh |
bl |
rcx |
ecx |
cx |
ch |
cl |
rdx |
edx |
dx |
dh |
dl |
rsi |
esi |
si |
— | sil² |
rdi |
edi |
di |
— | dil² |
rbp |
ebp |
bp |
— | bpl² |
rsp |
esp |
sp |
— | spl² |
r8–r15 |
r8d–r15d |
r8w–r15w |
— | r8b–r15b |
¹ Старшие байты — наследие старых архитектур. Адресуют биты 8-15.
² Младшие байты доступны только в 64-битном режиме.
Манипуляции с регистрами
| Задача | Инструкции | Пример | Описание |
|---|---|---|---|
| Расширение с нулями | movzx |
movzx rbx, ax |
Копирует ax в rbx, старшие биты = 0 |
| Расширение со знаком | movsx |
movsx rcx, al |
Копирует al в rcx, старшие биты = знаковый бит |
| Автоматическое обнуление | mov (для 32-бит) |
mov ebx, eax |
Копирует eax в ebx, обнуляет старшие 32 бита rbx |
| Знаковое расширение | cbw, cwd, cdq, cqo |
cqo |
rdx:rax ← знаковое расширение rax |
🔧 Практический пример: Объединение двух регистров
Задача: Собрать в регистре rcx 64-битное число из старших 32 бит ebx и младших 32 бит eax.
; Исходные данные:
mov ebx, 0xAAAAAAAA ; Старшая часть (rbx автоматически = 0x00000000_AAAAAAAA)
mov eax, 0xBBBBBBBB ; Младшая часть (rax автоматически = 0x00000000_BBBBBBBB)
; Шаг 1: Загрузить старшую часть в целевой регистр
mov rcx, rbx ; rcx = 0x00000000_AAAAAAAA
; Шаг 2: Сдвинуть старшую часть влево на 32 бита
shl rcx, 32 ; rcx = 0xAAAAAAAA_00000000
; Шаг 3: Объединить с помощью OR
or rcx, rax ; rcx = 0xAAAAAAAA_BBBBBBBB
📏 3. Адресация памяти и работа с массивами
📖 Подробнее: Полное руководство по массивам, индексации и циклам — в статье «Массивы в NASM: Адресация памяти, Циклы и Эффективность».
Объявление массивов
| Директива | Размер элемента | Назначение | Пример |
|---|---|---|---|
db |
1 байт | Символы, малые числа | my_bytes db 10, 'A', 255 |
dw |
2 байта | Короткие целые | my_words dw 1000, -1, 0 |
dd |
4 байта | Стандартные целые | my_dwords dd 123456, -100 |
dq |
8 байт | Длинные целые, указатели | my_qwords dq 0x1000, 999 |
resb, resw... |
1, 2… байта | Резервирование в .bss |
buffer resb 1024 |
💡 Как вычислить длину массива?
section .data
my_array dd 10, 20, 30, 40, 50
my_array_len equ ($ - my_array) / 4
; my_array_len теперь равно константе 5
Формула адресации
[base + index * scale + offset]Где:
base— базовый регистр (любой GPR)index— индексный регистр (любой GPR, кромеrsp)scale— множитель (только 1, 2, 4 или 8)offset— константное смещение или адрес метки
| Синтаксис | Что вычисляется | Применение |
|---|---|---|
[rbx] |
rbx + 0 |
Простая разыменовка |
[rbx + 8] |
rbx + 8 |
Доступ к полю структуры |
[array + rcx * 4] |
адрес_array + rcx * 4 |
Элемент массива int |
[rbx + rcx * 8 + 16] |
rbx + (rcx * 8) + 16 |
Сложная структура |
💡 Почему scale только 1,2,4,8? Эти значения соответствуют размерам базовых типов данных:
byte(1),word(2),dword(4),qword(8).
1. Забыли про масштабирование
; arr dw 100, 200 (2 байта/элемент)
mov rcx, 1
mov ax, [arr + rcx] ; ОШИБКА: неверно!
2. Выход за границы массива
; arr db 1, 2, 3 (длина 3)
mov al, [arr + 5] ; ОШИБКА: SegFault
3. Несоответствие размера регистра и данных
; arr dw 5000
mov al, [arr] ; ОШИБКА: потеря данных
1. Используйте scale
; arr dw 100, 200
mov rcx, 1
mov ax, [arr + rcx*2] ; ✅ ПРАВИЛЬНО
Мнемоника:
db→*1, dw→*2, dd→*4, dq→*8
2. Проверяйте границы
; arr db 1, 2, 3
cmp rcx, 3
jge .error
mov al, [arr + rcx] ; ✅ ПРАВИЛЬНО
3. Выбирайте правильный регистр
; arr dw 5000
mov ax, [arr] ; ✅ ПРАВИЛЬНО
🧮 Детальная визуализация двумерного массива
Логическое представление:
arr[0][0] arr[0][1] arr[0][2] arr[0][3]
arr[1][0] arr[1][1] arr[1][2] arr[1][3]
arr[2][0] arr[2][1] arr[2][2] arr[2][3]Физическое расположение в памяти (row-major):
+--------+--------+--------+--------+--------+--------+--------+--------+
| [0][0] | [0][1] | [0][2] | [0][3] | [1][0] | [1][1] | [1][2] | [1][3] |
+--------+--------+--------+--------+--------+--------+--------+--------+Формула для arr[i][j]:
- Линейный индекс =
i * (столбцы) + j - Байтовое смещение =
линейный_индекс * (размер_элемента)
Пример для arr[1][2]:
mov eax, [arr + 1*4*4 + 2*4] ; 1 строка * 4 столбца * 4 байта + 2*4
Шаблон: Обход массива в цикле
#include <stdio.h>
#define LEN 5
int main() {
int arr[LEN] = {10, 20, 30, 40, 50};
int sum = 0;
for (int i = 0; i < LEN; i++) {
sum += arr[i];
}
printf("%d\n", sum); // 150
return 0;
}section .data
arr dd 10, 20, 30, 40, 50
LEN equ ($ - arr) / 4
section .text
global _start
_start:
xor eax, eax ; sum = 0
xor rcx, rcx ; i = 0
.loop:
cmp rcx, LEN
jge .done
add eax, [arr + rcx*4]
inc rcx
jmp .loop
.done:
mov rdi, rax ; Результат в rdi (150)
mov rax, 60
syscall⚙️ 4. Фундаментальные инструкции
Перемещение данных и арифметика
| Инструкция | Назначение | Пример | Нюансы |
|---|---|---|---|
mov |
Копирование | mov rax, rbx |
Нельзя [память] → [память] |
lea |
Адрес/арифметика | lea rsi, [msg] |
Не читает память! Не портит флаги |
add/sub |
Сложение/Вычитание | add rax, 10 |
Изменяют CF, OF |
adc/sbb |
С переносом/заёмом | adc rbx, rcx |
Для 128-битной арифметики |
inc/dec |
Инкремент/Декремент | inc rax |
⚠️ НЕ изменяют CF! |
neg |
Изменение знака | neg rax |
⚠️ Не работает для минимального отрицательного |
shr/sar |
Сдвиги вправо | sar rax, 2 |
shr — логический, sar — арифметический |
shl/sal |
Сдвиги влево | shl rax, 3 |
Быстрое умножение на степени 2 |
and/or/xor |
Логика | xor rax, rax |
xor reg, reg — быстрое обнуление |
not |
Побитовое НЕ | not rax |
Инвертирует все биты |
⚡ Практические примеры арифметических операций
Сложение 128-битных чисел:
add rax, rcx ; Складываем младшие части
adc rdx, rbx ; Складываем старшие части + CF
Умножение на константу без MUL:
lea rax, [rax + rax * 4] ; rax * 5
shl rax, 1 ; rax * 2 (итого: * 10)
Деление на степень двойки:
shr rax, 3 ; Беззнаковое деление на 8
sar rax, 3 ; Знаковое деление на 8
Умножение (mul, imul)
💡 Ключевое правило: Результат в два раза больше множителей.
| Команда | Множитель 1 | × | Множитель 2 | = | Результат |
|---|---|---|---|---|---|
mul/imul byte src |
al (8 бит) |
× | src |
= | ah:al (16 бит) |
mul/imul word src |
ax (16 бит) |
× | src |
= | dx:ax (32 бит) |
mul/imul dword src |
eax (32 бит) |
× | src |
= | edx:eax (64 бит) |
mul/imul qword src |
rax (64 бит) |
× | src |
= | rdx:rax (128 бит) |
mul— для беззнаковых,imul— для знаковых
Деление (div, idiv)
💡 Ключевое правило: Делимое в два раза больше делителя.
| Команда | Делимое | ÷ | Делитель | = | Результат | Подготовка |
|---|---|---|---|---|---|---|
div/idiv byte src |
ax (16 бит) |
÷ | src (8 бит) |
= | al, ah |
cbw (знак.) / movzx (беззн.) |
div/idiv word src |
dx:ax |
÷ | src |
= | ax, dx |
cwd / xor dx, dx |
div/idiv dword src |
edx:eax |
÷ | src |
= | eax, edx |
cdq / xor edx, edx |
div/idiv qword src |
rdx:rax |
÷ | src |
= | rax, rdx |
cqo / xor rdx, rdx |
ПРОБЛЕМА: Забыли обнулить/расширить старшую часть
mov rax, 100
mov rbx, 3
div rbx ; ОШИБКА! rdx содержит мусор
ПОСЛЕДСТВИЯ
- Программа упадет с ошибкой деления
- Неверный результат
- Непредсказуемое поведение
РЕШЕНИЕ: Явное обнуление/расширение
; Беззнаковое деление:
mov rax, 100
xor rdx, rdx ; Обнуляем старшую часть
mov rbx, 3
div rbx ; rax = 33, rdx = 1
; Знаковое деление:
mov rax, -100
cqo ; Расширяем знак в rdx
mov rbx, 3
idiv rbx ; rax = -33, rdx = -1
ПРАВИЛЬНО
- Корректный результат
- Защита от падения программы
- Предсказуемое поведение
📚 5. Работа со стеком: системный и ручной
📖 Подробнее: Разбор стекового кадра и ABI — в статье «Связь C и NASM: ABI и Оптимизация».
Инструкции стека
| Инструкция | Действие | Как меняется rsp |
|---|---|---|
push val |
Положить val в стек |
Уменьшается на 8 |
pop reg |
Извлечь значение | Увеличивается на 8 |
call lbl |
Положить адрес возврата | Уменьшается на 8 |
ret |
Забрать адрес возврата | Увеличивается на 8 |
sub rsp, N |
Выделить место | Уменьшается на N |
add rsp, N |
Освободить место | Увеличивается на N |
🎯 Визуализация работы стека
Высокие адреса
│
├─────────────────┐
│ Пусто │
├─────────────────┤ ← RSP после инициализации
│ │
│ (Стек растет │
│ сюда вниз) │
│ │
↓
Низкие адреса
После push rax (где rax = 0x1234):
├─────────────────┐
│ 0x1234 │ ← RSP (уменьшился на 8)
├─────────────────┤
После push rbx (где rbx = 0x5678):
├─────────────────┐
│ 0x1234 │
├─────────────────┤
│ 0x5678 │ ← RSP (уменьшился ещё на 8)
├─────────────────┤
После pop rcx:
├─────────────────┐
│ 0x1234 │ ← RSP (увеличился на 8)
├─────────────────┤
│ 0x5678 (мусор) │ rcx = 0x5678
├─────────────────┤Ключевые моменты:
- Стек растет вниз (к меньшим адресам)
pushуменьшает RSPpopувеличивает RSP- Данные остаются до перезаписи
Red Zone (Оптимизация стека)
Согласно System V ABI (Linux/macOS), область памяти размером 128 байт ниже текущего значения rsp зарезервирована для выполняемой функции.
Суть: Позволяет хранить временные данные без изменения указателя стека (sub rsp / add rsp).
Диапазон: [rsp - 8] … [rsp - 128].
✅ Когда применять: Только в листовых функциях (leaf functions) — функциях, которые не вызывают другие подпрограммы.
Пример:
my_func:
mov [rsp-8], rax ; Сохранение во временную область
mov [rsp-16], rbx ; Выделение стека (sub rsp) не требуется
...
ret
⚠️ ВАЖНОЕ ОГРАНИЧЕНИЕ:
Любая инструкция call или push (а также обработчики сигналов поверх них) может перезаписать данные в Red Zone.
mov [rsp-8], rax ; Записали данные
call printf ; ОШИБКА: вызов функции уничтожит содержимое [rsp-8]
🚦 6. Условные переходы
Инструкции сравнения
| Инструкция | Действие | Флаги |
|---|---|---|
cmp a, b |
Вычисляет a - b |
Устанавливает ZF, CF, SF, OF |
test a, b |
Вычисляет a & b |
Устанавливает ZF, SF |
Условные переходы (jcc)
| Сравнение | Для ЗНАКОВЫХ | Для БЕЗЗНАКОВЫХ |
|---|---|---|
a == b |
je / jz |
je / jz |
a != b |
jne / jnz |
jne / jnz |
a > b |
jg |
ja |
a >= b |
jge |
jae |
a < b |
jl |
jb |
a <= b |
jle |
jbe |
💡 Мнемоника: Above/Below — для беззнаковых. Greater/Less — для знаковых.
⚡ Дополнительные условные переходы
| Инструкция | Проверяет | Описание |
|---|---|---|
jz / je |
ZF = 1 |
Zero / Equal |
jnz / jne |
ZF = 0 |
Not Zero / Not Equal |
jc |
CF = 1 |
Carry (перенос) |
jnc |
CF = 0 |
Not Carry |
jo |
OF = 1 |
Overflow (переполнение) |
jno |
OF = 0 |
Not Overflow |
js |
SF = 1 |
Sign (отрицательный) |
jns |
SF = 0 |
Not Sign |
jmp label |
— | Безусловный переход |
loop label |
rcx ≠ 0 |
Декремент rcx и переход |
setcc reg8 |
— | Установить байт в 0 или 1 |
ПРОБЛЕМА: Неправильная инструкция перехода
mov al, 255 ; 255 беззнаковое
cmp al, 1
jl less_label ; ОШИБКА! jl для знаковых
; 0xFF как знаковое = -1
; -1 < 1, переход НЕПРАВИЛЬНО выполнится!
ЛОГИКА НАРУШЕНА
Для беззнаковых: 255 > 1 (переход НЕ должен)
Но процессор видит: -1 < 1 (переход выполняется)
РЕШЕНИЕ: Правильная инструкция перехода
mov al, 255 ; 255 беззнаковое
cmp al, 1
jb less_label ; ✅ ПРАВИЛЬНО! jb для беззнаковых
; 255 > 1, переход НЕ выполнится
; Знаковое сравнение:
mov al, -1 ; -1
cmp al, 1
jl less_label ; ✅ ПРАВИЛЬНО! jl для знаковых
; -1 < 1, переход выполнится
ПРАВИЛО
- Используйте
ja/jb/jae/jbeдля беззнаковых - Используйте
jg/jl/jge/jleдля знаковых
🎯 Примеры использования условных переходов
Проверка диапазона:
cmp rax, 100
ja out_of_range ; Если rax > 100 (беззнаковое)
jmp in_range
Цикл со счетчиком:
mov rcx, 10 ; Счетчик
loop_start:
; Тело цикла
dec rcx
jnz loop_start
Поиск символа в строке:
lea rsi, [str]
mov al, 'o'
search_loop:
mov bl, [rsi]
test bl, bl
jz not_found
cmp bl, al
je found
inc rsi
jmp search_loop
📜 7. Мощные строковые инструкции
| Инструкция | Действие | Используемые регистры |
|---|---|---|
movsb/w/d/q |
Копирует из [rsi] в [rdi] |
rsi, rdi |
cmpsb/w/d/q |
Сравнивает [rsi] и [rdi] |
rsi, rdi |
scasb/w/d/q |
Сравнивает аккумулятор с [rdi] |
rax, rdi |
stosb/w/d/q |
Записывает аккумулятор в [rdi] |
rax, rdi |
lodsb/w/d/q |
Загружает из [rsi] в аккумулятор |
rax, rsi |
| Префикс | Действие | Используемые регистры |
|---|---|---|
cld / std |
Направление: вперёд / назад | rflags (DF) |
rep |
Повторить rcx раз |
rcx |
repe / repz |
Повторять, пока равно | rcx, rflags (ZF) |
repne / repnz |
Повторять, пока не равно | rcx, rflags (ZF) |
📚 Подробное объяснение строковых инструкций
Как работают строковые инструкции:
- Выполняют операцию (копирование, сравнение, поиск)
- Автоматически изменяют указатели:
- Если
DF = 0(послеcld): указатели увеличиваются (вперёд) - Если
DF = 1(послеstd): указатели уменьшаются (назад)
- Если
- С префиксом
rep*: повторяют операциюrcxраз
section .data
source db "Hello, World!", 0
section .bss
dest resb 20
section .text
cld ; Направление: вперёд
lea rsi, [source]
lea rdi, [dest]
mov rcx, 13
rep movsb ; Повторить 13 разsection .bss
buffer resb 100
section .text
cld
lea rdi, [buffer]
mov al, 0xFF ; Значение для заполнения
mov rcx, 100
rep stosb ; Повторить 100 разsection .data
str1 db "Hello", 0
str2 db "Hello", 0
section .text
cld
lea rsi, [str1]
lea rdi, [str2]
mov rcx, 5
repe cmpsb ; Повторять, пока равны
je strings_equalsection .data
str db "Hello, World!", 0
section .text
cld
lea rdi, [str]
mov al, 'W' ; Ищем 'W'
mov rcx, 13
repne scasb ; Повторять, пока не равно
je found_char⚠️ Важные нюансы строковых инструкций
1. Всегда устанавливайте направление:
cld ; DF=0, обработка вперёд (99% случаев)
std ; DF=1, обработка назад (редко)
2. После операции указатели изменены:
rep movsb
; Теперь:
; rsi = source + 10
; rdi = dest + 10
; rcx = 0
3. С repne/repe указатель сдвинут ещё на один:
repne scasb
je found
dec rdi ; Вернуться на найденный символ
🧮 8. Математика с плавающей точкой (FPU)
📖 Подробнее: Полное руководство по FPU — в статье «FPU в NASM: Вещественные числа, Синусы и Точность».
Стековая архитектура FPU
FPU имеет 8 регистров (ST(0)–ST(7)), работающих как стек. ST(0) — вершина.
Основные инструкции
| Инструкция | Назначение | Пример |
|---|---|---|
finit |
Инициализация FPU | finit |
fld / fild |
Загрузить float / int | fld qword [pi] |
fstp / fistp |
Сохранить и выгрузить | fstp qword [result] |
faddp |
Сложить, убрать операнды | faddp |
fsubp |
Вычесть | fsubp |
fmulp |
Умножить | fmulp |
fdivp |
Разделить | fdivp |
fsqrt |
Квадратный корень | fsqrt |
fsin/fcos |
Синус/косинус (радианы) | fsin |
fldpi |
Загрузить π | fldpi |
💡 Совет: Всегда используйте инструкции с суффиксом
pдля очистки стека.
fldpi ; ST(0) = π
fld [radius] ; ST(0) = 5.0, ST(1) = π
fmul st0, st0 ; ST(0) = 25.0, ST(1) = π
fmul ; ОШИБКА! ST(0) и ST(1) остаются
; Стек "засоряется"
finit
fldpi ; ST(0) = π
fld [radius] ; ST(0) = 5.0, ST(1) = π
fmul st0, st0 ; ST(0) = 25.0, ST(1) = π
fmulp ; ✅ Умножить и убрать
fstp [area] ; Сохранить, стек пуст
✨ 9. Практические идиомы и трюки
| Задача | Инструкции | Примечание |
|---|---|---|
| Обнулить регистр | xor rax, rax |
Быстрее, чем mov rax, 0 |
| Проверить на ноль | test rdi, rdi |
Не меняет регистр |
| Получить адрес | lea rsi, [my_var] |
Не читает память |
| Быстрая арифметика | lea rbx, [rax*4+8] |
rbx = rax*4+8 за одну инструкцию |
| Условное присваивание | cmovcc rax, rbx |
Без ветвлений, быстрее |
cmp rax, 0
jge skip_assign
mov rax, 100
skip_assign:
; Продолжение
НЕДОСТАТКИ
- Промах предсказателя
- Pipeline stall
- Медленнее на критических путях
mov rbx, 100
cmp rax, 0
cmovl rax, rbx ; if (rax < 0) rax = rbx
; Продолжение (без перехода)
ПРЕИМУЩЕСТВА
- Нет промахов предсказателя
- Pipeline не останавливается
- На 2-3 цикла быстрее
🖥️ 10. Системные вызовы (Syscalls)
📖 Подробнее: Полное руководство по файлам и ошибкам — в статье «Работа с файлами в NASM».
Основные syscalls
| Имя | RAX | RDI | RSI | RDX | Возврат (RAX) |
|---|---|---|---|---|---|
| read | 0 |
fd |
buffer |
count |
Кол-во байт / 0 (EOF) / ошибка |
| write | 1 |
fd |
buffer |
count |
Кол-во записанных байт |
| open | 2 |
filename |
flags |
mode |
fd или ошибка |
| close | 3 |
fd |
— | — | 0 или ошибка |
| lseek | 8 |
fd |
offset |
whence |
Новое смещение |
| exit | 60 |
exit_code |
— | — | Не возвращается |
Флаги open (регистр RSI)
| Задача | Флаги | Значение |
|---|---|---|
| Чтение | O_RDONLY |
0 |
| Запись (new) | O_WRONLY | O_CREAT | O_TRUNC |
0o1101 |
| Дозапись | O_WRONLY | O_CREAT | O_APPEND |
0o2101 |
| Lock-файл | O_WRONLY | O_CREAT | O_EXCL |
0o301 |
⚠️ Важно: При
O_CREATукажите права доступа вRDX(например,0o644).
📝 Пример: Запись в файл с проверкой ошибок
section .data
filename db "output.txt", 0
msg db "Hello, File!", 10
msg_len equ $ - msg
section .text
; open() с проверкой ошибок
mov rax, 2
lea rdi, [filename]
mov rsi, 0o1101 ; O_WRONLY|O_CREAT|O_TRUNC
mov rdx, 0o644
syscall
test rax, rax
js error ; Если отрицательный = ошибка
mov r12, rax ; Сохраняем fd
; write()
mov rax, 1
mov rdi, r12
lea rsi, [msg]
mov rdx, msg_len
syscall
; close()
mov rax, 3
mov rdi, r12
syscall
mov rax, 60
xor rdi, rdi
syscall
error:
neg rax ; Положительное значение ошибки
mov rdi, rax ; Exit с кодом ошибки
mov rax, 60
syscall
📋 11. Аргументы командной строки (CLI)
📖 Подробнее: Полный разбор стека и парсинга — в статье «Аргументы командной строки в NASM».
Структура стека при запуске
| Смещение | Значение | Описание |
|---|---|---|
[rsp] |
argc | Кол-во аргументов (включая имя программы) |
[rsp+8] |
argv[0] | Имя программы |
[rsp+16] |
argv[1] | Первый аргумент пользователя |
... |
... |
Остальные аргументы |
[rsp+8*argc+8] |
NULL | Маркер конца массива |
🎯 Паттерн: Взять первый аргумент
_start:
pop rcx ; rcx = argc
cmp rcx, 2 ; Нужно минимум 2 (программа + 1 аргумент)
jl .usage_error
mov rdi, [rsp + 8] ; rdi = argv[1] (адрес первого аргумента)
; Теперь rdi можно сразу передавать в sys_open
.usage_error:
; Вывести ошибку и выйти
mov rax, 60
mov rdi, 1
syscall
🐛 Лайфхак для GDB
Чтобы увидеть аргументы в отладчике:
run arg1 arg2— запуск с параметрами.x/d $rsp— показатьargc.x/s *($rsp+16)— показать строкуargv[1](разыменовать указатель).
🧩 12. Связывание файлов (Linking)
📖 Пример из практики: Многофайловые проекты — в статье «Модули ввода-вывода».
Директивы видимости
| Директива | Где писать | Пример | Описание |
|---|---|---|---|
global |
В файле с реализацией | global my_func |
Экспорт для других файлов |
extern |
В файле с вызовом | extern my_func |
Импорт из другого файла |
Компиляция многофайлового проекта
# 1. Компилируем каждый модуль
nasm -f elf64 main.asm -o main.o
nasm -f elf64 lib.asm -o lib.o
# 2. Линкуем в один файл
ld main.o lib.o -o program
section .text
global add_numbers
add_numbers:
; rdi = первый аргумент
; rsi = второй аргумент
mov rax, rdi
add rax, rsi
retsection .text
extern add_numbers
global _start
_start:
mov rdi, 10
mov rsi, 20
call add_numbers
; rax = 30
mov rdi, rax
mov rax, 60
syscall🛠️ 13. Инструменты и процесс сборки
📖 Подробнее: Настройка GDB и отладка — в статье «Отладка ASM в VS Code».
| Команда | Назначение | Пример |
|---|---|---|
nasm |
Ассемблер (.asm → .o) |
nasm -f elf64 -g -F dwarf program.asm -o program.o |
ld |
Компоновщик (.o → исполняемый) |
ld program.o -o program |
gdb |
Отладчик | gdb -tui ./program |
objdump |
Дизассемблер | objdump -d -M intel program |
strace |
Трассировка syscalls | strace ./program |
📦 Пример Makefile для ассемблерного проекта
.PHONY: all build run debug clean
all: build
prepare_dirs:
@mkdir -p build bin
build: prepare_dirs
nasm -f elf64 -g -F dwarf program.asm -o build/program.o
ld build/program.o -o bin/program
run: build
@./bin/program
@echo "Exit code: $$?"
debug: build
gdb -tui bin/program
disasm: build
objdump -d -M intel bin/program | less
clean:
@rm -rf build bin
Использование:
make # Собрать
make run # Собрать и запустить
make debug # Запустить в отладчике
make disasm # Дизассемблировать
🐛 Базовые команды GDB
break _start # Точка останова
run # Запустить программу
stepi (si) # Одна инструкция
nexti (ni) # Одна инструкция (не входя в call)
info registers (i r) # Все регистры
print $rax # Значение регистра
x/10x $rsp # 10 значений из стека (hex)
disassemble # Дизассемблировать
layout asm # Режим ассемблера
layout regs # Показать регистры
quit # Выход
🎓 14. Практические примеры
Пример 1: Вычисление длины строки (my_strlen)
section .text
global my_strlen
my_strlen:
mov rax, rdi ; Сохраняем начало
.loop:
cmp byte [rdi], 0
je .end
inc rdi
jmp .loop
.end:
sub rdi, rax ; rdi (конец) - rax (начало)
mov rax, rdi
ret
Пример 2: Обход массива и вычисление суммы
section .data
arr dd 10, 20, 30, 40, 50
LEN equ ($ - arr) / 4
section .text
global _start
_start:
xor eax, eax ; sum = 0
xor rcx, rcx ; i = 0
.loop:
cmp rcx, LEN
jge .done
add eax, [arr + rcx*4]
inc rcx
jmp .loop
.done:
mov rdi, rax ; Результат в rdi
mov rax, 60
syscall
Пример 3: Работа с файлами
section .data
filename db "output.txt", 0
content db "Hello!", 10
content_len equ $ - content
section .text
global _start
_start:
; open()
mov rax, 2
lea rdi, [filename]
mov rsi, 0o1101
mov rdx, 0o644
syscall
mov r12, rax
; write()
mov rax, 1
mov rdi, r12
lea rsi, [content]
mov rdx, content_len
syscall
; close()
mov rax, 3
mov rdi, r12
syscall
; exit()
mov rax, 60
xor rdi, rdi
syscall
🎯 Заключение
Эта памятка охватывает основные концепции x86-64 ассемблера. Используйте её как справочник при разработке и отладке ассемблерных программ.