Nikita Mandrykin

Практикуюсь в программировании. C • Python • Assembly • Linux

Назад

Шпаргалка NASM x86-64: Регистры, Инструкции, Syscalls (Linux)

📚 Содержание

Полный справочник ключевых концепций 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

РЕШЕНИЕ

Используйте .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, R8R15 64 Целочисленная арифметика, адреса, счетчики
Указатель инструкций RIP 64 Адрес следующей исполняемой инструкции. Не изменяется напрямую
Регистр флагов RFLAGS 64 Состояние процессора: ZF, CF, SF, OF и др.
Векторные/SIMD регистры XMM0XMM15 (расширения 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²
r8r15 r8dr15d r8wr15w r8br15b

¹ Старшие байты — наследие старых архитектур. Адресуют биты 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

Шаблон: Обход массива в цикле

Реализация на C
#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;
}
Реализация на NASM
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 уменьшает RSP
  • pop увеличивает 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)
📚 Подробное объяснение строковых инструкций

Как работают строковые инструкции:

  1. Выполняют операцию (копирование, сравнение, поиск)
  2. Автоматически изменяют указатели:
    • Если DF = 0 (после cld): указатели увеличиваются (вперёд)
    • Если DF = 1 (после std): указатели уменьшаются (назад)
  3. С префиксом rep*: повторяют операцию rcx раз
Копирование блока памяти (memcpy)
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 раз
Заполнение блока памяти (memset)
section .bss
    buffer resb 100

section .text
    cld
    lea rdi, [buffer]
    mov al, 0xFF            ; Значение для заполнения
    mov rcx, 100
    rep stosb               ; Повторить 100 раз
Сравнение строк (strcmp)
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_equal
Поиск символа (strchr)
section .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) остаются
                  ; Стек "засоряется"
✅ Правильный код с `p`
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

Чтобы увидеть аргументы в отладчике:

  1. run arg1 arg2 — запуск с параметрами.
  2. x/d $rsp — показать argc.
  3. 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
lib.asm (Реализация)
section .text
    global add_numbers

add_numbers:
    ; rdi = первый аргумент
    ; rsi = второй аргумент
    mov rax, rdi
    add rax, rsi
    ret
main.asm (Использование)
section .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 ассемблера. Используйте её как справочник при разработке и отладке ассемблерных программ.

💜

Полезный материал?

Если статья помогла разобраться в теме, вы можете поддержать автора любой комфортной суммой.

Поддержать через CloudTips