При разработке программ полностью на ассемблере NASM с меткой _start возникает вопрос: как это удобно отлаживать? Хочется использовать мощный графический инструмент VS Code с breakpoints, step-by-step выполнением и просмотром переменных.
🚀 1. Быстрый старт
Если нужно быстро начать, вот минимальный набор действий:
Для простых программ (без argc/argv)
1. Создайте src/debug_wrapper.c:
extern void asm_main(void);
int main(void) {
asm_main();
return 0;
}
2. В вашем .asm файле замените:
global _start
_start:global asm_main
asm_main:3. Измените Makefile:
build_signed: prepare_dirs
+ gcc -c -g -ggdb -o build/debug_wrapper.o src/debug_wrapper.c
nasm -f elf64 -g -F dwarf src/io_signed.asm -o build/io_signed.o
nasm -f elf64 -g -F dwarf src/signed_a.asm -o build/signed_a.o
- ld -m elf_x86_64 -o bin/signed build/signed_a.o build/io_signed.o
+ gcc -o bin/signed build/debug_wrapper.o build/signed_a.o build/io_signed.o -m64 -no-pie -z noexecstack4. Запустите отладку: поставьте breakpoint в debug_wrapper.c на строке asm_main(); и нажмите F5.
Для программ с параметрами командной строки
Потребуется дополнительно адаптировать код для получения argc/argv через регистры вместо стека. Подробности в разделе “Специальная адаптация для программ с аргументами”.
💡 2. Зачем это нужно?
В чём корень проблемы?
Для VS Code не существует полнофункционального расширения, которое бы реализовывало Debug Adapter Protocol (DAP) специально для ассемблера NASM.
Debug Adapter Protocol (DAP) — это стандартный протокол, который позволяет интерфейсу VS Code “общаться” с реальными отладчиками (такими как GDB).
Поскольку нативного “Assembly-адаптера” не существует, здесь используется хитрость: расширение C/C++ от Microsoft. Оно умеет работать с GDB, но оптимизировано под стандартный C/C++ проект. Наша “чистая” ассемблерная программа с меткой _start, слинкованная через ld, не соответствует этой модели — отсюда и проблемы.
Что мы получим?
✅ Графический отладчик — визуальный интерфейс вместо консольного GDB
✅ Breakpoints — остановка выполнения на любой строке
✅ Step-by-step выполнение — пошаговая трассировка кода
✅ Просмотр переменных — значения регистров и памяти в реальном времени
✅ Call Stack — цепочка вызовов функций
✅ Watch expressions — отслеживание произвольных выражений
Для кого это актуально?
Этот метод особенно полезен при выполнении:
- Лабораторных работ №3, 4 и 5 по ассемблеру
- Собственных проектов на чистом ассемблере, например, с использованием готовых модулей ввода-вывода
- Системного программирования с прямыми системными вызовами
- Низкоуровневой разработки без использования стандартной библиотеки C
🤔 3. Какой подход выбрать?
Существует два основных способа отладки ассемблерного кода в VS Code:
| Подход | Преимущества | Недостатки | Когда использовать |
|---|---|---|---|
| Консольный GDB | Не требует изменения кода | Нет графического интерфейса | Простая отладка, опытные пользователи |
| Графический отладчик | Удобный интерфейс, breakpoints | Требует временные изменения | Сложная отладка, визуальное изучение |
В этой статье рассматривается графический подход, так как он даёт максимальное удобство для обучения и разработки. Подробнее о консольном GDB можно узнать в статье «Настройка NASM x86-64 и VS Code: Гайд для Linux и Windows».
🔧 4. Как это работает?
Архитектура решения
┌─────────────────────────────────────────────────────────┐
│ VS Code (интерфейс) │
├─────────────────────────────────────────────────────────┤
│ C/C++ Extension (DAP адаптер) │
├─────────────────────────────────────────────────────────┤
│ GDB (отладчик) │
├─────────────────────────────────────────────────────────┤
│ ┌──────────────────┐ ┌────────────────────┐ │
│ │ debug_wrapper.c │ ───▶ │ asm_main() в .asm │ │
│ │ (точка входа) │ │ (ваш код) │ │
│ └──────────────────┘ └────────────────────┘ │
└─────────────────────────────────────────────────────────┘Ключевая идея
💡 Решение: временно добавляем минимальную C-обёртку, которая вызывает нашу ассемблерную функцию. Программа при этом всё равно завершается через ассемблерный код — C нужен только для старта отладчика. Для финальной версии обёртка удаляется.
Важно понимать:
- C-обёртка НЕ управляет выполнением программы
- Она только запускает вашу ассемблерную функцию
- Программа завершается через
syscallв ассемблерном коде - Весь основной код остаётся полностью на ассемблере
Два типа программ
Процесс настройки зависит от того, использует ли ваша программа параметры командной строки:
Без параметров командной строки
- Только замена
_startнаasm_main - Весь остальной код остаётся без изменений
- Минимальные временные изменения
Примеры:
Консольный ввод/вывод, простые вычисления, лабораторные работы №3 и №5
С argc/argv
- Замена
_startнаasm_main - Адаптация получения параметров
- Аргументы передаются через регистры вместо стека
Примеры:
Обработка файлов, лабораторная №4
📝 5. Практика: Пошаговая настройка
Предварительные требования
Установите необходимые инструменты. Подробное пошаговое руководство по настройке окружения в Windows (WSL) и Linux доступно в статье «Настройка NASM x86-64 и VS Code: Гайд для Linux и Windows».
Краткий список:
# Установка всего необходимого
sudo apt update && sudo apt install build-essential gdb nasm -y
# Проверка установки
gcc --version
nasm --version
gdb --version
Расширения VS Code:
Шаг 1: Конфигурация VS Code
Создайте в корне проекта папку .vscode с двумя файлами конфигурации.
Файл .vscode/tasks.json
Автоматизирует сборку проекта:
{
"version": "2.0.0",
"tasks": [
{
"label": "Make all",
"type": "shell",
"command": "make",
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": ["$gcc"]
},
{
"label": "Make clean",
"type": "shell",
"command": "make clean"
}
]
}
Файл .vscode/launch.json
Вариант 1: Программы без аргументов
{
"version": "0.2.0",
"configurations": [
{
"name": "🔍 Debug",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/bin/main",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"miDebuggerPath": "/usr/bin/gdb",
"preLaunchTask": "Make all",
"setupCommands": [
{
"description": "Включить pretty-printing для gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
},
{
"description": "Установить Intel-синтаксис",
"text": "set disassembly-flavor intel",
"ignoreFailures": true
}
]
}
]
}
Вариант 2: Программы с аргументами командной строки
{
"version": "0.2.0",
"configurations": [
{
"name": "🔍 Debug (только файл)",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/bin/main",
"args": ["data/input.txt"],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"externalConsole": false,
"MIMode": "gdb",
"miDebuggerPath": "/usr/bin/gdb",
"preLaunchTask": "Make all",
"setupCommands": [
{
"description": "Включить pretty-printing",
"text": "-enable-pretty-printing",
"ignoreFailures": true
},
{
"description": "Intel-синтаксис",
"text": "set disassembly-flavor intel",
"ignoreFailures": true
}
]
},
{
"name": "🔍 Debug (файл + параметры)",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/bin/main",
"args": ["data/input.txt", "-100", "100", "5"],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"externalConsole": false,
"MIMode": "gdb",
"preLaunchTask": "Make all",
"setupCommands": [
{
"description": "Включить pretty-printing",
"text": "-enable-pretty-printing",
"ignoreFailures": true
},
{
"description": "Intel-синтаксис",
"text": "set disassembly-flavor intel",
"ignoreFailures": true
}
]
}
]
}
💡 Настройте под свой проект: Измените значения в массиве
argsи путь к исполняемому файлу вprogramв соответствии с вашими требованиями.
Шаг 2: Создайте C-обёртку
Создайте файл src/debug_wrapper.c:
// src/debug_wrapper.c
extern void asm_main(void);
int main(void) {
asm_main();
// Программа завершится внутри asm_main через syscall
return 0; // Эта строка не выполнится
}// src/debug_wrapper.c
extern void asm_main(int argc, char** argv);
int main(int argc, char** argv) {
asm_main(argc, argv);
// Программа завершится внутри asm_main через syscall
return 0; // Эта строка не выполнится
}⚠️ Важно: C-обёртка НЕ управляет завершением программы. Она только запускает вашу ассемблерную функцию, которая сама завершит выполнение через
syscall.
Шаг 3: Измените точку входа в .asm файле
Для всех программ замените _start на asm_main:
section .text
global _start
_start:
; === Ваш код ===
call read_input
call process_data
call print_output
mov rax, 60
xor rdi, rdi
syscallsection .text
global asm_main
asm_main:
; === Ваш код БЕЗ ИЗМЕНЕНИЙ ===
call read_input
call process_data
call print_output
mov rax, 60 ; sys_exit - оставляем!
xor rdi, rdi
syscall ; Программа завершится здесь🚨 Критически важно:
- Меняется только
global _start→global asm_mainи метка_start:→asm_main:syscallдля выхода ОСТАЁТСЯ! Не заменяйте его наret- Весь остальной код остаётся АБСОЛЮТНО без изменений
Шаг 4: Специальная адаптация для программ с аргументами
Если ваша программа использует аргументы командной строки (argc, argv), требуется дополнительное изменение: адаптация получения аргументов.
Почему это необходимо?
Это различие фундаментально и регулируется соглашением о вызовах System V ABI. Детальное описание ролей регистров (RDI, RSI, RDX и т.д.) можно найти в «Шпаргалка NASM x86-64: Регистры, Инструкции, Syscalls (Linux)».
_start:
pop rax ; argc
pop rbx ; argv[0]
pop rdi ; argv[1]
При запуске программы через _start
- Аргументы
argcиargvлежат на стеке - Первое значение
[rsp]= количество аргументов - Далее идут указатели:
[rsp+8]= argv[0][rsp+16]= argv[1][rsp+24]= argv[2]- и так далее…
asm_main:
; rdi = argc, rsi = argv
mov r8, [rsi + 8] ; argv[1]
При вызове asm_main(int argc, char** argv) из C
- Аргументы передаются через регистры (согласно x86-64 System V ABI)
rdi= argc (количество аргументов)rsi= argv (указатель на массив строк)- Доступ к элементам через смещения:
[rsi]= argv[0][rsi+8]= argv[1][rsi+16]= argv[2]
Визуальное сравнение
╔═══════════════════════════════════════════════════════════════╗
║ ДОСТУП К АРГУМЕНТАМ ║
╠═══════════════════════════════════════════════════════════════╣
║ ║
║ Через _start (стек): Через asm_main (регистры): ║
║ ┌─────────────┐ ┌─────────────┐ ║
║ │ [rsp] = 3 │ ← argc │ rdi = 3 │ ← argc ║
║ ├─────────────┤ ├─────────────┤ ║
║ │ [rsp+8] │ ← argv[0] │ rsi = &argv │ ║
║ ├─────────────┤ │ │ ║
║ │ [rsp+16] │ ← argv[1] │ [rsi+0] │ ← argv[0] ║
║ ├─────────────┤ │ [rsi+8] │ ← argv[1] ║
║ │ [rsp+24] │ ← argv[2] │ [rsi+16] │ ← argv[2] ║
║ └─────────────┘ └─────────────┘ ║
║ ║
╚═══════════════════════════════════════════════════════════════╝Пошаговая трансформация кода
ШАГ 1: Получение argc
_start:
pop rax ; Извлекаем argc из стека
cmp rax, 2 ; Проверяем количество аргументов
jl .error_usage ; Если < 2, показываем usageasm_main:
; argc УЖЕ в rdi, извлекать не нужно!
cmp rdi, 2 ; Проверяем количество аргументов
jl .error_usage ; Если < 2, показываем usageШАГ 2: Получение argv[1] (первый аргумент)
_start:
pop rax ; argc
pop rbx ; argv[0] (пропускаем)
pop rdi ; argv[1] (первый аргумент)asm_main:
; rdi = argc
; rsi = argv
; Получаем argv[1] (первый аргумент)
mov rdi, [rsi + 8]Таблица доступа к argv:
| Элемент | Смещение | Код | Описание |
|---|---|---|---|
| argc | — | rdi |
Количество аргументов |
| argv | — | rsi |
Указатель на массив |
| argv[0] | 0 | mov r8, [rsi] |
Имя программы |
| argv[1] | 8 | mov r9, [rsi + 8] |
Первый аргумент |
| argv[2] | 16 | mov r10, [rsi + 16] |
Второй аргумент |
| argv[3] | 24 | mov r11, [rsi + 24] |
Третий аргумент |
| argv[4] | 32 | mov r12, [rsi + 32] |
Четвёртый аргумент |
Критически важно: Всегда сохраняйте rsi на стеке (push rsi) перед использованием других регистров, и восстанавливайте (pop rsi), когда снова нужен доступ к argv!
Полный пример трансформации
Развернуть полный пример для программы с файлом и параметрами
section .data
usage_msg db "Usage: ./program <filename>", 10
usage_len equ $ - usage_msg
section .text
- global _start
+ global asm_main
-_start:
+asm_main:
+ ; При входе:
+ ; rdi = argc
+ ; rsi = argv
+
; Проверка argc
- pop rax ; Извлекаем argc из стека
- cmp rax, 2 ; Проверяем количество аргументов
+ cmp rdi, 2 ; argc УЖЕ в rdi
jl .show_usage
- ; Получение имени файла (argv[1])
- pop rbx ; argv[0] (имя программы) - пропускаем
- pop rdi ; argv[1] (имя файла) - сохраняем в rdi
+ ; Сохраняем rsi, т.к. rdi будем использовать для файла
+ push rsi ; ДОБАВЛЕНО: сохраняем указатель на argv
+
+ ; Получение имени файла (argv[1])
+ mov rdi, [rsi + 8] ; ИЗМЕНЕНО: доступ через массив
; Открытие файла
mov rax, 2 ; sys_open
+ push rsi ; ДОБАВЛЕНО: сохраняем rsi
xor rsi, rsi ; O_RDONLY
xor rdx, rdx
syscall
+ pop rsi ; ДОБАВЛЕНО: восстанавливаем rsi
; Проверка ошибки открытия
test rax, rax
js .error_open
; Далее работа с файлом...
; (этот код остаётся БЕЗ ИЗМЕНЕНИЙ)
; Завершение (БЕЗ ИЗМЕНЕНИЙ)
mov rax, 60
xor rdi, rdi
syscall
.show_usage:
mov rax, 1
mov rdi, 1
mov rsi, usage_msg
mov rdx, usage_len
syscall
mov rax, 60
mov rdi, 1
syscall
.error_open:
mov rax, 60
mov rdi, 1
syscall
Ключевые изменения
| Аспект | Было (_start) | Стало (asm_main) | Почему |
|---|---|---|---|
| Получение argc | pop rax |
rdi (уже здесь) |
Передаётся через регистр |
| Проверка argc | cmp rax, 2 |
cmp rdi, 2 |
argc теперь в rdi |
| Пропуск argv[0] | pop rbx |
Не нужно | Можем сразу обратиться к нужному элементу |
| Получение argv[1] | pop rdi |
mov rdi, [rsi + 8] |
Доступ через массив |
| Сохранение rsi | Не нужно | push rsi / pop rsi |
rsi нужен для доступа к argv |
Частые ошибки при адаптации
❌ Ошибка 1: Забыли сохранить rsi
asm_main:
mov rdi, [rsi + 8]
; ... используем rdi для системного вызова ...
mov r8, [rsi + 16] ; ❌ rsi мог быть перезаписан!asm_main:
push rsi ; Сохраняем rsi
mov rdi, [rsi + 8]
; ... используем rdi ...
pop rsi ; Восстанавливаем rsi
mov r8, [rsi + 16] ; ✅ Теперь безопасно❌ Ошибка 2: Неправильное смещение
mov rdi, [rsi + 1] ; ❌ Смещение 1 байт, а не 8!mov rdi, [rsi + 8] ; ✅ Каждый указатель = 8 байт❌ Ошибка 3: Попытка использовать pop
asm_main:
pop rax ; ❌ Стек не содержит argc!asm_main:
; argc УЖЕ в rdi, ничего не нужно извлекать
cmp rdi, 2Чек-лист адаптации для программ с аргументами
-
global _start→global asm_main -
_start:→asm_main: - Удалить
pop raxдля argc → использоватьrdiнапрямую - Изменить
cmp rax, N→cmp rdi, N - Удалить все
pop ...для argv → использоватьmov ..., [rsi + offset] - Добавить
push rsiперед использованием других регистров - Добавить
pop rsiкогда снова нужен доступ к argv - Убедиться, что смещения кратны 8:
[rsi + 8],[rsi + 16],[rsi + 24] -
syscallдля выхода остаётся без изменений!
Шаг 5: Модифицируйте Makefile
Изменения в Makefile идентичны для всех типов программ. Рассмотрим на примере стандартного проекта.
Предполагаемая структура проекта
Перед началом убедитесь, что структура вашего проекта выглядит так:
project/
├── src/
│ ├── debug_wrapper.c # ← Создайте этот файл (Шаг 2)
│ └── main.asm # Основная программа (измените _start → asm_main)
├── data/ # Опционально: тестовые данные
│ └── test.txt
├── build/ # Создаётся автоматически при сборке
├── bin/ # Создаётся автоматически при сборке
└── Makefile # ← Модифицируйте этот файл (Шаг 5)💡 Важно: Папки
build/иbin/создаются автоматически при выполненииmake(targetprepare_dirsв Makefile).
Сравнение: Было → Стало
.PHONY: all prepare_dirs clean
all: prepare_dirs
nasm -f elf64 -g -F dwarf src/main.asm -o build/main.o
ld -m elf_x86_64 -o bin/main build/main.o
prepare_dirs:
@mkdir -p build bin
clean:
@echo "Cleaning project..."
@rm -f build/*.o bin/*
@echo "Done."
.PHONY: all prepare_dirs clean
all: prepare_dirs
gcc -c -g -ggdb -o build/debug_wrapper.o src/debug_wrapper.c
nasm -f elf64 -g -F dwarf src/main.asm -o build/main.o
gcc -g -o bin/main build/debug_wrapper.o build/main.o -no-pie
prepare_dirs:
@mkdir -p build bin
clean:
@echo "Cleaning project..."
@rm -f build/*.o bin/*
@echo "Done."
Что изменилось (построчно)
1. Добавлена компиляция C-обёртки:
gcc -c -g -ggdb -o build/debug_wrapper.o src/debug_wrapper.c
| Флаг | Назначение |
|---|---|
-c |
Компилировать без линковки (создать .o файл) |
-g |
Включить отладочную информацию |
-ggdb |
Генерировать отладочную информацию в формате GDB |
2. Линковка изменена с ld на gcc:
ld -m elf_x86_64 -o bin/main build/main.ogcc -g -o bin/main build/debug_wrapper.o build/main.o -no-pieРазбор изменений:
| Изменение | Зачем |
|---|---|
ld → gcc |
GCC автоматически подключает C runtime и стандартные библиотеки |
build/debug_wrapper.o первым |
C runtime ищет функцию main() в первом объектном файле |
Добавлен -g |
Сохраняет отладочную информацию при линковке |
Добавлен -no-pie |
Отключает Position Independent Executable — делает адреса предсказуемыми для отладчика |
Удалён -m elf_x86_64 |
Этот флаг нужен только для ld, GCC определяет архитектуру автоматически |
💡 Почему
debug_wrapper.oдолжен быть первым?При линковке GCC ищет точку входа
main()в первом объектном файле. Если поставитьmain.oпервым, линковщик не найдётmain()и выдаст ошибку:
/usr/bin/ld: warning: cannot find entry symbol _start; defaulting to 0000000000401000Адаптация для разных случаев
Пример для многомодульного проекта:
all: prepare_dirs
gcc -c -g -ggdb -o build/debug_wrapper.o src/debug_wrapper.c
nasm -f elf64 -g -F dwarf src/io_module.asm -o build/io_module.o
nasm -f elf64 -g -F dwarf src/main.asm -o build/main.o
gcc -g -o bin/main build/debug_wrapper.o build/main.o build/io_module.o -no-pie
Пример для проекта с математической библиотекой (FPU):
all: prepare_dirs
gcc -c -g -ggdb -o build/debug_wrapper.o src/debug_wrapper.c
nasm -f elf64 -g -F dwarf src/main.asm -o build/main.o
gcc -g -o bin/main build/debug_wrapper.o build/main.o -no-pie -lm
📌 Для FPU программ: добавлен флаг
-lmдля линковки с математической библиотекой.
Проверка правильности изменений
После редактирования выполните:
make clean && make
Успешная сборка выглядит так:
gcc -c -g -ggdb -o build/debug_wrapper.o src/debug_wrapper.c
nasm -f elf64 -g -F dwarf src/main.asm -o build/main.o
gcc -g -o bin/main build/debug_wrapper.o build/main.o -no-pieПроверьте, что программа работает:
./bin/main
Частые ошибки
❌ Ошибка 1: undefined reference to 'main'
/usr/bin/ld: warning: cannot find entry symbol _startПричина: Объектные файлы указаны в неправильном порядке.
Решение: Убедитесь, что debug_wrapper.o стоит первым:
gcc -g -o bin/main build/debug_wrapper.o build/main.o -no-pie
^^^^^^^^^^^^^^^^^^^^^ первым!❌ Ошибка 2: Breakpoints не срабатывают или «прыгают»
Причина: Забыли добавить флаг -no-pie.
Решение: Добавьте -no-pie в конец команды линковки:
gcc -g -o bin/main build/debug_wrapper.o build/main.o -no-pie
^^^^^^^❌ Ошибка 3: error: debug_wrapper.c: No such file or directory
Причина: Файл debug_wrapper.c не создан или находится не в src/.
Решение: Убедитесь, что файл существует:
ls -la src/debug_wrapper.c
Если файла нет, вернитесь к Шагу 2 и создайте его.
Сводная таблица изменений
| Аспект | Финальная версия | Версия для отладки |
|---|---|---|
| Компиляция C | ❌ Нет | ✅ gcc -c -g -ggdb src/debug_wrapper.c |
| Линковщик | ld -m elf_x86_64 |
gcc -g ... -no-pie |
| Порядок .o | Любой | debug_wrapper.o первым |
| Доп. флаги | Нет | -no-pie (всегда), -lm (только для FPU) |
🎯 6. Процесс отладки
Как запустить отладку
⚠️ Важное ограничение: Breakpoint’ы не работают напрямую в
.asmфайлах. Отладка выполняется через C-обёртку.
Шаги для запуска:
- Откройте
src/debug_wrapper.cв VS Code - Поставьте breakpoint (
F9) на строкеasm_main(); - Нажмите
F5или выберите конфигурацию🔍 Debug - Программа остановится на breakpoint’е в C-файле
- Нажмите
F11(Step Into) — вы попадёте в ассемблерную функцию - Используйте
F10(Step Over) для пошагового выполнения - Вводите данные в DEBUG CONSOLE когда программа их запросит
Горячие клавиши
| Клавиша | Действие |
|---|---|
F5 |
Запустить отладку |
F9 |
Поставить/убрать breakpoint |
F10 |
Шаг с обходом (step over) |
F11 |
Шаг с заходом (step into) |
Shift+F11 |
Шаг с выходом (step out) |
Что вы увидите
Ключевые панели:
- VARIABLES/WATCH (слева вверху) — значения переменных и регистров
- CALL STACK (слева внизу) — цепочка вызовов:
main()→asm_main()→ ваши функции - DEBUG CONSOLE (внизу) — ввод/вывод программы и команды GDB
- Редактор (центр) — текущая строка выполнения (жёлтая стрелка)
🔍 7. Просмотр переменных в WATCH
Таблица типов данных
| Тип в NASM | Эквивалент C | Выражение для WATCH |
|---|---|---|
resb 1 |
char / int8_t |
*(char *)&var |
resb 1 |
unsigned char / uint8_t |
*(unsigned char *)&var |
resw 1 |
short / int16_t |
*(short *)&var |
resw 1 |
unsigned short / uint16_t |
*(unsigned short *)&var |
resd 1 |
int / int32_t |
*(int *)&var |
resd 1 |
unsigned int / uint32_t |
*(unsigned int *)&var |
resq 1 |
long long / int64_t |
*(long long *)&var |
resq 1 |
unsigned long long / uint64_t |
*(unsigned long long *)&var |
dd 0.0 |
float |
*(float *)&var |
dq 0.0 |
double |
*(double *)&var |
Примеры для разных типов программ
Целочисленные вычисления (int16 → int32)
Signed вариант:
*(short *)&a
*(short *)&b
*(int *)&output
Unsigned вариант:
*(unsigned short *)&a
*(unsigned short *)&b
*(unsigned int *)&output
Работа с массивами
// Первые 10 элементов массива
*(long long(*)[10])&array
// Конкретный элемент
*((long long *)&array + 5)
// Счётчик элементов
*(long long *)&count
FPU вычисления
В панели WATCH:
*(float *)&var_a
*(float *)&var_b
*(int *)&var_c
В DEBUG CONSOLE:
info float # Все регистры FPU
print $st0 # Вершина стека FPU
💻 8. Полезные команды GDB
Вводите эти команды в DEBUG CONSOLE для расширенного контроля.
# Регистры
info registers # Все регистры CPU
info float # Регистры FPU
# Память
x/10xb &array # 10 байт в hex
x/10dw &array # 10 слов в decimal
x/10xq &array # 10 qwords в hex
# Работа с регистрами
print $rax # Значение
print/x $rax # В hex
print/t $rax # В binary
set $rax = 42 # Изменить📖 Что значат эти регистры?
Если вы не помните, за что отвечает
RFLAGSилиRSP, загляните в раздел «🧠 2. Архитектура регистров x86-64» в «Шпаргалке NASM».
💡 Подробное руководство по GDB: «Настройка NASM x86-64 и VS Code: Гайд для Linux и Windows»
🛠️ 9. Troubleshooting
Многие проблемы, особенно Segmentation Fault при отладке, возникают из-за логических, а не синтаксических ошибок. Рекомендую ознакомиться со статьёй «Топ ошибок в NASM: Почему падает Segfault и неверные расчёты», где разобраны типичные «тихие» баги.
❌ Ошибка: Cannot find program
Решение:
- Проверьте путь в
launch.json:"${workspaceFolder}/bin/main" - Запустите:
make clean && make - Убедитесь, что файл существует:
ls -la bin/
❌ Breakpoint не срабатывает
Решение:
- Убедитесь в наличии флагов отладки:
-g -F dwarf(NASM),-g -ggdb(GCC) - Пересоберите:
make clean && make - Ставьте breakpoint на строке с инструкцией, не на комментарии
❌ WATCH показывает неверные значения
Решение: Проверьте соответствие типов:
resb→char(int8_t) /unsigned char(uint8_t)resw→short(int16_t) /unsigned short(uint16_t)resd→int(int32_t) /unsigned int(uint32_t)resq→long long(int64_t) /unsigned long long(uint64_t)
❌ Segmentation Fault при отладке
Возможные причины:
- Невыровненный стек (особенно для FPU программ)
- Неинициализированная память
- Выход за границы массива
- Неправильный доступ к argv (для программ с аргументами)
❌ Для программ с аргументами: программа не видит аргументы
Решение:
- Проверьте
launch.json: массивargsдолжен быть заполнен - Убедитесь, что адаптировали код для получения argc/argv через регистры
- Проверьте, что используете правильные смещения:
[rsi + 8],[rsi + 16], …
🔄 10. Подготовка к финальной версии
Когда отладка завершена, откатите все временные изменения:
Шаг 1: Удалите C-обёртку
rm src/debug_wrapper.c
Шаг 2: Верните _start в .asm файлах
Для всех программ:
section .text
global asm_main
asm_main:
; Ваш код
mov rax, 60
xor rdi, rdi
syscallsection .text
global _start
_start:
; Ваш код БЕЗ ИЗМЕНЕНИЙ
; syscall остаётся как был!
mov rax, 60
xor rdi, rdi
syscallШаг 3: Для программ с аргументами — верните доступ через стек
Если вы адаптировали код для asm_main, верните обратно:
asm_main:
; При входе: rdi = argc, rsi = argv
cmp rdi, 2
jl .error_usage
push rsi
mov rdi, [rsi + 8]
; ..._start:
pop rax ; argc
cmp rax, 2
jl .error_usage
pop rbx ; argv[0]
pop rdi ; argv[1]
; ...Шаг 4: Верните финальный Makefile
Для всех программ используйте ld для линковки:
all: prepare_dirs
nasm -f elf64 -g -F dwarf src/main.asm -o build/main.o
# другие .asm файлы...
ld -m elf_x86_64 -o bin/main build/main.o # другие .o
prepare_dirs:
@mkdir -p build bin
clean:
@rm -f build/*.o bin/*
Шаг 5: Проверьте финальную сборку
make clean
make
./bin/main # для программ без аргументов
./bin/main data/test.txt # для программ с аргументами
🏁 11. Чеклист перед финализацией
Общий чеклист
- Удалён
src/debug_wrapper.c - Точка входа изменена с
asm_mainна_start - Makefile использует
ldдля линковки -
syscallдля выхода на месте (не заменён наret) - Программа собирается и работает:
make clean && make - Проверены все граничные случаи
- Код прокомментирован
Дополнительно для программ с аргументами
- Восстановлен доступ к argc/argv через стек (pop инструкции)
- Удалены
push rsi/pop rsiесли они были добавлены - Проверена работа с разными вариантами аргументов
- Программа корректно обрабатывает файлы
✅ Заключение
Этот подход позволяет:
✅ Использовать современные инструменты — графический отладчик VS Code
✅ Видеть состояние программы — переменные, регистры, память
✅ Пошагово выполнять код — breakpoints, step into/over/out
✅ Быстро находить ошибки — call stack, watch expressions
✅ Соблюдать требования — финальная версия остаётся чистым ассемблером
Ключевые принципы
- C-обёртка не управляет выполнением — она только запускает asm_main
- Программа завершается через syscall — остаётся в ассемблерном коде
- Для простых программ процесс минимален — только замена _start на asm_main
- Для программ с аргументами нужна адаптация — argc/argv передаются через регистры rdi/rsi
- Временные изменения минимальны — легко откатить перед финализацией