1. Введение в архитектуру x86, установка окружения и структура программы
Введение в архитектуру x86, установка окружения и структура программы
Добро пожаловать в курс «Основы программирования на ассемблере NASM». Это первая статья, с которой начнется ваше погружение в мир низкоуровневого программирования. Здесь нет магии, скрытой за абстракциями языков высокого уровня вроде Python или Java. Здесь есть только вы, процессор и память.
В этой статье мы разберем, как устроен компьютер с точки зрения программиста, установим необходимые инструменты и напишем нашу первую, пусть и простую, программу.
Что такое язык Ассемблера?
Язык ассемблера (Assembly language) — это человекочитаемое представление машинного кода. Компьютерный процессор не понимает слова «print» или «while». Он понимает только наборы нулей и единиц, которые представляют собой электрические сигналы. Эти наборы называются машинными инструкциями.
Ассемблер — это тонкая прослойка между машинным кодом и программистом. Каждая команда на ассемблере (например, mov или add) обычно соответствует ровно одной машинной инструкции процессора. Это дает вам полный контроль над оборудованием, но и возлагает на вас ответственность за каждый байт и каждый такт процессора.
Мы будем изучать NASM (Netwide Assembler). Это один из самых популярных, гибких и широко используемых ассемблеров для архитектуры x86.
Архитектура x86: Взгляд изнутри
Чтобы писать на ассемблере, нужно понимать, для чего мы пишем. Мы пишем команды для архитектуры x86 (и её 64-битного расширения x86-64). Это архитектура, на которой работают большинство современных персональных компьютеров и серверов (процессоры Intel и AMD).
Ключевые компоненты, с которыми нам предстоит работать:
!Схема взаимодействия процессора, регистров и оперативной памяти
Регистры процессора
Регистры — это самое важное понятие для начала. Представьте, что оперативная память — это огромный склад, а регистры — это ваши руки. Чтобы что-то сделать с данными (сложить, сравнить), вы должны сначала взять их со склада (памяти) в руки (регистры).
В архитектуре x86 существует несколько типов регистров. Мы начнем с регистров общего назначения. В 32-битной архитектуре они имеют размер 32 бита (4 байта) и начинаются с буквы E (Extended). В 64-битной — начинаются с R и имеют размер 64 бита.
Для простоты понимания в этом курсе мы будем часто обращаться к 64-битным регистрам, так как это современный стандарт, но помнить об их 32-битных «предках».
Основные регистры общего назначения:
| Регистр (64-bit) | Регистр (32-bit) | Назначение | | :--- | :--- | :--- | | RAX | EAX | Аккумулятор. Используется для арифметики и возврата значений функций. | | RBX | EBX | Базовый регистр. Часто используется как указатель на данные. | | RCX | ECX | Счетчик. Используется в циклах. | | RDX | EDX | Регистр данных. Используется при умножении/делении и вводе/выводе. | | RSI | ESI | Индекс источника. Используется при копировании данных. | | RDI | EDI | Индекс назначения. Используется при копировании данных. | | RSP | ESP | Указатель стека. Указывает на вершину стека (о стеке поговорим позже). | | RBP | EBP | Базовый указатель стека. Используется для навигации по стеку. |
> Регистры — это самые быстрые ячейки памяти в компьютере. Оптимизация работы с ними — ключ к производительности.
Память и адресация
Память представляет собой огромный массив байтов. Каждый байт имеет свой уникальный адрес. Когда мы говорим процессору «прочитай данные», мы должны указать адрес этих данных.
Размер памяти измеряется в байтах. Связь между битами и байтами выражается формулой:
где — это один байт (минимальная адресуемая единица памяти), а — это восемь бит (нулей или единиц), из которых он состоит.
Установка окружения
Для работы нам понадобятся два инструмента:
Мы будем ориентироваться на работу в среде Linux, так как это «родная» среда для изучения системного программирования. Если у вас Windows, рекомендуется использовать WSL (Windows Subsystem for Linux) или виртуальную машину.
Установка в Linux (Ubuntu/Debian)
Откройте терминал и введите следующие команды:
Пакет build-essential установит необходимые утилиты, включая компоновщик ld.
Проверьте установку:
Вы должны увидеть версию NASM.
Структура программы на NASM
Программа на ассемблере NASM имеет четкую структуру. Она делится на секции (sections). Секции помогают операционной системе понять, как обращаться с разными частями вашей программы.
Стандартная программа состоит из трех основных секций:
Пример шаблона программы
Обратите внимание на global _start. Метка _start — это стандартное имя точки входа в программу для линкера ld в Linux. Директива global делает эту метку видимой извне, чтобы линкер мог её найти.
Первая программа: «Hello, World!»
Давайте напишем программу, которая выводит строку «Hello, World!» в терминал и корректно завершается. Мы будем использовать системные вызовы (syscalls) ядра Linux.
Создайте файл hello.asm и вставьте туда следующий код:
bash
nasm -f elf64 hello.asm -o hello.o
bash
ld hello.o -o hello
bash
./hello
``
Если вы увидели надпись Hello, World!`, поздравляю! Вы написали и запустили свою первую программу на ассемблере.
Заключение
Сегодня мы познакомились с фундаментом: архитектурой x86, регистрами и структурой программы на NASM. Мы узнали, что программа состоит из секций данных и кода, и научились использовать системные вызовы для общения с ОС.
В следующей статье мы углубимся в систему команд: научимся складывать, вычитать и управлять потоком выполнения программы.