Nikita Mandrykin

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

Назад

Настройка NASM x86-64 и VS Code: Гайд для Linux и Windows

🚀 1. Быстрое Решение за 3 Шага

Минимальная конфигурация для немедленного начала работы (10-15 минут).

Шаг 1: Выбор платформы

💻 Windows

Используется:

WSL (Windows Subsystem for Linux) — полноценное Linux-окружение внутри Windows.

Требования:

  • Windows 10 версии 2004+ (сборка 19041+) или Windows 11
  • Включенная виртуализация в BIOS

Установка WSL:

  1. Откройте PowerShell от имени администратора
  2. Выполните команды:
    wsl --install
    wsl --set-default-version 2
    
  3. Перезагрузите компьютер
  4. После перезагрузки Ubuntu запустится автоматически
  5. Создайте пользователя и пароль

ℹ️ После настройки WSL все дальнейшие команды выполняются в Ubuntu.

🐧 Linux

Если у вас уже установлен Linux, пропустите установку WSL.

Дистрибутивы в руководстве:

  • Ubuntu/Debian
  • Fedora
  • Arch Linux

ℹ️ Команды приведены для этих дистрибутивов. Для других используйте соответствующий пакетный менеджер — инструменты те же.

Проверка версии Windows и включение виртуализации

Проверка версии Windows:

  1. Нажмите Win + R
  2. Введите winver и нажмите Enter
  3. Убедитесь: версия 2004+ (сборка 19041+) или Windows 11
Проверка версии Windows через команду winver

Включение виртуализации:

  1. Откройте Диспетчер задач (Ctrl + Shift + Esc)
  2. Вкладка ПроизводительностьЦП
  3. Проверьте параметр Виртуализация
  4. Если Выключено:
    • Перезагрузите компьютер
    • Войдите в BIOS (F2, Del, F10 или F12)
    • Найдите Intel VT-x / AMD-V / SVM
    • Включите и сохраните настройки

Шаг 2: Установка инструментов

Выполните команды в зависимости от вашего дистрибутива:

Ubuntu/Debian (включая WSL):

sudo apt update && sudo apt upgrade -y
sudo apt install build-essential gdb nasm -y

Fedora:

sudo dnf update -y
sudo dnf groupinstall "Development Tools" -y
sudo dnf install nasm gdb -y

Arch Linux:

sudo pacman -Syu
sudo pacman -S base-devel nasm gdb

Что устанавливается:

Пакет Назначение
build-essential / Development Tools / base-devel GCC, G++, Make и библиотеки для компиляции
nasm Ассемблер для преобразования .asm файлов в объектные файлы (.o)
gdb Отладчик GNU для пошаговой отладки

Шаг 3: Быстрая проверка

Создайте тестовый проект:

# Создайте проект
mkdir ~/hello_asm && cd ~/hello_asm
mkdir src

# Создайте hello.asm
cat > src/hello.asm << 'EOF'
section .data
    msg db "Hello, World!", 10
    len equ $ - msg

section .text
    global _start

_start:
    mov rax, 1
    mov rdi, 1
    mov rsi, msg
    mov rdx, len
    syscall

    mov rax, 60
    xor rdi, rdi
    syscall
EOF

# Соберите и запустите
nasm -f elf64 -g -F dwarf src/hello.asm -o hello.o
ld hello.o -o hello
./hello

Ожидаемый результат:

Hello, World!

Если всё работает — окружение готово!

🐛 Не работает или падает ошибка?

Если программа не собирается или выдаёт Segmentation fault, изучите руководство «Отладка ASM в VS Code: Настройка GDB» и список «Топ ошибок в NASM: Segfault и неверные расчёты».

🤔 2. Зачем это было нужно?

Проблема: Почему нельзя просто установить NASM?

NASM — это только ассемблер. Он превращает .asm файлы в объектные файлы .o, но не может создать исполняемый файл самостоятельно.

Что происходит при сборке:

hello.asm  →  [NASM]  →  hello.o  →  [LD]  →  hello (исполняемый)

Полный набор инструментов:

Инструмент Роль Зачем нужен
NASM Ассемблер Преобразует .asm в машинный код (.o)
LD Компоновщик Создаёт исполняемый файл из .o
GCC Компилятор C Нужен для проектов C + Assembly
GDB Отладчик Пошаговое выполнение, просмотр регистров
VS Code Редактор Удобство редактирования и сборки

Почему WSL для Windows?

Системные вызовы (syscalls) отличаются:

Linux (работает в WSL)
; Вывод текста в Linux
mov rax, 1      ; syscall write
mov rdi, 1      ; stdout
mov rsi, msg
mov rdx, len
syscall
Windows (не работает в WSL)
; Вывод текста в Windows
mov rcx, STD_OUTPUT_HANDLE
call GetStdHandle
mov rcx, rax
mov rdx, msg
; ... (WinAPI, не syscalls)

Преимущества WSL:

  • Работает из коробки — не нужно настраивать MinGW, Cygwin или бороться с путями Windows
  • Полная совместимость с Linux-инструментами без костылей
  • Избавляет от проблем компиляции C/Assembly кода в Windows
  • Один и тот же код работает в WSL и чистом Linux без изменений
  • Не требует перезагрузки или виртуальной машины

Почему два типа проектов?

C + Assembly:

  • Баланс между производительностью и удобством
  • Доступна стандартная библиотека C (printf, malloc)
  • Графическая отладка в VS Code
  • Типичное применение: оптимизация критичных участков

Assembly-only:

  • Полный контроль над каждой инструкцией
  • Минимальный размер исполняемого файла
  • Прямая работа с системными вызовами
  • Типичное применение: изучение архитектуры, системное программирование

🔀 3. Какой тип проекта выбрать?

Таблица Сравнения

Аспект C + Assembly Assembly-only
Когда использовать Оптимизация узких мест в C-программах Изучение архитектуры x86-64
Стандартная библиотека ✅ Доступна (printf, malloc, etc.) ❌ Нет (только syscalls)
Отладка VS Code ✅ Графический интерфейс (F5, breakpoints) ❌ Только GDB в терминале
Точка входа main _start
Линковка Через gcc Через ld
Сложность Средняя Высокая
Размер исполняемого файла Больше (включает libc) Минимальный

Рекомендации по выбору

