GNU as в Ubuntu: ассемблирование кода и примеры

Введение

Утилита as, входящая в набор инструментов GNU Assembler, выступает базовым звеном при сборке низкоуровневых программ в Linux. В данном руководстве подробно рассматривается весь рабочий цикл: от развертывания инструментария компиляции до генерации объектных файлов, их компоновки с помощью линковщика и проверки кода возврата исполняемого бинарника.

1. Подготовка окружения и аудит возможностей GNU as

Для трансляции ассемблерного кода понадобятся ключевые системные утилиты из состава binutils, а также метапакет gcc. Установим их через пакетный менеджер:

Установка binutils и gcc в Ubuntu

Ассемблер as (часто именуемый GAS) транслирует мнемоники архитектуры процессора в машинный код объектного формата ELF. Сведения о релизе утилиты и целевой платформе проверяются ключом --version:

⚙️ Часто используемые флаги трансляции:

-o : задает имя и путь для генерируемого объектного модуля.
-g : внедряет отладочные символы, необходимые при исследовании логики в GDB.
-a : формирует листинг трансляции, где исходный текст сопоставляется с байтами машинного кода.
--32 : задействует режим сборки под 32-разрядную архитектуру x86.
--64 : предписывает сборку 64-битного кода x86-64.
-v / --version : выводит диагностические сведения о сборке транслятора.

Проверка версии и архитектуры GNU as

2. Создание минималистичной программы и первый объектный файл

Напишем компактный исходник под архитектуру x86-64 с точкой входа _start. Программа выполняет системный вызов sys_exit (номер 60) и завершается с кодом 42:

Создание исходного файла exit_code.s

Трансляция файла exit_code.s в бинарный модуль exit_code.o осуществляется с помощью ключа -o. Если текст не содержит синтаксических ошибок, процесс завершается в терминале без лишнего вывода:

Ассемблирование исходного кода в объектный модуль

Убедимся в успешном создании артефакта компиляции и оценим его размер стандартным вызовом ls:

Анализ свойств полученного объектного файла

3. Внедрение отладочных метаданных (-g) и генерация листинга (-a)

Для полноценной пошаговой отладки программы через GDB сгенерируем отдельный модуль exit_code_debug.o, добавив флаг -g. Это сохранит исходную разметку символов:

Генерация объектного файла с отладочной таблицей

Чтобы наглядно увидеть, как инструкции ассемблера превращаются в конкретные опкоды процессора, применим опцию формирования листинга -a=exit_code.lst:

Трансляция с записью листинга трансляции

Ознакомимся со структурой сгенерированного отчета с помощью утилиты head:

Просмотр первых строк файла листинга

Листинг объединяет смещения адресов, шестнадцатеричные байты сгенерированных опкодов и исходные строки кода, что крайне удобно для реверс-инжиниринга и анализа плотности инструкций.

4. Сборка исполняемого файла линковщиком ld и контроль выполнения

Объектный файл не является самодостаточным исполняемым модулем. Чтобы разрешить системную точку входа, свяжем exit_code.o в бинарный файл exit_program силами компоновщика ld:

Компоновка исполняемого ELF-бинарника

Запустим собранный бинарник напрямую из текущего каталога. Программа завершает свою работу мгновенно, не отправляя данных в стандартный поток вывода:

Тестовый запуск скомпонованной программы

Считаем статус завершения процесса через служебную переменную окружения оболочки $?:

Контроль кода возврата через системную переменную

Отображение значения подтверждает, что системный вызов ядра отработал штатно и программа передала операционной системе ожидаемый код завершения. Освоение базового цикла работы с as и ld дает системному инженеру четкое понимание того, как высокоуровневые конструкции операционной системы реализуются на уровне инструкций процессора.