Как установить fasm на ubuntu

Обновлено: 06.07.2024

Написание и отладка кода на ассемблере x86/x64 в Linux

17 августа 2016

Сегодня мы поговорим о программировании на ассемблере. Вопрос «зачем кому-то в третьем тысячелетии может прийти в голову писать что-то на ассемблере» раскрыт в заметке Зачем нужно знать всякие низкоуровневые вещи, поэтому здесь мы к нему возвращаться не будем. Отмечу, что в рамках поста мы сосредоточимся на вопросе компиляции и отладки программ на ассемблере. Сам же язык ассемблера заслуживает отдельного большого поста, а то и серии постов.

Введение

Компиляторов ассемблера существует много. Мы будем использовать GNU Assembler (он же GAS, он же /usr/bin/as). Скорее всего, он уже есть вашей системе. К тому же, если вы пользуетесь GCC и собираетесь писать ассемблерные вставки в коде на C, то именно с этим ассемблером вам предстоит работать. Из достойных альтернатив GAS можно отметить NASM и FASM.

Наконец, язык ассемблера отличается в зависимости от архитектуры процессора. Пока что мы сосредоточимся на ассемблере для x86 (он же i386) и x64 (он же amd64), так как именно с этими архитектурами приходится чаще всего иметь дело. Впрочем, ARM тоже весьма распространен, главным образом на телефонах и планшетах. Еще из сравнительно популярного есть SPARC и PowerPC, но шансы столкнуться с ними весьма малы. Отмечу, что x86 и x64 можно было бы рассматривать отдельно, но эти архитектуры во многом похожи, поэтому я не вижу в этом большого смысла.

«Hello, world» на int 0 x80

Рассмотрим типичный «Hello, world» для архитектуры x86 и Linux:

.data
msg :
. ascii "Hello, world!\n"
. set len , . - msg

Коротко рассмотрим первые несколько действий, выполняемых программой: (1) программа начинает выполнение с метки _start, (2) в регистр eax кладется значение 4, (3) в регистр ebx помещается значение 1, (4) в регистр ecx кладется адрес строки, (5) в регистр edx кладется ее длина, (6) происходит прерывание 0 x80. Так в мире Linux традиционно происходит выполнение системных вызовов. Конкретно int 0 x80 считается устаревшим и медленным, но из соображений обратной совместимости он все еще работает. Далее мы рассмотрим и более новые механизмы.

Следующая строчка из файла unistd_32.h:

То есть, рассмотренный код эквивалентен:

Затем аналогичным образом производится вызов:

Совсем не сложно!

В общем случае системный вызов через 0 x80 производится по следующим правилам. Регистру eax присваивается номер системного вызова из unistd_32.h. До шести аргументов помещаются в регистры ebx, ecx, edx, esi, edi и ebp. Возвращаемое значение помещается в регистр eax. Значения остальных регистров при возвращении из системного вызова остаются прежними.

Выполнение системного вызова через sysenter

Начиная с i586 появилась инструкция sysenter, специально предназначенная (чего нельзя сказать об инструкции int) для выполнения системных вызовов.

Рассмотрим пример использования ее на Linux:

.data
msg :
. ascii "Hello, world!\n"
len = . - msg

. text
. globl _start

Сборка осуществляется аналогично сборке предыдущего примера.

Как видите, принцип тот же, что при использовании int 0 x80, только перед выполнением sysenter требуются поместить в стек адрес, по которому следует вернуть управление, а также совершить кое-какие дополнительные манипуляции с регистрами. Причины этого более подробно объясняются здесь.

Инструкция sysenter работает быстрее int 0 x80 и является предпочтительным способом совершения системных вызовов на x86.

Выполнение системного вызова через syscall

До сих пор речь шла о 32-х битных программах. На x64 выполнение системных вызовов осуществляется так:

.data
msg :
. ascii "Hello, world!\n"
. set len , . - msg

Собирается программа таким образом:

as --64 hello-syscall.s -o hello-syscall.o
ld -melf_x86_64 -s hello-syscall.o -o hello-syscall