🎯 Выберите C + Assembly если:

Вы хотите:

  • Оптимизировать критичные участки C-кода
  • Использовать SIMD-инструкции (SSE, AVX)
  • Иметь привычную отладку с breakpoints
  • Работать с библиотеками C

Примеры задач:

  • Математические вычисления с SIMD
  • Криптографические функции
  • Обработка изображений/видео
  • Портирование legacy ассемблерного кода
🎓 Выберите Assembly-only если:

Вы хотите:

  • Глубоко понять работу процессора
  • Научиться работать с syscalls напрямую
  • Написать минимальный исполняемый файл
  • Изучить calling conventions

Примеры задач:

  • Обучающие проекты по архитектуре
  • Системное программирование
  • Embedded-разработка
  • Написание загрузчиков

Совет для начинающих: Начните с C + Assembly. Это даст вам:

  • Привычные инструменты отладки
  • Возможность комбинировать высокоуровневый и низкоуровневый код
  • Плавный переход к чистому ассемблеру

🛠️ 4. Настройка окружения: Пошаговая реализация

Опциональная настройка консоли

Для удобной работы в терминале можно установить Oh My Zsh с плагинами.

Что такое Oh My Zsh и зачем он нужен?

Oh My Zsh — фреймворк для управления конфигурацией Zsh:

  • Сотни готовых плагинов и тем
  • Автодополнение команд
  • Подсветка синтаксиса
  • Интеграция с Git
  • Удобные алиасы

Основные возможности:

  • Плагины — расширения функциональности
  • Темы — изменение внешнего вида prompt
  • Алиасы — короткие команды (gst вместо git status)
  • Автообновление — фреймворк обновляется автоматически

1. Установка дополнительных пакетов

sudo apt install vim-gtk3 zsh -y  # Ubuntu/Debian
sudo dnf install vim-enhanced zsh -y  # Fedora
sudo pacman -S gvim zsh  # Arch Linux
Пакет Описание
vim-gtk3 / vim-enhanced / gvim Vim с поддержкой буфера обмена
zsh Альтернативная оболочка с расширенными возможностями

2. Установка Oh My Zsh

sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"

После установки Zsh станет оболочкой по умолчанию, и появится конфигурационный файл ~/.zshrc.

3. Установка плагинов

# zsh-syntax-highlighting: подсветка корректных/некорректных команд
git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting

# zsh-autosuggestions: автоподсказки из истории команд
git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions

4. Установка темы Powerlevel10k

Что даёт Powerlevel10k?

Powerlevel10k — современная и быстрая тема для Zsh:

  • Показывает текущую директорию, ветку Git, статус команд
  • Настраиваемый внешний вид
  • Отображает статус виртуальных окружений, время выполнения
  • Значительно быстрее стандартных тем
git clone --depth=1 https://github.com/romkatv/powerlevel10k.git "${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/powerlevel10k"

5. Настройка конфигурации

Откройте файл конфигурации:

nano ~/.zshrc

Найдите строку с ZSH_THEME и измените:

ZSH_THEME="powerlevel10k/powerlevel10k"

Найдите строку с plugins и замените:

plugins=(
    git
    zsh-autosuggestions
    zsh-syntax-highlighting
    history
)

Сохраните: Ctrl + O, Enter, затем выйдите: Ctrl + X.

6. Применение изменений

source ~/.zshrc

Запустится мастер настройки Powerlevel10k. Следуйте инструкциям на экране.

Для повторной настройки темы:

p10k configure

7. Практические примеры использования плагинов

zsh-autosuggestions:

  • Подсказки появляются из истории ваших команд
  • Если ранее вводили cd ~/asm_project, то при вводе cd ~/asm_pr появится серая подсказка
  • Нажмите (стрелка вправо) для принятия всей подсказки
  • Или Ctrl+→ для принятия одного слова
  • Продолжайте печатать для уточнения

zsh-syntax-highlighting:

  • Зелёный цвет: ls -la (команда существует и корректна)
  • Красный цвет: lss -la (опечатка, команда не найдена)
  • Желтый цвет: строки в кавычках и пути к файлам
  • Синий цвет: параметры и флаги

history plugin:

  • Ctrl+R: интерактивный поиск в истории команд
  • !!: повторить последнюю команду
  • !make: выполнить последнюю команду, начинающуюся с make
  • !$: последний аргумент предыдущей команды

git plugin (встроенный):

  • gst вместо git status
  • ga . вместо git add .
  • gc -m "message" вместо git commit -m "message"
  • gp вместо git push
  • gl вместо git pull

Настройка Git

Настройте Git для создания коммитов с вашим именем и email.

🌍 Глобальные настройки

Применяются ко всем репозиториям на компьютере

git config --global user.name "Ваше Имя"
git config --global user.email "youremail@example.com"
git config --global core.editor "nano"

Параметры:

  • user.name — имя в коммитах
  • user.email — email в коммитах
  • core.editor — редактор по умолчанию

Где хранятся: ~/.gitconfig

Проверить:

git config --list
📁 Локальные настройки

Переопределяют глобальные для конкретного проекта

Полезно для разделения рабочих и личных проектов.

# Перейдите в директорию проекта
cd ~/asm_project

# Установите локальные настройки
git config user.name "Другое Имя"
git config user.email "work@example.com"

Где хранятся: .git/config в директории проекта

Проверить:

git config --local --list  # Только локальные
git config user.name       # С учётом приоритета

Приоритет настроек:

  1. Локальные (.git/config) — наивысший
  2. Глобальные (~/.gitconfig)
  3. Системные (/etc/gitconfig) — наименьший

Настройка VS Code

Установка

Скачайте VS Code с официального сайта: code.visualstudio.com

Подключение к WSL (только для Windows)

После установки VS Code подключитесь к WSL:

  1. Запустите VS Code на Windows

  2. Нажмите зелёную кнопку “Open a Remote Window” (иконка ><) в левом нижнем углу

VS Code главное окно с зелёной кнопкой подключения
  1. Выберите “Connect to WSL”
Меню выбора удалённого подключения
  1. Дождитесь установки серверных компонентов (происходит автоматически при первом подключении)
Процесс загрузки VS Code Server
  1. После подключения в левом нижнем углу появится индикатор “WSL: Ubuntu”
VS Code успешно подключён к WSL

