Перейти к основному содержимому

Первая программа на ассемблере

Разработчику Архитектору

Play ITЗагрузка интерактивного демо…


Первая программа на ассемблере​

Если начинать ассемблер "с нуля", важнее всего не объём теории, а быстрый цикл "написал -> собрал -> запустил -> посмотрел регистры". Эта статья специально построена так, чтобы уже в первом проходе вы получили рабочий результат и связали команды с реальным поведением процесса в ОС.


Где применяют ассемблер​

Ассемблер — один шаг над машинным кодом: вы видите регистры, стек и системные вызовы. Нужен для отладки, драйверов, reverse engineering и понимания, как C и ОС реально работают на CPU.

Первая программа — mov, syscall/int 80h (зависит от ОС и ABI).


Язык ассемблера максимально близок к машинному коду: мнемоника обычно даёт одну инструкцию CPU, макросы и директивы — отдельный этап сборки. Программист управляет регистрами, памятью и (в привилегированном коде) прерываниями; в пользовательском режиме доступ к устройствам идёт через системные вызовы или API ОС.

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


Архитектура и диалекты​

Ассемблер привязан к архитектуре процессора (системе команд, ISA). В курсе опора — x86-64; сравнение CISC/RISC и семейств ISA — в Система команд и Intel/AT&T.

Для x86 один и тот же машинный код можно записать в стиле Intel (NASM, MASM) или AT&T (GAS, листинг objdump). В разделе эталон — Intel: операнды "назначение, источник". Подробное сравнение с примерами — в статье 17.


Можно ли писать на ассемблере в Windows​

Да, написание и запуск программ на ассемблере возможны в операционной системе Windows. Для этого требуется установить комплект инструментов, включающий ассемблер, компоновщик (линкер) и, при необходимости, отладчик. Современные версии Windows поддерживают 64-битное программирование, но многие учебные примеры построены на 32-битной модели, так как она проще для понимания и имеет меньше ограничений со стороны операционной системы.


Необходимые инструменты​

Для написания первой программы на ассемблере в Windows понадобятся следующие компоненты:


Ассемблер​

Наиболее подходящий выбор для начинающих — NASM (Netwide Ассемблер). Это свободный, кроссплатформенный ассемблер, поддерживающий Intel Syntax и генерирующий объектные файлы в форматах COFF (для Windows) и ELF (для Linux). NASM активно поддерживается, имеет простой синтаксис и обширную документацию.


Компоновщик (Linker)​

После ассемблирования исходного кода получается объектный файл, который необходимо объединить с системными библиотеками и оформить в исполняемый файл. В Windows для этой цели используется link.exe из Microsoft Visual Studio Build Tools или ld из MinGW-w64.

Для упрощения процесса рекомендуется установить MinGW-w64 — набор инструментов GNU для Windows, включающий компилятор GCC, ассемблер GAS, компоновщик ld и другие утилиты. Однако для работы с NASM удобнее использовать link.exe из официальных инструментов Microsoft.


Текстовый редактор или IDE​

Ассемблер не требует сложной среды разработки. Достаточно любого текстового редактора, поддерживающего подсветку синтаксиса, например:

  • Visual Studio Code с расширением для NASM,
  • Notepad++,
  • Sublime Text.

Полноценные IDE, такие как SASM (SimpleASM), специально созданы для обучения ассемблеру. SASM включает встроенный NASM, компоновщик, отладчик и окно вывода, что делает его идеальным выбором для первых шагов.


Отладчик (опционально, но полезно)​

Для анализа выполнения программы можно использовать x64dbg — современный отладчик для Windows, поддерживающий как 32-, так и 64-битные приложения. Он позволяет просматривать регистры, память, стек и пошагово выполнять инструкции.


Пошаговая установка инструментов​

Шаг 1 — Установка SASM (рекомендуемый способ для новичков)​

  1. Перейдите на официальный сайт SASM: https://dman95.github.io/SASM/
  2. Скачайте установщик для Windows.
  3. Запустите установщик и следуйте инструкциям.
  4. После установки откройте SASM. По умолчанию он настроен на использование NASM и генерацию 32-битных программ под Windows.

SASM автоматически устанавливает всё необходимое — ассемблер, компоновщик, среду выполнения. Это избавляет от необходимости настраивать пути и команды вручную.


Шаг 2 — Альтернативная установка (вручную)​

Если вы предпочитаете полный контроль:

  1. Скачайте NASM с официального сайта: https://www.nasm.us/

    • Выберите Windows-версию (обычно это ZIP-архив).
    • Распакуйте содержимое в папку, например C:\nasm.
    • Добавьте эту папку в переменную среды PATH.
  2. Установите Microsoft Visual Studio Build Tools:

    • Перейдите на страницу загрузки Visual Studio.
    • Выберите "Build Tools for Visual Studio".
    • В установщике отметьте компонент "C++ build tools", включающий link.exe.
  3. Убедитесь, что в командной строке доступны команды nasm и link.


Написание первой программы — Hello, World!​