Принцип все тот же, но есть важные отличия. Номера системных вызовов нужно брать из unistd_64.h, а не из unistd_32.h. Как видите, они совершенно другие. Так как это 64-х битный код, то и регистры мы используем 64-х битные. Номер системного вызова помещается в rax. До шести аргументов передается через регистры rdi, rsi, rdx, r10, r8 и r9. Возвращаемое значение помещается в регистр rax. Значения, сохраненные в остальных регистрах, при возвращении из системного вызова остаются прежними, за исключением регистров rcx и r11.

Интересно, что в программе под x64 можно одновременно использовать системные вызовы как через syscall, так и через int 0 x80.

Отладка ассемблерного кода в GDB

Статья была бы не полной, если бы мы не затронули вопрос отладки всего этого хозяйства. Так как мы все равно очень плотно сидим на GNU-стэке, в качестве отладчика воспользуемся GDB. По большому счету, отладка не сильно отличается от отладки обычного кода на C, но есть нюансы.

Например, вы не можете так просто взять и поставить брейкпоинт на процедуру main. Как минимум, у вас попросту нет отладочных символов с информацией о том, где эту main искать. Решение заключается в том, чтобы самостоятельно определить адрес точки входа в программу и поставить брейкпоинт на этот адрес:

Вообще программирование на ассемблере в Linux мало распространено и занимаются им, разве что, фанаты ассемблера. Сегодня мы и поговорим о программировании на ассемблере и инструментарий.Что нам понадобится:

После загрузки архива с офф. сайта распакуем его:

tar zxvf fasm-1.69.11.tgz

В папке у нас будет бинарный файл fasm, который мы можем использовать для компиляции. Для удобства вы можете создать симлинк на него:

sudo ln -s /home/username/fasm/fasm /usr/local/bin

ald и shed устанавливаются не сложнее:

В итоге у нас будет 3 полезных инструмента для программирования на ассемблере.

Как и большинство других операционных систем, Linux предоставляет т.н. API - набор полезных для программиста функций. В большинстве случаев вызов системной функции производится с помощью прерывания 80h. Следует отметить, что Linux используется fastcall-конвенция передачи параметров. Согласно ей параметры передаются через регистры (в windows, например, используется stdcall, где параметры передаются через стек). Номер вызываемой функции кладется в eax, а параметры в регистры:

Номер параметра Регистр
1 ebx
2 ecx
3 edx
4 esi
5 edi
6 ebp

Как видите все не так сложно. Узнать номер системной функции, ее описание и параметры можно, хотя бы здесь. Возьмем, к примеру sys_exit. Как можно увидеть на той странице у нее есть один параметр - код возврата и она имеет порядковый номер 1. Таким образом мы можем вызвать ее следующим кодом:

Надеюсь, что все понятно.

Ну что же. Писать мы ничего не будем, т.к. за нас все написано :) В папке fasm/examples/elfexe есть файл hello.asm в котором находится следующий код:

Как видите здесь вызываются 2 системных функции - sys_write (с порядковым номером 4) и sys_exit. sys_write принимает 3 параметра - дескриптор потока вывода (1 - stdout), указатель на строку и размер строки. Сам номер функции, как уже говорилось, мы должны положить в eax. Функцию sys_exit мы уже разобрали. Скомпилировать это чудо можно так: fasm hello.asm (но не обязательно, т.к. там же, где лежит исходник, есть и бинарник).

Посмотрим, что внутри

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

Мы видим всю нашу программу, данные, elf-заголовок. Неплохо? Теперь мы посмотрим на нашу программу в отладчике. Наберем в консоли:

Нас должна попреветствовать строка с предложением ввести команду. Список команд вы можете узнать, набрав help или получить помощь по отдельной команде, набрав help command . Дизассемблировать нашу программу можно командой disassemble (или ее алиас - " d "). Вы увидете дизассемблированный листинг вашей программы. Слева - адрес, справа - сама команда, а посередине - опкод команды.

Получить дамп можно командой dump (странно, но ее нет в выводе команды help).

