Автор: lonesome TSH/Digital Daemons
Дата: .01.2003
Раздел: Низкоуровневое программирование в Linux
А теперь перейдем к первой программе:
;Листинг 01 - минимальная программа для Linux ;Приемы оптимизации не применяются для упрощения кода global _start _start: mov eax, 4 mov ebx, 1 mov ecx, msg mov edx, msglen int 0x80 mov eax, 1 mov ebx, 0 int 0x80 section .data msg: db "Linux rulez 4ever",0x0A,0 msglen equ $-msg
Рассмотрим программу поподробнее:
Знак ';' (точка с запятой) означает комментарий - все что находится правее
этого символа ассемблер игнорирует
global _start - директива global указывает ассемблеру сделать глобальной
(экспортируемой) метку "_start". Подробнее об экспортируемых метках см. ниже
_start: - объявление метки с именем "_start". Фактически это означает, что
в программе будет определена константа _start, которая будет иметь значение равное
адресу, по которому объявлена данная метка
Предыдущие три строчки были
директивами ассемблера, т.е. не являлись командами процессора, и не
преобразовывались при компиляции в машинный код. Следущие строчки являются именно
командами процессора:
mov eax, 4 - машинная команда MOV копирует данные из второго операнда в первый. В
данном случае первый операнд - это регистр EAX (подробнее о регистрах - в
следующем уроке). Второй операнд - это константа (определенное в момент
компилирования и неизменяемое значение). Результатом выполнения этой команды будет то,
что в регистре EAX окажется число 4. Операнды команды разделяются запятой
mov ebx, 1 - то же самое, но помещается единица в регистр EBX
mov ecx, msg - на первый взгляд эта команда отличается от двух предыдущих, но она
тоже выполняет перемещение данных, только в данном случае используется константа
msg, которая определена ниже и регистр ECX
mov edx, msglen - содержимое определенной ниже константы msglen помещается
в регистр EDX
int 0x80 - команда int процессора вызывает т.н. программное
прерывание. Грубо говоря - программное прерывание - это команда перехода выполнения
программы в определенный операционной системой обработчик прерывания. Всего
процессор поддерживает 256 обработчиков для 256 прерываний и операнд этой команды
указывает на обработчик какого прерывания нужно передать выполнение программы.
0x80 - 80 в шестнадцатеричной системе счисления (на шестнадцатеричную систему
указывают первые два символа: 0x). В случае ОС Linux, прерывание с номером 0x80
является системным вызовом - передачей управления ядру системы с целью выполнения
каких-либо действий. В регистре EAX должен находится номер системного вызова, в
зависимости от которого ядро системы будет выполнять какие-либо действия. В данном случае
мы помещаем в EAX число 4, т.е. указываем ядру выполнить системный вызов номер 4 (write).
Этот системный вызов используется для записи данных в файл или на консоль (которая тоже в
принципе представлена файлом). В EBX мы поместили дескриптор(идентификатор)
консоли - stdout. В ECX и EDX содержатся адрес начала сообщения (адрес первого
байта) и длина сообщения в байтах. Т.е этот системный вызов должен выполнить вывод
строчки, находящейся по адресу msg, на консоль.
mov eax, 1 - в EAX помещается 1 - номер системного вызова "exit"
mov ebx, 0 - в EBX помещается 0 - параметр вызова "exit" означает код с
которым завершится выполнение программы
int 0x80 - системный вызов. После системного вызова "exit" выполнение программы
завершается
section .data Директива ассемблера section определяет следующие данные, как
находящиеся в указанном в качестве параметра сегменте. Сегмент .text - сегмент
кода, в котором должен находиться исполняемый код программы и чтение из которого
запрещено. Сегмент .data - сегмент данных, в котором должны находиться
данные программы. Выполнение (передача управления) на сегмент данных запрещена. Поскольку
следующие строчки нашей программы - данные, то мы определяем сегмент данных.
msg: db "Linux rulez 4ever",0x0A,0 - вначале мы определяем метку msg
(напоминаю, что метка - текущий адрес), и сразу после нее - строчку, т.е. метка
msg будет указывать на первый байт строки. Директива db указывает
ассемблеру поместить в данном месте байт данных. Несколько байт могут быть разделены
запятой. Если нужно поместить символ, то запись 'X' означает код символа 'X', а
форма записи "abcde" эквивалентна 'a', 'b', 'c', 'd', 'e'. Код символа 0x0A
означает переход строки, а нулевой байт является концом строки. Поскольку вызов write
знает точно, сколько байт нужно выводить, то нулевой байт в конце строки необязателен, но
мы его все равно поставим :). Он необходим для программ, взаимодействующих с GLIBC, т.к.
функции стандартной библиотеки Си вычисляют длину строки, как расстояние между первым
байтом и ближайшим нулевым байтом.
msglen equ $-msg - директива equ определяет константу, расположенную слева
от директивы и присваивает ей значение, находящееся справа. Символ $ является
специальной константой ассемблера, значение которой всегда равно адресу по которому она
находится, т.е в данном случае выражение $ - msg как раз будет равно длине строки,
т.к. в данном месте программы $ равно адресу следующего за строкой байта.
Результат этой директивы - мы определили константу msglen, значение которой равно
длине определенной выше строки.
Результат работы ассемблера - это объектный файл. Так как мы компилируем программу под
Linux, то нам необходим объектный файл формата ELF (Executable and Linkable Format).
Получить его можно следующей командой:
nasm -felf prog01.asm -o prog01.o
Полученный объектный файл необходимо скомпоновать. Такое название это действие
получило потому, что с его помощью можно компоновать несколько объектных файлов в один
исполняемый. Если в каком-нибудь из объектных файлов существуют экспортируемые функции
или переменные, то они доступны всем компонуемым объектным файлам. Существует функция,
которая должна быть определена всегда - это точка входа - "_start". С этой функции
начинается выполнение программы.
Компоновка:
ld prog01.o -o prog01
Поскольку мы не использователи никаких библиотек, а взаимодействовали напрямую с ядром
системы, то при компоновке мы указываем только наш объектный файл.
После выполнения этой команды файл "prog01" будет исполняемым файлом нашей
программы.