Новичок открывает учебник по системному программированию — и сразу встречает незнакомые мнемоники, загадочные регистры и прерывания. Без живых примеров этот материал кажется абстракцией.
Статья написана для студентов, начинающих системных разработчиков и всех, кто готовится к работе с встраиваемыми системами или оптимизацией кода. Для тех, кто интересуется, как устроены и работают простые программы на ASM. Мы предложим разбор кода и практические шаблоны для начинающих разработчиков, разберём типичные ошибки.
Здесь вы найдёте разбор базовых конструкций, готовые примеры программ на ассемблере и объяснение того, как они работают изнутри. По данным IEEE Computer Society, знание языка ассемблера остаётся обязательным требованием для специалистов по встраиваемым системам и кибербезопасности.
Что такое ассемблер и зачем он нужен
Ассемблер — язык программирования, каждая инструкция которого соответствует одной команде процессора. В отличие от Python или Java, здесь нет автоматического управления памятью: программист сам решает, куда записать значение, в каком регистре его держать и когда освободить ресурс.
Такой подход даёт прямой доступ к железу и предсказуемое время выполнения каждой операции. Именно поэтому ассемблер используют там, где важен каждый такт: драйверы, прошивки микроконтроллеров, критические участки игровых движков.
Современная разработка редко пишется целиком на ассемблере. Чаще он нужен точечно: оптимизировать горячий цикл, написать загрузчик, разобрать дизассемблированный листинг при реверс-инжиниринге.
Знание ассемблера помогает понять, во что компилятор превращает ваш C-код — и найти узкие места там, где профилировщик уже не помогает.
Основные элементы: регистры, инструкции, память
Прежде чем смотреть на примеры кода на ассемблере, нужно усвоить три базовые сущности: регистры, инструкции и адресное пространство памяти.
Регистры
Регистр — это ячейка внутри процессора, самое быстрое место для хранения данных. В архитектуре x86 основные регистры общего назначения: AX, BX, CX, DX (16-бит) или EAX, EBX, ECX, EDX (32-бит). Каждый регистр выполняет свою роль по соглашению: AX — аккумулятор для арифметики, CX — счётчик в циклах, DX — расширение при делении.
64-битный код использует RAX, RBX и т. д. Современные компиляторы активно задействуют регистры для хранения промежуточных результатов — это быстрее, чем каждый раз обращаться к оперативной памяти.
Инструкции и операции
Каждая строка кода на ассемблере — это одна операция: скопировать, сложить, сравнить, перейти. Синтаксис: мнемоника + операнды. Например, MOV AX, 10 кладёт число 10 в регистр AX.
Вот ключевые группы команд:
- Пересылки: MOV, PUSH, POP.
- Арифметика: ADD, SUB, MUL, DIV.
- Логика: AND, OR, XOR, NOT.
- Переходы: JMP, JE, JNE, CALL, RET.
- Ввод-вывод: INT (системные прерывания).
Порядок операндов зависит от синтаксиса: Intel (MOV AX, BX — «приёмник слева») или AT&T (movw %bx, %ax — «источник слева»).
Для Windows и DOS обычно используют синтаксис Intel. Для Linux и GCC — AT&T. Уточните синтаксис до того, как запускать код — иначе ассемблер выдаст ошибки уже на этапе разбора файла.
Адресация памяти
Адресация — способ указать, откуда взять данные. Непосредственная (MOV AX, 5 — константа прямо в команде), прямая (MOV AX, [1000h] — по адресу), косвенная через регистр (MOV AX, [BX] — адрес лежит в BX).
Для работы с массивами применяют индексную адресацию: MOV AX, [BX + SI], где BX — база, SI — смещение.
Примеры программ на ассемблере: базовые конструкции
Теория без кода не работает. Ниже — таблица с ключевыми командами и примерами их применения, а затем разбор целостных программных фрагментов.
| Инструкция | Действие | Пример | Результат |
|---|---|---|---|
| MOV | Копирует значение | MOV AX, 5 | AX = 5 |
| ADD | Складывает операнды | ADD AX, BX | AX = AX + BX |
| SUB | Вычитает операнд | SUB AX, 1 | AX = AX − 1 |
| INT | Системное прерывание | INT 21h | Вызов DOS |
| JMP | Безусловный переход | JMP start | Переход к метке |
| CMP | Сравнивает значения | CMP AX, 0 | Устанавливает флаги |
| JE | Прыжок при равенстве | JE equal | Если AX = 0 |
Каждую из этих инструкций можно встретить уже в самом первом рабочем примере — выводе текста на экран.
Hello, World на x86 DOS (NASM)
Классический старт: вывод строки через прерывание DOS INT 21h.

Алгоритм работы: загрузить адрес строки в DX, выбрать функцию 09h в AH, вызвать прерывание. DOS берёт управление, находит строку по адресу и выводит символы до знака ‘$’. Затем программа завершается через функцию 4Ch.
Символ $ в конце строки — обязательный маркер для функции 09h. Без него DOS будет выводить всё подряд до первого встреченного $ в памяти.
Сложение двух чисел (NASM, 32-бит)
Следующий шаг — арифметическая операция с возвратом результата через код выхода:

Результат сложения помещается в EBX, который Linux использует как код выхода процесса. Проверить можно командой echo $? — она выведет 12.
Циклы и ветвления: как устроен алгоритм
Реальный алгоритм редко обходится без повторений и условий. В ассемблере цикл строится вручную: нужно инициализировать счётчик, проверить условие, выполнить тело и сделать переход обратно.
Цикл с счётчиком через CX
Регистр CX специально предназначен для организации циклов. Команда LOOP уменьшает CX на 1 и переходит к метке, пока CX не равен нулю:

