Первая программа на Linux

Последнее обновление: 12.10.2023

NASM является кроссплатформенным ассемблером, который доступен в том числе и на ОС на базе Linux. этой статье мы рассмотрим начало работы с ассемблером NASM на Linux. В качестве целевой ОС будем использовать Ubuntu.

Установка

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

sudo apt update

Затем собственно установим пакет nasm с помощью команды:

sudo apt -y install nasm

После установки пакета мы можем проверить установленную версию NASM с помощью команды nasm -v:

root@Eugene:~# nasm -v
NASM version 2.15.05
root@Eugene:~#

Стоит отметить, что таким образом не всегда доступна самая последняя версия ассемблера. Например, на момент написания статьи последней является 2.16.01. И в этом случае мы можем при необходимости мы можем загрузить самую последнюю версию с официального сайта со страницы https://www.nasm.us/pub/nasm/releasebuilds/2.16.01/linux/

Начало работы с NASM

Определим в файловой системе каталог для файлов с исходным кодом и создадим в нем следующий файл hello.asm:

global _start           ; делаем метку метку _start видимой извне

section .text           ; объявление секции кода
_start:                 ; объявление метки _start - точки входа в программу
    mov rax, 60         ; 60 - номер системного вызова exit 
    mov rdi, 22         ; произвольный код возврата - 22 
    syscall             ; выполняем системный вызов exit

Рассмотрим поэтапно данный код. Вначале идет директива global:

global _start

Данная директива делает видимой извне определенную метку программы. В данном случае метку _start, которая является точкой входа в программу. Благодаря этому компоновщик при компоновке программы в исполняемый файл сможет увидеть данную метку.

Затем идет секция кода программы, которая и определяет выполняемые программой действия. Для определения секции применяется директива section, после которой указывается имя секции. Причем секция кода программы должна называться .text.

section .text 

Далее собственно идет код программы. И он начинается с определения метки _start, на которую собственно и проецируется программа. Сама по себе метка представляет произвольное название, после которого идет двоеточие. После двоеточия могут идти инструкции программы или определения данных.

Метка _start выступает в качестве точки входа в программу. Название подобной метки произвольное, но обычно это или _start или main

Наша программа не производит какой-то феноменальной работы. Все что она делает - это завершается. Да, чтобы программе завершиться, ей надо произвести некоторую работу. А именно нам надо сказать операционной системе, чтобы она завершила программу. Для взаимодействия с операционной системе предназначены системные вызовы. Каждый системный вызов имеет определенный номер и может принимать некоторые данные - параметры. Так, чтобы указать операционной системе, что мы хотим завершить программу, нам надо выполнить системный вызов с номером 60 (который еще называется "exit"). Номер системного вызова передается в регистр rax:

mov rax, 60

То есть в данном случае мы выполняем инструкцию mov, которая помещает в первый операнд - регистр rax значение из второго операнда - число 60.

Системный вызов exit может принимать один параметр - статусный код возврата - некоторое число, которое указывает на статус выполнения программы (успешно завершилась программа или нет и если нет, то почему). В реальности это может быть произвольное число. Первый параметр системных вызовов всегда помещается в регистр rdi. Поэтому следующая инструкция mov помещает в регистр rdi число 22 (число выбрано произвольным образом):

mov rdi, 22

И в конце для выполнения системного вызова выполняется инструкция syscall

syscall

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

global _start  ; делаем метку метку _start видимой извне - это текст комментария

Компиляция

Наша программа готова. Теперь скомпилируем ее. Для компиляции перейдем в терминале к месту расположения файла и выполним следующую команду

nasm -f elf64 hello.asm -o hello.o

Здесь ассемблеру передается файл "hello.asm" и с помощью опции -f указывается формат файла, в который мы хотим скомпилировать код. Для 64-разрядных систем Linux формат файла - elf64.

Опция -o hello.o указывает на имя скомпилированного файла. В результате выполнения этой команды будет создан объектный файл hello.o

Далее скомпонуем объектный файл в исполняемый файл. Для этого выполним команду

ld -o hello hello.o

Здесь программе компоновщика (линкера) ld передается набор опция -o, которая указывает, что надо скомпоновать объектный файл hello.o в исполняемый файл hello

. В результате будет создан исполныемый файл hello. Запустим его:

./hello

Консоль ничего не отобразит, поскольку все, что делает наша программа - это завершается. Однако в нашей программе также устанавливается статусный код возврата - число 22. Получим его, выполнив команду:

echo $?

Компиляция программы и ее запуск полностью:

root@Eugene:~/asm# nasm -f elf64 hello.asm -o hello.o
root@Eugene:~/asm# ld -o hello hello.o
root@Eugene:~/asm# ./hello
root@Eugene:~/asm# echo $?
22
root@Eugene:~/asm#

Создание первой программы

Теперь создадим более осмысленную программу, которая будет выводить строку на консоль, и для этого изменим файл hello.asm следующим образом:

global _start           ; делаем метку метку _start видимой извне

section .data   ; секция данных
message: db "Hello METANIT.COM!",10  ; строка для вывода на консоль

section .text           ; объявление секции кода
_start:                 ; объявление метки _start - точки входа в программу
    mov rax, 1          ; 1 - номер системного вызова функции write
    mov rdi, 1          ; 1 - дескриптор файла стандартного вызова stdout
    mov rsi, message    ; адрес строки для вывод
    mov rdx, 19         ; количество байтов
    syscall             ; выполняем системный вызов write

    mov rax, 60         ; 60 - номер системного вызова exit
    syscall             ; выполняем системный вызов exit

После директивы global, которая делает видимой извне метку _start, идет секция данных - .data:

section .data   ; секция данных
message: db "Hello METANIT.COM!",10  ; строка для вывода на консоль

Секция данных также определяется с помощью директивы section. В секции .data определена метка message, на которую проецируется строка. После метки указывается тип данных. Строка в ассемблере - это просто набор байтов, поэтому имеет тип db. Затем в кавычках определяется собственно выводимая строка - "Hello METANIT.COM!",10. Обратите внимание на 10 после строки - это код символа перевода строки. То есть при выводе мы ожидаем, что будет происходить перевод на другую строку.

Для вывода этой строки на консоль нам надо выполнить системный вызов write, который имеет номер 1. Для этого в регистр rax помещаем номер системного вызова - номер 1:

mov rax, 1

Для вывода на консоль нам надо указать в регистре rdi дескриптор стандартного вывода - по умолчанию это число 1

mov rdi, 1

В регистр rsi надо загрузить адрес выводимой строки:

mov rsi, message

Кроме того, нам надо указать в регистре rdx сколько байтов строки мы хотим выводить. Каждый символ строки ASCII занимает 1 байт. В нашей строке 19 символов (учитывая символ с числовым кодом 10 в конце строки), поэтому в rdx помещаем число 19:

mov rdx, 19 

Для выполнения системного вызова выполняем инструкцию syscall

Остальные инструкции программы - это выполнение системного вызова завершения программы, который был рассмотрен ранее.

Повторно скомпилируем программу и запустим ее на выполнение:

root@Eugene:~/asm# nasm -f elf64 hello.asm -o hello.o
root@Eugene:~/asm# ld -o hello hello.o
root@Eugene:~/asm# ./hello
Hello METANIT.COM!
root@Eugene:~/asm#
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850