Цепочка сборки: исходник .asm → NASM (объектный .o/.obj) → линкер (link, ld или gcc) → исполняемый файл. Загрузчик ОС кладёт секции .text, .data, .bss в память процесса.

Программа под Windows вызывает не ядро напрямую, а функции Win32 API из DLL. Простейший способ вывести текст — вызвать функцию MessageBoxA из библиотеки user32.dll.

Ниже приведён пример программы на 32-битном ассемблере с использованием NASM-синтаксиса:

Код ITЗагрузка примера кода…

Разбор:

  • section .data хранит нуль-терминированные строки msg и caption, которые WinAPI ожидает как char*.
  • global _start объявляет точку входа процесса, а extern _MessageBoxA@16 и extern _ExitProcess@4 подключают внешние символы из DLL через import table.
  • Перед call _MessageBoxA@16 параметры передаются через стек в обратном порядке (stdcall) — uType, lpCaption, lpText, hWnd.
  • Суффикс @16 указывает, что функция получает 16 байт аргументов (4 параметра по 4 байта).
  • Вызов _ExitProcess@4 завершает процесс кодом 0, переданным через push 0.
  • Фрагмент показывает полный минимальный путь GUI-программы — подготовка данных, API-вызов, штатное завершение.

Этот код делает следующее:

  • Определяет две строки в секции данных: сообщение и заголовок окна.
  • В секции кода вызывает функцию MessageBoxA из Windows API.
  • После закрытия окна вызывает ExitProcess для корректного завершения.

Сборка и запуск вручную​

Сборка вручную (NASM и link.exe), в командной строке:

nasm -f win32 hello.asm -o hello.obj
link /subsystem:windows /entry:_start hello.obj user32.lib kernel32.lib

Разбор:

  • Первая команда запускает NASM и создаёт объектный файл hello.obj в формате win32 COFF.
  • Ключ -f win32 должен совпадать с 32-битной моделью исходника и именами импортируемых stdcall-символов.
  • Вторая команда линковщика формирует hello.exe как GUI-приложение (/subsystem:windows) с точкой входа _start.
  • user32.lib и kernel32.lib подгружают import-описания для MessageBoxA и ExitProcess.
  • Итогом является PE-файл, который Windows загрузит и свяжет с нужными DLL при запуске.

В результате будет создан исполняемый файл hello.exe, который можно запустить двойным щелчком.


Сборка и запуск в SASM​

  1. Откройте SASM.
  2. Создайте новый файл.
  3. Вставьте приведённый выше код.
  4. Нажмите кнопку Run (или F9).
  5. Появится окно с надписью "Hello, World!".

SASM автоматически обрабатывает все этапы сборки и подключает необходимые библиотеки.


Особенности 64-битного программирования​

В 64-битной Windows системные вызовы через WinAPI работают иначе — соглашение о вызовах меняется, параметры передаются через регистры, а не через стек. Для первого знакомства рекомендуется начинать с 32-битного режима, так как он проще и лучше документирован в учебных материалах.

Подробнее: Windows x64, WinAPI и отличия от Linux — shadow space, WriteFile, отличия от Linux syscall.


Структура программы на ассемблере​

Программа на ассемблере состоит из логических блоков, называемых секциями. Каждая секция содержит определённый тип данных или инструкций. Наиболее важные секции в программе для Windows:

  • .data — инициализированные данные — строки, числа, константы.
  • .bss — содержит неинициализированные данные (например, буферы, массивы, которые будут заполнены позже).
  • .text — содержит исполняемый код, то есть последовательность машинных инструкций.

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


Регистры процессора​

Центральный процессор использует небольшие сверхбыстрые ячейки памяти — регистры — для временного хранения данных и адресов. В архитектуре x86 основные 32-битные регистры включают:

  • EAX, EBX, ECX, EDX — общего назначения,
  • ESI, EDI — индексные регистры для работы со строками и массивами,
  • ESP — указатель стека,
  • EBP — базовый указатель для доступа к параметрам функций.

В 64-битном режиме регистры расширяются до 64 бит (RAX, RBX и так далее), но для первых шагов достаточно понимания 32-битной модели.

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


Стек и вызовы функций​

Стек — это область памяти, используемая для передачи параметров в функции, хранения локальных переменных и возврата управления после вызова. Операции push и pop добавляют и извлекают значения из стека.

При вызове функции через call процессор автоматически помещает в стек адрес возврата. Функция завершается командой ret, которая извлекает этот адрес и передаёт управление обратно вызывающему коду.

В Windows API функции используют соглашение о вызовах stdcall, при котором параметры передаются через стек в обратном порядке (справа налево), а очистка стека выполняется самой функцией. Это отличает их от соглашения cdecl, где очистка стека лежит на вызывающей стороне.

Имена функций в 32-битном stdcall имеют специальный суффикс вида @N, где N — общее количество байт параметров. Например, _MessageBoxA@16 означает, что функция принимает 16 байт аргументов (четыре 32-битных параметра).

