Assembler #1 Пишем свою первую программу
Доброго времени суток, господа. Мой проект имеет главной целью помочь молодым специалистам в сфере IT. И первым делом я хотел бы начать небольшой курс для начинающих системных программистов. Сегодня мы напишем свою первую программу на Assembler(далее ASM), а именно всем известный "Hello World" для NASM x64. Работать будем ОС Linux.
Для начала представлю Вам сам код, а позже подробно расскажу как он работает:
db
В первых строках нашей программы Вы можете увидеть следущий код:
text db "Hello, World!", 10
db
- расшифровывается как "define bytes". Это означает, что вы хотите выделить некоторое количество байт и занести в них какую-либо информацию.
После db мы видим строку "Hello, World!", 10
. Буквы в кавычках - наш текст, как можно судя из обычных логических предположений понять. А число 10
после запятой означает знак окончания строки. Именно благодаря ему мы имеем возможность перейти на новую строку терминала и сделать наш вывод более эстетичным.
text
- имя, которое мы присваиваем адресу в памяти, где будет находиться наш текст. Это можно сравнить с обычным обьявлением переменной в любом ЯП, что имеет компилятор.
Регистры
Регистры - части процессора, которые хранят данные внутри себя.
В архитектуре x86_64 регистры хранят в себе 64 бита информации
Это значит, что каждый регистр способен хранить в себе значения в таком диапазоне:
signed: -9,223,372,036,854,775,807 - 9,223,372,036,854,775,807
unsigned: 1,844674407×10¹⁹
Вот список регистров процессора на архитектуре x86_64
Системные вызовы
Системный вызов или syscall
, как мы записали его в нашем коде - это событие, при котором программа обращается к ядру ОС.
Системные вызовы оличаются на разных ОС, причиной этого является то, что разные ОС могут использовать разные ядра.
Каждый системный вызов имеет ID
, что асоциирован с ним. Так-же системные вызовы могут получать на вход различные аргументы или, другими словами, список входной информации. Эти самые аргументы "кладутся" в регистры процессора. Эта таблица более наглядно может объяснить сей факт:
Из этого мы видим, что в rax
"кладется" ID
нашего системного вызова, а в определённые регистры - аргументы, которые мы передаём.
На этой странице Вы можете найти полный список системных вызовов Linux 64bit
sys_write
Как Вы уже могли понять, зайдя на страницу, что я указал выше, мы используем системный вызов sys_write
. Ему мы передаем такие аргументы:
rdi
- файловый дескриптор
rsi
- буфер с текстом
rdx
- длина строки
В rdi
мы "положили" значение 1
. Это значит что мы используем stdout
, стандартный системный вывод в терминал.
В rsi
лежит строка, которую мы привязали к имени text
В rdi
- полная длинна нашей строки.
sys_exit
В конце нашей программы мы видим ещё один системный вызов. На страничке выше, можно обнаружить, что его ID
- 60, а аргумент(который только один) - код ошибки. Мы передаём туда 0
, так-как это указывает на отсутсвие ошибок при исполнении и удачное завершение программы.
Секции
Все x86_64 ASM файлы имеют три секции:
- .data
- .bss
- .text
.data
- содержит данные, что должны быть объявлены в процессе компиляции.
.bss
- содержит области памяти, что выделяются для будущего использования
.text
- секция, в которой содержится сам код программы.
Лейблы
Лейблы(далее Label) - точка, что указывает на определённое место в коде и позволяет компилятору ориентироваться в нём.
В нашей программе имеется один Label - _start:
. Он присутсвует в каждой программе и является точкой, из которой начинается её исполнение. Без него программа не сможет работать и линкер будет выдавать ошибку. Мы дали этой метке значение global
это означает, что линкеру будет доступен адрес этой метки.
Проверяем работоспособность
Теперь осталось только проверить как работает наша программа.
nasm -f elf64 hello.asm -o hello.o
- компилирует программу
ld hello.o -o hello
- линкует её
На выходе получаем:
Всех благодарю за внимание, надеюсь Вам понравилось. Ждите продолжения!