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

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

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

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!

Цепочка сборки: исходник .asmNASM (объектный .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.