Теперь попробуем поработать с командой next . Выполните ее и в ответ вам покажут значения регистров, установленные флаги, а так же адрес, опкод и дизассемблированную команду, которая должна выполниться следующей. Попробуйте выполнять команды и следите за изменением флагов и регистров. После вызова первого прерывания у вас на экране должна появиться надпись "Hello world!".

Установка и настройка Flat Assembler (FASM)


Установка и настройка Flat Assembler (FASM)

Программирование на Ассемблере я начинал с Turbo Assembler (TASM) под MS-DOS, сейчас пишу на Flat Assembler (FASM) под Windows. Это очень удобный и мощный пакет для разработки, бесплатный для любого использования. Написан полностью на самом себе, исходники прилагаются. Мне он нравится тем, что позволяет хранить код и описания ресурсов в одном ASM-файле, поддерживает макросы, генерит чистый машинный код без всякой незаявленной самодеятельности, не требует лишней рутинной работы типа прописывания каждой задействованной функции в секцию импорта и еще множество приятных мелочей, облегчающих жизнь программисту. Единственным недостатком является отсутствие достаточного количества готовых исходников, например по сравнению с тем же MASM'ом, а портирование исходников с других платформ на FASM иногда вызывает затруднения. Но на мой взгляд портирование, в отличие от копирования, помогает гораздо лучше изучить язык. Несколько исходников-примеров есть в самом дистрибутиве FASM, еще несколько примеров можно скачать с офсайта или поискать в Интернете. Ответы на многие возникающие вопросы можно найти на официальном форуме FASM. Форум англоязычный, но на нем немало наших соотечественников. К новичкам на форуме относятся хорошо и отвечают даже на самые глупые вопросы.

Для установки Flat Assembler скачайте дистрибутив с офсайта (около 800 килобайт). На момент публикации версия FASM 1.67.27, если ссылка изменится, то можете посмотреть обновление на странице загрузки. Там же можно скачать версии FASM для Linux, Unix и MS-DOS. Бесплатный add-on FASMARM к FASM для работы с ARM можно найти здесь, текущая версия FASMARM 1.12. Инсталлятора нет, программа устанавливается простым извлечением из архива в какое-нибудь удобное для вас место, например C:\FASM. Обязательно скачайте справочник Microsoft Win32 Programmer's Reference, распакуйте его в папку с FASM'ом. Для удобства можно сделать вложенную папку \help.

Microsoft Win32 Programmer's Reference

Техническую документацию по FASM на английском языке можно скачать с офсайта, на русском языке есть вот такие мануалы:

Основной мануал Flat Assembler 1.64

Руководство по препроцессору FASM

FASM 1.64: Руководство программиста

Для продвинутых программистов могу порекомендовать справочник Windows NT/2000 Native API Reference на английском языке:

Windows NT/2000 Native API Reference

Для общего развития можно почитать книги Ассемблер для процессоров Intel Pentium (автор Ю.С.Магда) и Ассемблер для DOS, Windows и Unix (автор С.В.Зубков), Ассемблер & Win32. Курс молодого бойца (автор и составитель Р.Аблязов), Intel Hex Opcodes And Mnemonics - общее описание ассемблерных команд Intel 80486, x86 Instruction Set Reference - перечень и описание команд x86 процессора.

Ассемблер для процессоров Intel Pentium

Ассемблер для DOS, Windows и Unix

Ассемблер & Win32. Курс молодого бойца

Учебник по основам языка Ассемблера

Учебник по языку Ассемблер в задачах и примерах

Intel Hex Opcodes And Mnemonics

x86 Instruction Set Reference

И напоследок две книги из категории "must have". Их, конечно, лучше иметь в бумажном варианте в качестве настольных справочников, но электронные версии тоже вполне подойдут. Книги залиты на файлообменник. Обе книги на русском языке, в хорошем качестве.

Внутреннее устройство Microsoft Windows 2000. Мастер-класс


Внутреннее устройство Microsoft Windows 2000. Мастер-класс

Внутреннее устройство Microsoft Windows: Windows Server 2003, Windows XP, and Windows 2000


