Rambler's
Top100

Ассемблер для Linux для начинающих: первая программа

Автор: lonesome TSH/Digital Daemons
Дата: .01.2003
Раздел: Низкоуровневое программирование в Linux

Ассемблер, который я буду использовать - NASM (Netwide Assembler, nasm.2y.net). Этот выбор объясняется тем, что:
Во первых, он мультиплатформенный, т.е. для портирования программы на разные ОС достаточно только изменить код взаимодействия с системой, а всю программу переписывать не нужно
Во вторых, он, его синтаксис непротиворечив и недвусмысленен, в чем схож с AT&T ассемблером для UNIX
В третьих, он имеет привычный Intel-синтаксис, т.е. программист на MASM или TASM сможет без особых проблем перейти на NASM

А теперь перейдем к первой программе:


;Листинг 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" будет исполняемым файлом нашей программы.





Rambler's Top100