После завершения в AX будет 15 (5+4+3+2+1). Команда LOOP — сокращённая запись пары DEC CX / JNZ. Это удобнее читать, но на современных процессорах LOOP может быть медленнее явной пары команд.
Условное ветвление через CMP и JE
Ветвление работает через два шага: сравнение командой CMP (она вычитает второй операнд из первого и устанавливает флаги, но не сохраняет результат) и условный переход:

Флаги ZF (нуль), CF (перенос), SF (знак) — именно их проверяют команды JE, JNE, JL, JG. Понимание флагов позволяет строить любые условия без дополнительных переменных.
CMP не меняет операнды — только устанавливает флаги. Это отличает его от SUB, которая записывает результат вычитания.
Работа с памятью и стеком
Стек — область памяти, организованная по принципу «последним пришёл — первым вышел» (LIFO). Он нужен для временного хранения данных, передачи аргументов в подпрограммы и сохранения адреса возврата.
PUSH кладёт значение на стек и уменьшает указатель SP. POP забирает верхний элемент и увеличивает SP. Классический паттерн: сохранить регистр перед вызовом подпрограммы, выполнить вычисления, восстановить:

Порядок при POP обратный: последний сохранённый регистр снимается первым. Нарушение этого порядка — одна из самых частых причин «мусора» в регистрах при отладке.
Подпрограммы: CALL и RET
CALL сохраняет адрес следующей инструкции на стек и передаёт управление. RET снимает этот адрес со стека и возвращает выполнение:

Соглашение о вызове (calling convention) определяет, где передавать аргументы и где ожидать результат. В простых DOS-программах обычно используют регистры напрямую; в 32/64-битном коде Linux и Windows действуют стандарты cdecl, stdcall или System V AMD64 ABI.
❗Несоответствие соглашений о вызове — частый источник ошибок при смешивании ASM-кода с кодом на C.
Типичные ошибки и как их избежать
Даже небольшие простые программы на ассемблере требуют аккуратности. Перечислим ошибки, с которыми сталкиваются чаще всего.
1. Неправильный порядок операндов. В синтаксисе Intel: MOV приёмник, источник. Перепутать их легко при переходе с AT&T.
2. Потеря маркера конца строки. В DOS INT 21h строка должна заканчиваться на ‘$’; без него программа выведет случайные символы из памяти.
3. Несбалансированный стек. Каждый PUSH должен иметь соответствующий POP. Лишний PUSH или POP разрушает адрес возврата.
4. Неправильный сегмент данных. Переменные, объявленные в .data, недоступны, если не инициализирован сегментный регистр DS.
5. Смешение синтаксисов. NASM и MASM по-разному работают с адресами: в NASM [BX] — обращение к памяти, просто BX — значение регистра. MASM использует иные правила.
Хорошая практика — комментировать каждую нетривиальную строку и проверять флаги после каждой арифметической команды. Это упрощает отладку и чтение кода спустя время.
✅Проверяйте программу пошагово в отладчике (GDB, OllyDbg, x64dbg) — это быстрее, чем искать ошибку в листинге вручную.
Как выбрать ассемблер и среду для работы
Перед написанием первого кода нужно определиться с инструментами. Выбор зависит от платформы, цели и того, какой синтаксис вам ближе.
NASM (Netwide Assembler) — самый популярный вариант для обучения на Linux и Windows. Он использует синтаксис Intel, хорошо документирован и поддерживает форматы ELF, PE, COM. Подходит для DOS-программ, 32- и 64-битного кода под Linux и Windows.
MASM (Microsoft Macro Assembler) ориентирован на экосистему Windows. Он входит в состав Visual Studio и удобен, если вы работаете с WinAPI или пишете код для встраивания в C+±проекты под MSVC. Синтаксис схож с NASM, но есть отличия в работе с макросами и сегментами.
GAS (GNU Assembler) — часть цепочки инструментов GCC. Использует синтаксис AT&T, поэтому поначалу читается непривычно: операнды записываются в обратном порядке, перед регистрами стоит знак процента. Его выбирают, когда нужно писать inline-ассемблер внутри C-кода для GCC.
Для отладки достаточно стандартных средств: GDB на Linux, x64dbg или OllyDbg на Windows. Они позволяют выполнять программу пошагово, смотреть содержимое регистров и стека после каждой инструкции.
Если только начинаете, оптимальный путь такой: установите NASM, возьмите любой текстовый редактор с подсветкой синтаксиса (VS Code с расширением для ASM), соберите первую программу через командную строку. Никаких тяжёлых IDE на старте не нужно — минимальная цепочка сборки понятнее и быстрее.
Подытожим: с чего начать и что изучить дальше
Ассемблер — не язык повседневной разработки, но знание его открывает понимание, которое не даст ни один высокоуровневый язык. Вы видите, как процессор читает инструкции одну за другой, как данные перемещаются между регистрами и памятью, как управление передаётся между подпрограммами.
Разобранные здесь примеры кода — не просто учебные упражнения. Они отражают реальные паттерны: вывод данных, арифметику, циклы, условия, работу со стеком. Именно эти конструкции встречаются в прошивках, драйверах и эксплойтах.
Следующий шаг — поставить NASM или MASM, написать и собрать первую программу самостоятельно. Потом перейти к разбору дизассемблированного вывода компилятора: возьмите простую функцию на C, скомпилируйте с флагом -O0 и откройте в GDB. Вы узнаете в листинге те же конструкции, которые разобраны в этой статье.
Хорошая база — документация Intel IA-32/64 Software Developer Manual и руководство к выбранному ассемблеру. Они сухие, но точные: каждая операция описана с точностью до флага и такта.
Вам нужна биржа фриланса для новичков или требуются разработчики сайтов?


Комментарии