Создадим первую программу с помощью ассемблера GAS. При создании программы на ассемблере стоит понимать, что это не высокоуровневый язык, где достаточно вызвать одну функцию, которая выполнит всю сложную работу. В ассемблере, чтобы выполнить довольно простые вещи, придется писать довольно много инструкций. И здесь есть разные подходы: мы можем написать весь код только на ассемблере - вариант, который в реальности втречается редко, либо мы можем какие-то части писать на ассемблере, а какие-то на языке высокого уровня, например, на С. В данном случае рассмотрим создание программы целиком на ассемблере, но в дальнейшем посмотрим на второй вариант на примере взаимодействия с языками С и С++.
Итак, напишем программу на ассемблере, которая выводит строку на консоль. Для этого определим на жестком диске папку для файлов с исходным кодом. Допустим, она будет называться asm. И в этой папке создадим новый файл, который назовем hello.s и в котором определим следующий код:
.globl _start .data message: .asciz "Hello METANIT.COM\n" # текст выводимого сообщения .text _start: movq $message, %rsi # в RSI - адрес строки movq $1, %rdi # в RDI - дексриптор вывода в стандартный поток (консоль) movq $18, %rdx # в RDX - длина строки movq $1, %rax # в RAX - номер функции для вывода в поток syscall # вызываем функцию Linux movq $60, %rax syscall
Теперь протестируем программу. Откроем консоль и перейдем в ней к папке, где располагается файл hello.s. Затем выполним следующюю команду
as hello.s -o hello.o
В результате ассемблер GAS из кода программы скомпилирует объектный файл hello.o. Далее создадим исполняемый файл, выполнив следующую программу компоновщика:
ld hello.o -o hello
Затем запустим программу командой
./hello
Полный вывод компиляции и выполнения программы:
eugene@Eugene:~/asm# as hello.s -o hello.o eugene@Eugene:~/asm# ld hello.o -o hello eugene@Eugene:~/asm# ./hello Hello METANIT.COM eugene@Eugene:~/asm#
Вкратце разеберем программу. Вначале идет инструкция
.globl _start
Далее определяет секция данных программы
.data
Эта директива указывает, что дальше располагаются определения данных. В нашем случае это одна переменная message:
message: .asciz "Hello METANIT.COM\n"
То есть в начале указывается имя переменной - message, а после двоеточия - ее определение. С помощью директивы .asciz мы указываем, что переменная будет представлять строку. Затем идет собственно строка в кавычках - значение переменной message.
С помощью директивы .text
открываем секцию кода и затем определяем точку входа в программу - метку _start
:
.text _start:
Далее идут инструкции программы. Суть программы - вывод сообщения message на экран. Но ассемблер по умолчанию не имеет подобного функционала, и обычно в этом случае задействуются системные вызовы текущей операционной системы. В зависимости от конкретной системы детали этих вызовов - их номер и параметры будут отличаться. Например, на Linux это системная функция write, которая имеет номер 1 и которая принимает 3 параметра:
В регистр RSI помещается адрес строки
В регистр RDI помещается дескриптор вывода - то есть куда выводить строку. К примеру, это может быть файл на диске, консоль и т.д.
В регистр RDX помещается количество символов строки
И в соответствии с данным API сначала помещаем в регистр RSI адрес строки message:
movq $message, %rsi
Чтобы указать, что в регистр помещается именно адрес, а не сама переменная, перед названием переменной указывается символ $.
Поскольку мы выводим на консоль, то в регистр RDI надо поместить дескриптор консольного вывода - число 1:
movq $1, %rdi
Далее в регистр RDX помещается количество символов выводимой строки:
movq $18, %rdx
И для вывода строки помещаем в регистр RAX номер системной функции Linux (число 1) и вызываем ее:
movq $1, %rax syscall
После этого помещаем в RAX номер системной функции завершения приложения - число 60 и также вызываем ее для завершения работы программы:
movq $60, %rax syscall