Как выглядит стек при call (упрощённо, 32-bit):

push 0 ; uType
push caption
push msg
push 0 ; hWnd
call _MessageBoxA@16 ; в стек кладётся адрес возврата
; после ret из MessageBox стек очищен самой DLL (stdcall)

Разбор:

  • Каждый push уменьшает ESP и кладёт 4 байта параметра.
  • Порядок в стеке снизу вверх — hWnd, msg, caption, uType, затем return address.
  • call добавляет адрес следующей инструкции поверх аргументов.
  • В stdcall функция сама делает ret 16 и снимает 16 байт параметров.
  • Понимание этой картины помогает читать краши и отлаживать неверный порядок push.

Для сравнения — Linux x86-64 Hello без WinAPI:

Код ITЗагрузка примера кода…

Разбор:

  • Тот же смысл, что у Windows-примера (вывод текста + выход), но контракт другой: syscall вместо DLL.
  • Нет push аргументов — первые параметры в регистрах (RDI, RSI, RDX).
  • Сборка: nasm -f elf64 hello.asm && ld hello.o -o hello.
  • Запуск в Linux/WSL; в чистой Windows этот бинарник не стартует без подсистемы Linux.

Понимание имён и экспорта​

Ключевое слово global _start указывает ассемблеру, что метка _start является точкой входа в программу. Компоновщик ищет именно эту метку, чтобы определить, с какого места начинать выполнение.

Ключевое слово extern объявляет, что функция определена вне текущего файла — в системной библиотеке. Например, _MessageBoxA@16 находится в user32.dll, а _ExitProcess@4 — в kernel32.dll. При компоновке эти имена связываются с реальными адресами в памяти.


Альтернативный подход — DOS и эмуляторы​

Если использовать современный Windows API кажется сложным, можно начать с эмуляции старой среды MS-DOS. Для этого подходит эмулятор DOSBox вместе с ассемблером TASM или MASM, а также утилитой LINK.EXE из пакета Microsoft Macro Ассемблер.

Пример простой программы для DOS:

Код ITЗагрузка примера кода…

Разбор:

  • msg db 'Hello, World!$' использует DOS-формат строк, где символ $ служит терминатором для функции вывода AH=09h.
  • mov ah, 09h выбирает сервис DOS "печать строки", а mov dx, msg передаёт смещение адреса строки.
  • int 21h передаёт управление DOS-прерыванию, которое выполняет выбранную функцию из регистра AH.
  • Блок mov ah, 4Ch / mov al, 0 / int 21h завершает программу и возвращает код выхода 0.
  • Код демонстрирует историческую модель API через прерывания, отличную от современного WinAPI и Linux syscall.

Здесь используется прерывание DOS int 21h для вывода текста. Строка должна заканчиваться символом $. Такой код нельзя запустить напрямую в современной Windows, но он отлично работает в DOSBox или через эмулятор emu8086.

Этот подход полезен для обучения, так как исключает необходимость взаимодействия с современными библиотеками и позволяет сосредоточиться на базовых инструкциях процессора.


Отладка первой программы​

После успешного запуска программы полезно заглянуть "внутрь" её выполнения. Отладчик x64dbg позволяет:

  • поставить точку останова на метке _start,
  • просматривать содержимое регистров до и после каждой инструкции,
  • наблюдать, как изменяется стек при вызове call,
  • видеть, какие системные библиотеки загружены в память.

Даже простой просмотр того, как адрес строки передаётся в стек, даёт ощущение контроля над машиной.


Частые ошибки​

СимптомПричина
Segmentation faultНеверный syscall / ABI (Linux и Windows)
nasm: command not foundАссемблер не установлен
Пустой выводНеверная метка строки или адрес в rsi/rcx
Линковка падаетНе указан -o или формат ELF/PE

Что попробовать​

  1. Измените текст в .data — убедитесь, что длина в mov edx, N совпадает.
  2. В x64dbg шагните по одной инструкции от _start.
  3. Сравните с эквивалентом на C.

Как закрепить материал после первой сборки​

Чтобы знания не остались "разовой демонстрацией", полезно пройти короткий цикл закрепления:

  1. Перепишите пример с MessageBoxA на вывод в консоль через WriteFile (см. Windows x64, WinAPI и отличия от Linux).
  2. Соберите аналогичный "Hello" под Linux и сравните стек/регистры в точке вызова (см. Архитектура ассемблерных программ).
  3. Вынесите вывод в отдельную подпрограмму и вызовите её два раза (см. Команды и подпрограммы).
  4. Разделите код на два файла и свяжите линкером (см. Несколько модулей и линковка).

Так вы сразу перейдёте от "одного примера" к инженерному рабочему процессу.


В подборках​

Статья входит в тематические подборки и блок "С чего начать?" на главной. Соседние шаги того же маршрута:

Первые шаги (маршрут подборки) — Первая программа на С, Первая программа на COBOL, Первая программа на F#, Первая программа на Fortran, Первая программа на 1С, Первая программа на Lisp.