Внутреннее устройство Microsoft Windows: Windows Server 2003, Windows XP, and Windows 2000

При первом запуске FASM создаст файл настроек FASMW.INI, откройте его для редактирования в Блокноте. В конец файла надо будет добавить две новых секции. Сперва пропишите полный путь к Win32 Programmer's Reference:

[Help]
path=C:\FASM\help\WIN32.HLP

Это же можно сделать и через меню редактора Help - Pick help file. Теперь, если все сделано правильно, достаточно установить в редакторе курсор на название функции API и нажать F1. Справочник сразу откроется на описании этой функции.

После этого надо установить необходимые переменные окружения и пути для подключаемых файлов. Например у меня прописаны вот такие:

[Environment]
include="C:\FASM\INCLUDE"
music="C:\FASM\KEYGEN\XM_FILES"
ufmod="C:\FASM\INCLUDE\MUSIC"

Переменная include обязательная, остальные для удобства. Теперь при написании программ вместо абсолютных путей можно (и нужно) использовать относительные:

Учиться программировать начнем с процессора Intel 8086. Будем писать программы под DOS Программирование под Windows и Linux сложнее, а нам надо с чего-то начинать. Поэтому начнем с простого и понятного 16-битного процессора 8086.

Практическая ценность от программирования под DOS в наше время не очень большая, если вы, конечно, не собираетесь тесно работать с этой операционной системой. Но она позволит нам быстро освоить основы ассемблера, а потом мы уже перейдем к программированию под 32-битные системы.

Выбор компилятора

Для программирования на ассемблере нам прежде всего необходим компилятор. Наиболее известные компиляторы это TASM, MASM и FASM. В моем учебном курсе я буду использовать FASM. Это довольно новый, удобный, быстро развивающийся компилятор ассемблера, написанный на себе самом Его преимущества — это поддержка сложных макросов и мультиплатформенность. Есть версии под DOS, Windows и Linux.

С его помощью можно сгенерировать файл любого формата, не обязательно исполняемый файл, так что FASM — это превосходный инструмент для экспериментов и исследований.

Сложность в том, что сегодня большинство операционных систем даже на телефонах имеет 64-битную разрядность. В силу особенностей архитектуры просто так запустить 32-разрядную программу DOS на 64-битной системе не получится.

Эмуляция 32-битного DOS в 64-битной среде. DOSBox

Тем, кто работает в 32-битной системе Windows могут просто использовать fasm под Windows для написания и компиляции программ. В этом случае все программы учебного курса вы сможете запустить под Windows. Конечно, реально они будут работать в эмуляторе DOS, в режиме виртуального процессора 8086. Но для учебных целей это вполне подойдёт.

Ну а как же быть тем, кто идёт в ногу с цивилизацией и работает в 64-битной среде? Поскольку писать пограммы для 64 битного процессора нам еще рано, потребуется как-то эмулировать среду 32-битного DOS, к примеру, можно установить виртуальную машину, на неё поставить Windows XP 32-бит или последнюю версию MS-DOS, но предлогаю поступить проще и обойтись эмулятором DOSBox.

С оффициального сайта вы можете скачать DOSBox последней версии. Установите его, следуя инструкциям установщика и для удобства на тот же раздел диска, где находится fasm. Ярлык запуска эмулятора DOSBox появился на рабочем столе. Готово!

Ну или почти, поскольку попытка скомпилировать программу внутри fasm запущенном на DOSBox из коробки обернется неудачей. Причина в том, что DOSBox ориентирован в первую очередь на любителей ретроигр.


Редактирование dosbox.conf

Не будем огорчатся, что разработчики эмулятора обошли поддержку 32-битного реального режима (FRM mode). Мы можем получить нужный результат, перед вызовом fasm загрузив DPMI сервер. Скачайте cwsdpmi по ссылке с моего сайта. Распакуйте архив туда, куда вы ранее установили fasm. В итоге путь к CWSDPMI будет иметь вид C:\fasm\CWSDPMI.

Читайте также: