Программы на ассемблере: примеры простых программ

Содержание

  1. 1.Что такое ассемблер и зачем он нужен
  2. 2.Основные элементы: регистры, инструкции, память
    1. 2.1.Регистры
    2. 2.2.Инструкции и операции
    3. 2.3.Адресация памяти
  3. 3.Примеры программ на ассемблере: базовые конструкции
    1. 3.1.Hello, World на x86 DOS (NASM)
    2. 3.2.Сложение двух чисел (NASM, 32-бит)
  4. 4.Циклы и ветвления: как устроен алгоритм
    1. 4.1.Цикл с счётчиком через CX
    2. 4.2.Условное ветвление через CMP и JE
  5. 5.Работа с памятью и стеком
    1. 5.1.Подпрограммы: CALL и RET
  6. 6.Типичные ошибки и как их избежать
  7. 7.Как выбрать ассемблер и среду для работы
  8. 8.Подытожим: с чего начать и что изучить дальше
Хотите стать фрилансером и начать зарабатывать удаленно?
Регистрируйтесь на Ворк24!
Хотите заказать настройку и доработку сайта?
Эксперты Ворк24 помогут!

Новичок открывает учебник по системному программированию — и сразу встречает незнакомые мнемоники, загадочные регистры и прерывания. Без живых примеров этот материал кажется абстракцией.

Статья написана для студентов, начинающих системных разработчиков и всех, кто готовится к работе с встраиваемыми системами или оптимизацией кода. Для тех, кто интересуется, как устроены и работают простые программы на 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.

1.png

Алгоритм работы: загрузить адрес строки в DX, выбрать функцию 09h в AH, вызвать прерывание. DOS берёт управление, находит строку по адресу и выводит символы до знака ‘$’. Затем программа завершается через функцию 4Ch.

❗Это важно!

Символ $ в конце строки — обязательный маркер для функции 09h. Без него DOS будет выводить всё подряд до первого встреченного $ в памяти.

Сложение двух чисел (NASM, 32-бит)

Следующий шаг — арифметическая операция с возвратом результата через код выхода:

2.png

Результат сложения помещается в EBX, который Linux использует как код выхода процесса. Проверить можно командой echo $? — она выведет 12.

Циклы и ветвления: как устроен алгоритм

Реальный алгоритм редко обходится без повторений и условий. В ассемблере цикл строится вручную: нужно инициализировать счётчик, проверить условие, выполнить тело и сделать переход обратно.

Цикл с счётчиком через CX

Регистр CX специально предназначен для организации циклов. Команда LOOP уменьшает CX на 1 и переходит к метке, пока CX не равен нулю:

3.png

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

Условное ветвление через CMP и JE

Ветвление работает через два шага: сравнение командой CMP (она вычитает второй операнд из первого и устанавливает флаги, но не сохраняет результат) и условный переход:

4.png

Флаги ZF (нуль), CF (перенос), SF (знак) — именно их проверяют команды JE, JNE, JL, JG. Понимание флагов позволяет строить любые условия без дополнительных переменных.

💡Обратите внимание!

CMP не меняет операнды — только устанавливает флаги. Это отличает его от SUB, которая записывает результат вычитания.

Работа с памятью и стеком

Стек — область памяти, организованная по принципу «последним пришёл — первым вышел» (LIFO). Он нужен для временного хранения данных, передачи аргументов в подпрограммы и сохранения адреса возврата.

PUSH кладёт значение на стек и уменьшает указатель SP. POP забирает верхний элемент и увеличивает SP. Классический паттерн: сохранить регистр перед вызовом подпрограммы, выполнить вычисления, восстановить:

5.png

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

Подпрограммы: CALL и RET

CALL сохраняет адрес следующей инструкции на стек и передаёт управление. RET снимает этот адрес со стека и возвращает выполнение:

6.png

Соглашение о вызове (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 и руководство к выбранному ассемблеру. Они сухие, но точные: каждая операция описана с точностью до флага и такта.

Вам нужна биржа фриланса для новичков или требуются разработчики сайтов?

Комментарии

Нет комментариев
Не можешь разобраться в этой теме?
Обратись за помощью к фрилансерам
Гарантированные бесплатные доработки в течение 1 года
Быстрое выполнение от 1 дня
Безопасная сделка
Прямой эфир