Установка расширений

⚠️ Для WSL: Устанавливайте расширения в WSL-режиме (кнопка “Install in WSL: Ubuntu”)

Расширение Описание
C/C++ Extension Pack (Microsoft) Поддержка отладки C/C++ и ассемблера, IntelliSense
Makefile Tools (Microsoft) Подсветка синтаксиса Makefile, интеграция со сборкой
x86 and x86_64 Assembly (13xforever) Подсветка синтаксиса .asm файлов

Как установить:

  1. Откройте Extensions (Ctrl + Shift + X)
  2. Найдите расширение
  3. Нажмите Install
  4. Для WSL: убедитесь в кнопке “Install in WSL: Ubuntu”

Проверка

Что должно быть:

  • ✅ VS Code установлен
  • ✅ Для Windows: индикатор “WSL: Ubuntu” виден
  • ✅ Все три расширения установлены
  • ✅ Встроенный терминал открывается корректно

Проекты C + Assembly

Концепция проектов C + Assembly

Этот подход используется, когда нужно:

  • Оптимизация критичных участков — переписать узкие места на ассемблере
  • Использование специфичных инструкций — SIMD, атомарные операции
  • Обучение — понимание взаимодействия высокоуровневого и низкоуровневого кода
  • Портирование legacy-кода — интеграция старых ассемблерных модулей

Типичные примеры:

  • Математические библиотеки с SIMD-оптимизациями
  • Криптографические функции
  • Обработка изображений и видео
  • Низкоуровневые системные утилиты

Особенности:

  • ✅ Доступна стандартная библиотека C
  • ✅ Простое управление памятью через malloc/free
  • ✅ Графическая отладка в VS Code
  • ✅ Точка входа — функция main

Структура проекта

c_asm_project/
├── src/
│   ├── main_8bit.c
│   ├── calc_8bit.asm
│   ├── main_16bit.c
│   └── calc_16bit.asm
├── build/              # Создаётся автоматически
├── bin/                # Создаётся автоматически
├── .vscode/
│   ├── launch.json
│   └── tasks.json
└── Makefile

Создание структуры:

mkdir ~/c_asm_project && cd ~/c_asm_project
mkdir src .vscode
touch Makefile .vscode/launch.json .vscode/tasks.json

Концептуальный пример

Полный рабочий проект: Калькулятор с 8-битными и 16-битными операциями

src/main_8bit.c:

#include <stdio.h>
#include <stdint.h>

// Объявляем функции из ассемблера
extern uint8_t add_8bit(uint8_t a, uint8_t b);
extern uint8_t sub_8bit(uint8_t a, uint8_t b);
extern uint16_t mul_8bit(uint8_t a, uint8_t b);

int main(void) {
    uint8_t a = 25;
    uint8_t b = 17;

    printf("8-bit Calculator\n");
    printf("================\n");
    printf("%u + %u = %u\n", a, b, add_8bit(a, b));
    printf("%u - %u = %u\n", a, b, sub_8bit(a, b));
    printf("%u * %u = %u\n", a, b, mul_8bit(a, b));

    return 0;
}

src/calc_8bit.asm:

section .text
    global add_8bit
    global sub_8bit
    global mul_8bit

; uint8_t add_8bit(uint8_t a, uint8_t b)
; Параметры: a в dil, b в sil
; Возврат: результат в al
add_8bit:
    mov al, dil      ; al = a
    add al, sil      ; al = a + b
    ret

; uint8_t sub_8bit(uint8_t a, uint8_t b)
sub_8bit:
    mov al, dil      ; al = a
    sub al, sil      ; al = a - b
    ret

; uint16_t mul_8bit(uint8_t a, uint8_t b)
; Параметры: a в dil, b в sil
; Возврат: результат в ax (полный 16-битный результат)
mul_8bit:
    mov al, dil      ; al = a
    mul sil          ; ax = al * sil
    ; Результат в ax (все 16 бит, без потерь)
    ret

src/main_16bit.c:

#include <stdio.h>
#include <stdint.h>

// Объявляем функции из ассемблера
extern uint16_t add_16bit(uint16_t a, uint16_t b);
extern uint16_t sub_16bit(uint16_t a, uint16_t b);
extern uint32_t mul_16bit(uint16_t a, uint16_t b);

int main(void) {
    uint16_t a = 1000;
    uint16_t b = 250;

    printf("16-bit Calculator\n");
    printf("=================\n");
    printf("%u + %u = %u\n", a, b, add_16bit(a, b));
    printf("%u - %u = %u\n", a, b, sub_16bit(a, b));
    printf("%u * %u = %u\n", a, b, mul_16bit(a, b));

    return 0;
}

src/calc_16bit.asm:

section .text
    global add_16bit
    global sub_16bit
    global mul_16bit

; uint16_t add_16bit(uint16_t a, uint16_t b)
; Параметры: a в di, b в si
; Возврат: результат в ax
add_16bit:
    mov ax, di       ; ax = a
    add ax, si       ; ax = a + b
    ret

; uint16_t sub_16bit(uint16_t a, uint16_t b)
sub_16bit:
    mov ax, di       ; ax = a
    sub ax, si       ; ax = a - b
    ret

; uint32_t mul_16bit(uint16_t a, uint16_t b)
; Параметры: a в di, b в si
; Возврат: результат в eax (полный 32-битный результат)
mul_16bit:
    mov ax, di       ; ax = a
    mul si           ; dx:ax = ax * si
    ; Объединяем dx:ax в eax для возврата полного результата
    shl edx, 16      ; Сдвигаем dx в старшие 16 бит
    movzx eax, ax    ; Обнуляем старшую часть eax, копируем ax
    or eax, edx      ; eax = (dx << 16) | ax (все 32 бита)
    ret

Makefile

Создайте Makefile:

.PHONY: all build_8bit build_16bit prepare_dirs clean

all: build_8bit build_16bit

prepare_dirs:
	@mkdir -p build bin

build_8bit: prepare_dirs
	gcc -c -g -ggdb -o build/main_8bit.o src/main_8bit.c
	nasm -f elf64 -g -F dwarf src/calc_8bit.asm -o build/calc_8bit.o
	gcc -o bin/8_bit build/main_8bit.o build/calc_8bit.o -m64 -no-pie -z noexecstack

build_16bit: prepare_dirs
	gcc -c -g -ggdb -o build/main_16bit.o src/main_16bit.c
	nasm -f elf64 -g -F dwarf src/calc_16bit.asm -o build/calc_16bit.o
	gcc -o bin/16_bit build/main_16bit.o build/calc_16bit.o -m64 -no-pie -z noexecstack

clean:
	@echo "Cleaning project..."
	rm -f build/*.o
	rm -f bin/8_bit bin/16_bit
	@echo "Done."

Использование:

make               # Собрать всё
make build_8bit    # Только 8-битную версию
make clean         # Очистить

Пояснение флагов компиляции

GCC (компиляция C)
gcc -c -g -ggdb -o build/main.o src/main.c

Флаги:

  • -c — только компиляция, без линковки
  • -g — отладочная информация
  • -ggdb — расширенная информация для GDB
  • -o file — имя выходного файла
NASM (ассемблирование)
nasm -f elf64 -g -F dwarf src/calc.asm -o build/calc.o

Флаги:

  • -f elf64 — формат ELF 64-bit для Linux
  • -g — отладочная информация
  • -F dwarf — формат DWARF для GDB
  • -o file — имя выходного файла

Линковка:

gcc -o bin/program build/main.o build/calc.o -m64 -no-pie -z noexecstack
Флаг Описание
-o file Имя исполняемого файла
-m64 Генерировать 64-битный код
-no-pie Отключить PIE (упрощает отладку)
-z noexecstack Неисполняемый стек (безопасность)
🛡️ Почему важен флаг -z noexecstack?

Этот флаг критически важен для безопасности современных программ.

Суть проблемы: По умолчанию в старых системах стек считался «исполняемым». Это означало, что злоумышленник мог записать вредоносный код (шелл-код) в переменную на стеке (через переполнение буфера), а затем заставить процессор выполнить его.

Что делает флаг: Передает компоновщику (линкеру) указание пометить секцию стека специальным битом NX (No-Execute).

  • Результат: Процессор физически запрещает исполнение инструкций, находящихся в области стека.
  • Если забыть: При попытке запуска программы на современных ядрах Linux вы можете получить предупреждение от линкера warning: execstack, а сама программа станет уязвимой для атак.

tasks.json

Создайте .vscode/tasks.json:

{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "Make 8-bit",
      "type": "shell",
      "command": "make build_8bit",
      "group": "build",
      "problemMatcher": ["$gcc"]
    },
    {
      "label": "Make 16-bit",
      "type": "shell",
      "command": "make build_16bit",
      "group": "build",
      "problemMatcher": ["$gcc"]
    },
    {
      "label": "Make clean",
      "type": "shell",
      "command": "make clean"
    }
  ]
}

Использование: Ctrl+Shift+B → выбрать задачу

launch.json

Создайте .vscode/launch.json:

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Debug 8-bit",
      "type": "cppdbg",
      "request": "launch",
      "program": "${workspaceFolder}/bin/8_bit",
      "args": [],
      "stopAtEntry": true,
      "cwd": "${workspaceFolder}",
      "environment": [],
      "externalConsole": false,
      "MIMode": "gdb",
      "miDebuggerPath": "/usr/bin/gdb",
      "preLaunchTask": "Make 8-bit",
      "setupCommands": [
        {
          "description": "Включить pretty-printing для gdb",
          "text": "-enable-pretty-printing",
          "ignoreFailures": true
        },
        {
          "description": "Установить архитектуру",
          "text": "set architecture i386:x86-64",
          "ignoreFailures": true
        }
      ]
    },
    {
      "name": "Run 8-bit",
      "type": "cppdbg",
      "request": "launch",
      "program": "${workspaceFolder}/bin/8_bit",
      "args": [],
      "stopAtEntry": false,
      "cwd": "${workspaceFolder}",
      "environment": [],
      "externalConsole": false,
      "MIMode": "gdb",
      "miDebuggerPath": "/usr/bin/gdb",
      "preLaunchTask": "Make 8-bit"
    }
  ]
}

Ключевые параметры:

Параметр Описание
program Путь к исполняемому файлу
stopAtEntry true — остановка на main(), false — запуск без остановки
preLaunchTask Задача сборки перед запуском
MIMode Режим Machine Interface: gdb

Отладка в VS Code

Управление отладкой:

Клавиша Действие
F5 Продолжить
F10 Шаг с обходом
F11 Шаг с заходом
Shift+F11 Выход из функции
Shift+F5 Остановить
Ctrl+Shift+F5 Перезапустить

Возможности:

  • Графический интерфейс
  • Точки останова мышью
  • Просмотр переменных и регистров
  • Пошаговое выполнение
  • Стек вызовов

Проекты Assembly-only

Концепция проектов Assembly-only

Этот подход используется, когда нужно:

  • Полный контроль над кодом — каждая инструкция написана вручную
  • Минимальный размер исполняемого файла — нет зависимостей от библиотек
  • Изучение работы процессора на самом низком уровне
  • Написание загрузчиков, драйверов или embedded-кода

Особенности:

  • Нет стандартной библиотеки (malloc, printf, etc.)
  • Все взаимодействие с ОС через syscalls
  • Максимальная производительность и полное понимание работы программы

Структура проекта

asm_project/
├── src/
│   └── hello.asm
├── build/              # Создаётся автоматически
├── bin/                # Создаётся автоматически
├── .vscode/
│   └── tasks.json      # ТОЛЬКО tasks.json
└── Makefile

Создание структуры:

mkdir ~/asm_project && cd ~/asm_project
mkdir src .vscode
touch Makefile .vscode/tasks.json

Концептуальный пример

Полный рабочий проект: Hello World с обработкой ввода

src/hello.asm:

section .data
    prompt db "Введите ваше имя: ", 0
    prompt_len equ $ - prompt
    greeting db "Привет, ", 0
    greeting_len equ $ - greeting
    newline db 10

section .bss
    name resb 64        ; Буфер для имени (64 байта)
    name_len resq 1     ; Длина введённого имени

section .text
    global _start

_start:
    ; Вывод приглашения
    mov rax, 1              ; syscall: write
    mov rdi, 1              ; fd: stdout
    mov rsi, prompt         ; buf: prompt
    mov rdx, prompt_len     ; count: prompt_len
    syscall

    ; Чтение имени
    mov rax, 0              ; syscall: read
    mov rdi, 0              ; fd: stdin
    mov rsi, name           ; buf: name
    mov rdx, 64             ; count: 64
    syscall

    ; Сохранить длину (минус newline)
    dec rax                 ; Убираем символ новой строки
    mov [name_len], rax

    ; Вывод приветствия
    mov rax, 1              ; syscall: write
    mov rdi, 1              ; fd: stdout
    mov rsi, greeting       ; buf: greeting
    mov rdx, greeting_len   ; count: greeting_len
    syscall

    ; Вывод имени
    mov rax, 1              ; syscall: write
    mov rdi, 1              ; fd: stdout
    mov rsi, name           ; buf: name
    mov rdx, [name_len]     ; count: name_len
    syscall

    ; Вывод newline
    mov rax, 1              ; syscall: write
    mov rdi, 1              ; fd: stdout
    mov rsi, newline        ; buf: newline
    mov rdx, 1              ; count: 1
    syscall

    ; Выход
    mov rax, 60             ; syscall: exit
    xor rdi, rdi            ; status: 0
    syscall

Пример работы:

$ ./bin/hello
Введите ваше имя: Алексей
Привет, Алексей

Makefile

Создайте Makefile:

.PHONY: all build prepare_dirs clean

all: build

prepare_dirs:
	@mkdir -p build bin

build: prepare_dirs
	nasm -f elf64 -g -F dwarf src/hello.asm -o build/hello.o
	ld build/hello.o -o bin/hello

clean:
	@echo "Cleaning project..."
	rm -f build/*.o
	rm -f bin/hello
	@echo "Done."

Использование:

make          # Собрать
make clean    # Очистить
./bin/hello   # Запустить

tasks.json

Создайте .vscode/tasks.json:

{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "Make build",
      "type": "shell",
      "command": "make build",
      "group": "build",
      "problemMatcher": []
    },
    {
      "label": "Make clean",
      "type": "shell",
      "command": "make clean"
    }
  ]
}

Использование: Ctrl+Shift+B


Ключевое отличие от C+Assembly

C + Assembly

Линковка через GCC:

gcc -o bin/program main.o calc.o
  • Точка входа: main
  • Подключается libc
Assembly-only

Линковка через LD:

ld hello.o -o bin/hello
  • Точка входа: _start
  • “Голый” исполняемый файл

Важно: В Assembly-only проектах точка входа всегда называется _start, а не main. Линковщик ld по умолчанию ищет именно этот символ.


Отладка Assembly-only проектов

Доступные варианты

Для отладки чистого ассемблера существует два подхода:

  1. Графическая отладка в VS Code (рекомендуется) — требует минимальной C-обёртки, подробно описана в отдельной статье
  2. Консольный GDB (описан ниже) — не требует изменений в коде, но менее удобен
Почему графическая отладка VS Code не работает напрямую

VS Code использует Debug Adapter Protocol (DAP). Расширение C/C++ предоставляет адаптер для GDB, но оно рассчитано на программы с точкой входа main и стандартной библиотекой C. Для чистого ассемблера с _start требуется адаптация через временную C-обёртку.

Для большинства задач удобнее использовать графическую отладку. Способ её настройки описан в статье «Отладка ASM в VS Code: Настройка GDB и визуальный интерфейс».

Когда использовать консольный GDB

Консольный GDB полезен в следующих случаях:

  • Финальная отладка перед сдачей проекта без временных обёрток
  • Работа на сервере без графического интерфейса
  • Необходимость специфических команд GDB, недоступных через VS Code
  • Изучение низкоуровневой отладки как самостоятельного навыка

Работа с GDB

Справочная таблица команд
Задача Команда Сокращение Пример
Запуск gdb ./bin/prog
Точка останова break b b _start
Старт run r r arg1 arg2
Шаг в инструкцию step s
Шаг через call next n
Регистры info registers i r i r rax rdi
Память examine x/FMT addr x/10xb $rsi
Значение print p/FMT p/x $rax
Продолжить continue c
Выход quit q

Форматы: x (hex), d (decimal), t (binary), s (string), i (instruction)

Основные команды

Запуск и управление выполнением:

gdb ./bin/hello         # Запуск с TUI-интерфейсом
break _start            # Точка останова на _start
run                     # Старт программы
continue                # Продолжить после останова
quit                    # Выход из GDB

Пошаговое выполнение:

s                       # Одна инструкция (заходит внутрь call)
n                       # Шаг с обходом call (выполняет функцию целиком)
finish                  # Выполнить до конца текущей функции

Просмотр данных:

# Проверка всех регистров
info registers          

# Значение конкретного регистра
print/x $rax            # В hex
print/d $rdi            # В decimal

# Следующие инструкции
x/10i $rip              

# Строка по адресу (когда в регистре адрес строки)
x/s $rsi                

# Содержимое массива
x/10xb &numbers         # 10 байт в hex
x/3gx &numbers          # 3 qword в hex

Интерфейс:

layout asm              # Показать ассемблерный код
layout regs             # Показать регистры
layout split            # Код + регистры
refresh                 # Перерисовать экран

Форматы вывода памяти
Формат Описание Пример Результат
/x Hexadecimal p/x $rax 0x2a
/d Decimal p/d $rax 42
/t Binary p/t $rax 101010
/s String x/s $rsi "Hello"
/i Instruction x/5i $rip Дизассемблированный код
/xb Hex bytes x/10xb $rsi 48 65 6c 6c 6f...
/xg Hex qwords x/3xg &array 0x0000000000000001...
Пример сессии отладки
$ gdb ./bin/hello
(gdb) layout regs
(gdb) break _start
Breakpoint 1 at 0x401000: file src/hello.asm, line 17.
(gdb) run
Breakpoint 1, _start() at src/hello.asm:17
(gdb) info registers
rax            0x0                 0
rbx            0x0                 0
rcx            0x0                 0
rdx            0x0                 0
rsi            0x0                 0
rdi            0x0                 0
rsp            0x7fffffffd920      0x7fffffffd920
rip            0x401000            0x401000 <_start>
eflags         0x202               [ IF ]
(gdb) step
(gdb) print/x $rax
$1 = 0x1
(gdb) i address prompt
Symbol "prompt" is at 0x402000 in a file compiled without debugging.
(gdb) x/s 0x402000
0x402000:       "Введите ваше имя: "
(gdb) continue
Continuing.
Введите ваше имя: Алексей
Привет, Алексей
[Inferior 1 (process 43112) exited normally]
Улучшение опыта работы

Создайте ~/.gdbinit для автоматической настройки:

# Контекст при каждой остановке
set disassemble-next-line on

# История команд
set history save on
set history size 10000
set history filename ~/.gdb_history

# Красивый вывод структур
set print pretty on

# Intel-синтаксис
set disassembly-flavor intel

# Без подтверждения при выходе
set confirm off
Опционально: GDB Dashboard

Что такое GDB Dashboard:

GDB Dashboard — это конфигурация для GDB с визуальным интерфейсом, которая одновременно показывает код, регистры, стек и другие данные.

Сравнение со стандартным GDB:

Функция Стандартный GDB С Dashboard
Код layout asm → одна панель Несколько панелей одновременно
Регистры info registers вручную Постоянно видны
Стек backtrace вручную Автоматическое обновление
Подсветка Нет Цветной код

Возможности:

  • Автоматические панели для кода, регистров, стека
  • Подсветка синтаксиса
  • Контекст выполнения без дополнительных команд
  • Настраиваемые модули
Интерфейс GDB Dashboard с панелями Assembly, Registers, Source и Stack

Установка:

# Резервная копия существующего .gdbinit
[ -f ~/.gdbinit ] && cp ~/.gdbinit ~/.gdbinit.backup

# Скачивание конфигурации
wget -P ~ https://github.com/cyrus-and/gdb-dashboard/raw/master/.gdbinit

Внимание: Команда перезапишет существующий ~/.gdbinit.

Базовые команды:

Важно: GDB Dashboard несовместим с флагом -tui. Dashboard сам управляет интерфейсом через свои панели. Запускайте просто gdb ./bin/prog без -tui.

dashboard                                    # Показать/скрыть
dashboard -layout assembly registers stack   # Выбрать панели
dashboard assembly -style height 15          # Настроить высоту

Восстановление:

cp ~/.gdbinit.backup ~/.gdbinit

Практический пример: Отладка ошибки сегментации

Процесс отладки программы с выходом за границы массива

Проблемный код (src/buggy.asm):

section .data
    numbers dq 10, 20, 30
    array_size equ 3

section .text
    global _start

_start:
    ; BUG: Попытка записи в read-only секцию .text
    ; Это гарантированно вызовет segmentation fault
    mov qword [_start], 0x90909090
    
    mov rax, 60
    xor rdi, rdi
    syscall

Процесс отладки:

# 1. Запуск программы
$ ./bin/buggy
[1]    7460 segmentation fault (core dumped)  ./bin/buggy

# 2. Запуск GDB
$ gdb ./bin/buggy
(gdb) run
Starting program: /home/basted/test_asm/bin/buggy 

Program received signal SIGSEGV, Segmentation fault.
_start () at src/buggy.asm:11
11          mov qword [_start], 0x90909090

# 3. Анализ состояния регистров
(gdb) info registers
rax            0x0                 0
rbx            0x0                 0
rcx            0x0                 0
rdx            0x0                 0
rsi            0x0                 0
rdi            0x0                 0
rbp            0x0                 0x0
rsp            0x7fffffffd8a0      0x7fffffffd8a0
rip            0x401000            0x401000 <_start>
eflags         0x10202             [ IF RF ]

# 4. Дизассемблирование
(gdb) disassemble _start
Dump of assembler code for function _start:
=> 0x0000000000401000 <+0>:     movq   $0xffffffff90909090,0x401000
   0x000000000040100c <+12>:    mov    $0x3c,%eax
   0x0000000000401011 <+17>:    xor    %rdi,%rdi
   0x0000000000401014 <+20>:    syscall
End of assembler dump.

# 5. Проверка карты памяти и прав доступа
(gdb) info proc mappings
Start Addr         End Addr           Size               Offset   Perms  File 
0x0000000000400000 0x0000000000401000 0x1000             0x0      r--p   buggy 
0x0000000000401000 0x0000000000402000 0x1000             0x1000   r-xp   buggy 
0x0000000000402000 0x0000000000403000 0x1000             0x2000   rw-p   buggy 
# Секция .text (0x401000) имеет права r-xp (read-execute), НЕТ записи!

# 6. Пошаговое выполнение
(gdb) break _start
Breakpoint 1 at 0x401000: file src/buggy.asm, line 11.
(gdb) run
Breakpoint 1, _start () at src/buggy.asm:11
11          mov qword [_start], 0x90909090

(gdb) stepi
Program received signal SIGSEGV, Segmentation fault.
_start () at src/buggy.asm:11
11          mov qword [_start], 0x90909090

Исправленный код:

section .data
    numbers dq 10, 20, 30
    array_size equ 3
    result dq 0              ; Добавили переменную для записи

section .text
    global _start

_start:
    ; Правильно: доступ к последнему элементу массива
    mov rcx, 2                  ; Индекс: 0, 1, 2
    mov rax, [numbers + rcx*8]  ; numbers[2] = 30
    
    ; Правильно: запись в секцию .data (разрешена запись)
    mov [result], rax
    
    mov rax, 60
    xor rdi, rdi
    syscall

Вывод: Для массива из 3 элементов по 8 байт валидные offset: 0, 8, 16. Попытка записи в секцию .text (адрес 0x401000 с правами r-xp) всегда вызывает segmentation fault, так как эта секция защищена от записи операционной системой.

💡 Подробный разбор частых ошибок, которые можно найти с помощью GDB, читайте в статье «Топ ошибок в NASM: Почему падает Segfault и неверные расчёты».

📋 5. Типичные ошибки и решения

Проблемы с компиляцией и линковкой

"NASM: fatal error - unable to open input file"

Причина: Неверный путь к файлу в Makefile или команде

Решение:

# Проверьте текущую директорию
pwd

# Проверьте наличие файла
ls -la src/hello.asm

# Убедитесь, что пути в Makefile относительные:
nasm -f elf64 src/hello.asm  # Правильно
nasm -f elf64 /src/hello.asm # Неправильно (абсолютный путь от корня)

"ld: warning: cannot find entry symbol _start"

Причина: Неправильное имя метки или отсутствие global _start

Решение:

; Убедитесь, что есть обе строки:
section .text
    global _start  ; Экспортируем метку
_start:            ; Определяем метку
    ; ваш код

"undefined reference to `main`"

Причина: Используется gcc для линковки Assembly-only проекта

Решение:

# Неправильно для Assembly-only:
gcc -o bin/hello hello.o

# Правильно:
ld hello.o -o bin/hello

# Или измените точку входа на main и добавьте exit:

section .text
    global main
    extern exit

main:
    ; ваш код
    
    ; Вызов exit(0)
    xor rdi, rdi
    call exit
"Segmentation fault" сразу при запуске

Возможные причины и решения:

1. Неправильное выравнивание стека:

; Неправильно (стек не выровнен по 16 байт перед call):
_start:
    call some_function

; Правильно:
_start:
    sub rsp, 8      ; Выравнивание стека
    call some_function
    add rsp, 8

2. Забыт ret в функции:

; Неправильно:
my_function:
    mov rax, 42
    ; ret забыт! Выполнение продолжится дальше

; Правильно:
my_function:
    mov rax, 42
    ret

3. Испорчен базовый указатель:

; Неправильно:
my_function:
    push rbp
    mov rbp, rsp
    ; ... код ...
    pop rbp
    mov rax, 123  ; Перезатерли rax после восстановления rbp!
    ret

; Правильно:
my_function:
    push rbp
    mov rbp, rsp
    ; ... код ...
    mov rax, 123  ; Сначала возвращаемое значение
    pop rbp       ; Потом восстанавливаем rbp
    ret

Проблемы с VS Code

Расширения не работают в WSL

Причина: Расширения установлены на Windows, но не работают в WSL

Решение:

  1. Подключитесь к WSL (зелёная кнопка >< → “Connect to WSL”)
  2. Откройте Extensions (Ctrl+Shift+X)
  3. Найдите расширение
  4. Нажмите “Install in WSL: Ubuntu” (НЕ просто “Install”)
Breakpoints не работают в C+Assembly

Причина: Отсутствует отладочная информация или используется PIE

Решение:

  1. Убедитесь в флагах компиляции:

    gcc -c -g -ggdb src/main.c  # Для C 
    nasm -f elf64 -g -F dwarf src/calc.asm  # Для ASM

  2. Отключите PIE при линковке:

    gcc -o bin/prog main.o calc.o -no-pie

  3. Пересоберите проект:

    make clean && make
    

Проблемы с GDB

"No symbol table is loaded"

Причина: Программа скомпилирована без отладочной информации

Решение:

# Пересоберите с флагами отладки:
nasm -f elf64 -g -F dwarf src/hello.asm -o hello.o
ld hello.o -o bin/hello

# Проверьте наличие отладочной информации:
file bin/hello
# Должно быть: "not stripped"

"Cannot access memory at address"

Причина: Попытка доступа к невыделенной или защищенной памяти

Решение:

# Проверьте, что адрес валиден:
(gdb) info proc mappings  # Показать карту памяти

# Проверьте границы вашего массива:
section .data
    array db 1, 2, 3
    array_size equ $ - array  # Размер в байтах

# Используйте только валидные смещения:
mov al, [array + 0]  # OK
mov al, [array + 2]  # OK (последний элемент)
mov al, [array + 3]  # ОШИБКА (выход за границы)

Проблемы с Makefile

"Makefile:X: *** missing separator"

Причина: Использование пробелов вместо табуляции

Решение:

# НЕПРАВИЛЬНО (пробелы):
hello:
    nasm -f elf64 src/hello.asm

# ПРАВИЛЬНО (табуляция):
hello:
	nasm -f elf64 src/hello.asm

В VS Code настройте автозамену для Makefile:

// settings.json
{
  "[makefile]": {
    "editor.insertSpaces": false,
    "editor.detectIndentation": false
  }
}

🏁 6. Итоговая проверка

Проверка окружения

Убедитесь, что все инструменты установлены:

nasm -v       # NASM version 2.16.xx+
gcc --version # gcc 11.x+
ld -v         # GNU ld version 2.xx
gdb -v        # GNU gdb version 12.x+

Ожидаемый вывод:

NASM version 2.16.01
gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0
GNU ld (GNU Binutils for Ubuntu) 2.38
GNU gdb (Ubuntu 12.1-0ubuntu1~22.04) 12.1

Проверка Git:

git config user.name
git config user.email

Должны вывестись ваши имя и email.

Проверка C + Assembly проекта

Структура:

  • ✅ Директории src/, .vscode/, build/, bin/
  • ✅ Файлы Makefile, tasks.json, launch.json
  • ✅ C-файлы и .asm файлы в src/

Работоспособность:

cd ~/c_asm_project
make              # Без ошибок
ls bin/           # Файлы появились
./bin/8_bit       # Программа запускается
make clean        # Очистка успешна
ls build/ bin/    # Директории пусты

Отладка в VS Code:

  1. Откройте проект в VS Code: code .
  2. Установите breakpoint в main.c
  3. Нажмите F5 → выберите “Debug 8-bit”
  4. Программа должна остановиться на main()
  5. F10 для пошагового выполнения
  6. Проверьте панель Variables

Чек-лист:

  • F5 запускает отладку
  • ✅ Остановка на main()
  • F10 переходит к следующей строке
  • ✅ Видны значения переменных
  • ✅ Можно просмотреть регистры в Debug Console: -exec info registers

Проверка Assembly-only проекта

Структура:

  • ✅ Директории src/, .vscode/, build/, bin/
  • ✅ Файлы Makefile, tasks.json (БЕЗ launch.json)
  • ✅ .asm файл имеет точку входа _start

Работоспособность:

cd ~/asm_project
make              # Без ошибок
ls bin/           # Файл появился
./bin/hello       # Программа запускается и выводит текст
echo $?           # Проверить код возврата (должно быть 0)
make clean        # Очистка успешна

Отладка через GDB:

gdb ./bin/hello
(gdb) layout asm
(gdb) layout regs
(gdb) break _start
Breakpoint 1 at 0x401000
(gdb) run
(gdb) info registers
(gdb) stepi
(gdb) continue

Чек-лист:

  • ✅ GDB запускается без ошибок
  • ✅ TUI-режим работает (layout asm, layout regs)
  • ✅ Breakpoint устанавливается на _start
  • stepi и nexti работают
  • info registers показывает значения
  • x/s адрес читает строки
  • ❌ VS Code отладка не работает (это нормально)

Финальная проверка VS Code

Для C + Assembly проектов:

cd ~/c_asm_project
code .
  1. Ctrl+Shift+B → выберите “Make 8-bit”
  2. Проверьте вывод в Terminal
  3. F5 → “Debug 8-bit”
  4. Убедитесь, что отладка запускается

Для Assembly-only проектов:

cd ~/asm_project
code .
  1. Ctrl+Shift+B → “Make build”
  2. Ctrl+` → открыть терминал
  3. gdb ./bin/hello → отладка в терминале

📚 7. Быстрая справка

Горячие клавиши VS Code

Действие Клавиша
Открыть терминал Ctrl+`
Быстрая сборка Ctrl+Shift+B
Запустить отладку F5
Запустить без отладки Ctrl+F5
Breakpoint F9
Шаг с обходом F10
Шаг с заходом F11
Выход из функции Shift+F11
Продолжить F5
Остановить Shift+F5
Перезапустить Ctrl+Shift+F5
Extensions Ctrl+Shift+X
Debug панель Ctrl+Shift+D
Командная палитра Ctrl+Shift+P
Открыть файл Ctrl+O
Поиск в файлах Ctrl+Shift+F

Команды терминала

# Сборка
make                    # Собрать всё
make <target>           # Конкретную цель
make clean              # Очистить

# Запуск
./bin/<program>         # Запустить программу
./bin/<program> arg1    # С аргументами

# Проверка инструментов
nasm -v                 # Версия NASM
gcc --version           # Версия GCC
ld -v                   # Версия линкера
gdb -v                  # Версия GDB
which nasm              # Путь к исполняемому файлу

# Git
git config --list       # Все настройки
git config --global user.name "Имя"
git config --global user.email "email"
git config --local user.name "Другое имя"  # Для текущего репо

# WSL (PowerShell на Windows)
wsl --list              # Список дистрибутивов
wsl --list --verbose    # Подробная информация
wsl --shutdown          # Остановить все WSL
wsl --update            # Обновить WSL
wsl --set-default-version 2
wsl -d Ubuntu           # Запустить конкретный дистрибутив

# Навигация
cd ~/project            # Перейти в директорию
pwd                     # Текущая директория
ls -la                  # Список файлов (подробно)
mkdir -p dir/subdir     # Создать директории
rm -rf directory        # Удалить директорию
cp file1 file2          # Копировать файл
mv file1 file2          # Переместить/переименовать

Команды GDB

# Запуск
gdb ./bin/hello         # Стандартный режим
gdb -tui ./bin/hello    # TUI-режим
gdb --args ./bin/prog arg1 arg2  # С аргументами

# Основные команды
break _start            # Breakpoint на функции
break *0x401000         # Breakpoint на адресе
break hello.asm:10      # Breakpoint на строке
info breakpoints        # Список breakpoints
delete 1                # Удалить breakpoint #1
disable 1               # Отключить breakpoint #1
enable 1                # Включить breakpoint #1

# Выполнение
run                     # Запустить программу
run arg1 arg2           # С аргументами
continue                # Продолжить выполнение
stepi                   # Одна инструкция
nexti                   # Шаг с обходом call
finish                  # До конца функции
until                   # До следующей строки
kill                    # Остановить программу

# Информация
info registers          # Все регистры
info registers rax rbx  # Конкретные регистры
print $rax              # Значение регистра
print/x $rax            # В hex
print/d $rax            # В decimal
print/t $rax            # В binary
print/c $rax            # Как символ

# Память
x/10i $rip              # 10 инструкций от rip
x/s $rsi                # Строка по адресу
x/16xb 0x404000         # 16 байт в hex
x/4xg 0x404000          # 4 qword в hex
disassemble             # Дизассемблировать текущую функцию
disassemble _start      # Конкретную функцию
disassemble 0x401000    # По адресу

# Интерфейсы
layout asm              # Показать ассемблерный код
layout regs             # Показать регистры
layout split            # Код + регистры
layout src              # Исходный код (для C)
tui disable             # Отключить TUI
tui enable              # Включить TUI
Ctrl+L                  # Обновить экран
Ctrl+X затем A          # Переключить TUI

# Другое
set disassembly-flavor intel  # Intel синтаксис
set disassembly-flavor att    # AT&T синтаксис
backtrace               # Стек вызовов
frame 0                 # Перейти к кадру стека
info stack              # Информация о стеке
quit                    # Выйти из GDB

Форматы данных в GDB

Формат Команда Описание Пример вывода
/x x/x или p/x Hexadecimal 0x2a
/d x/d или p/d Decimal 42
/u x/u или p/u Unsigned decimal 42
/o x/o или p/o Octal 052
/t x/t или p/t Binary 101010
/c x/c или p/c Character '*'
/s x/s String "Hello"
/i x/i Instruction mov rax, 1

Размеры:

  • b = byte (1 байт)
  • h = halfword (2 байта)
  • w = word (4 байта)
  • g = giant word (8 байт)

Примеры:

x/4xb 0x404000    # 4 байта в hex
x/2xw 0x404000    # 2 слова (4 байта каждое) в hex
x/8xg 0x404000    # 8 qword (8 байт каждый) в hex


✅ Заключение

Теперь у вас есть полноценное окружение для разработки на ассемблере с:

✅ Автоматической сборкой через Makefile
✅ Интеграцией с VS Code (редактирование и сборка)
✅ Графической отладкой (только C + Assembly)
✅ Консольной отладкой через GDB (Assembly-only)
✅ Поддержкой смешанных и чистых проектов
✅ Знанием типичных ошибок и их решений
✅ Полным справочником команд и инструментов

Ключевые моменты:

  • C + Assembly: VS Code для редактирования, сборки и отладки
  • Assembly-only: VS Code для редактирования и сборки, GDB для отладки
  • Все конфигурации универсальны для Linux и WSL
  • Всегда используйте флаги отладки (-g, -ggdb, -F dwarf)
  • Помните о различиях в точках входа: main vs _start
💜

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

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

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