Эта статья не претендует на звание учебника, охватывающего все стороны программирования на ассемблере в Linux. Я лишь хочу помочь людям, которые начинают делать первые шаги. Всё, что здесь будет описано, я буду описывать так, как понял это я. Надеюсь, что люди, которые найдут ошибку в моём описании, меня поправят.
Зачем все это? Литературы по ассемблеру под Linux как таковой нет. Руководства по разработке драйверов я не учитываю — до этого еще далеко. В интернете можно встретить лишь несколько статей, но там всё рассматривается поверхностно и заканчивается на примере вывода надписи «Hello, world!». На встречал интересное руководство по опкодам (опкоды – машинное представление команд ассемблера), но чтобы эти опкоды правильно писать — нужно что то знать :) Да, и перепись всего понятого — мне поможет лучше запомнить. Надеюсь, у читающего есть хотя бы туманное представление какого-либо языка программирования высокого уровня.
Инструментарий.
Чтобы писать программы на ассемблере не нужны тяжёлые среды разработки (вроде Eclipse). Если вы занимаетесь программированием, то, наверно, большинство необходимого у вас уже установлено:
NASM — свободный (GNU LGPL) кроссплатформенный Intel x86 ассемблер;
LD – GNU линкер – объединяет несколько объектных файлов в один исполняемый файл;
KHexEdit – редактор бинарных файлов;
strace — утилита, помогающая узнать какие системные вызовы использует процесс;
GCC – набор GNU компиляторов. Полезно дизассемблировать программы и изучать, какой код на ассемблере был сгенерирован в соответствии с исходным кодом на языке С;
GDB — Отладчик. Как им пользоваться, поясню позже;
VIM+GDB plugin — Текстовый редактор (или любой другой).
и, собственно SlackWare 10.2 и ядро 2.6.22
Операционная система не важна — во всех линуксах ядро одно и тоже. Принципиальная разница отсутствует.
Про синтаксис компилятора и описание архитектуры компьютера писать не буду. Много времени займет. Про архитектуру понаписано достаточно учебников. А про синтаксис можно посмотреть в приложении.
Стоит, наверно, начать с того, с чего начинают в любой книжке. Введите следующий код в файл, и сохраните под именем hello.asm
BITS32 ; Говорим компилятору, что код 32-битный
; В исполняемом файле может быть различное количество секций.
; Секции обычно выделяются по содержимому:
; .text — для кода,
; .data — для данных,
; .bss — для неинициализированных данных.
section .text ; Начало секции кода
global _start ; Метка _start должна быть глобальной,
; чтобы линкер смог её найти и сделать точкой входа в программу.
_start:
mov eax,4 ; системный вызов «write»
mov ebx,1 ; стандартный вывод
mov ecx,msg ; адрес сообщения
mov edx,[msg_size] ; длина
int 0x80 ; вызов прерывания
mov eax,1 ; системный вызов «exit»
xor ebx,ebx ; код выхода
int 0x80 ; вызов прерывания
section .data ; Начало секции данных
; Объявление переменной с сообщением
msg db 'Hello, YarLUG',0xA
; Объявление переменной с длинной сообщения
msg_size dd $-msg
Это первая наша программа. Она должна выводить на экран приветствие «Hello, YarLUG».
Для того, чтобы создать работающую программу нужно откомпилировать текст программы в объектный файл, а затем объектный файл скомпоновать в исполняемый.
$ nasm -f elf hello.asm
$ ld -o hello hello.o
Зачем так сложно? Представьте, у нас есть очень большой проект, состоящий из нескольких подпроектов. Каждый подпроект компилируется в отдельный объектный файл и затем все они компонуются в один большой исполняемый файл. Если вы изменяете один подпроект, то вам не нужно перекомпилировать все подпроекты, нужно всего лишь откомпилировать изменённый подпроект, а потом скомпоновать с остальными.
Давайте проверим работоспособность нашей первой программы:
$ ./example1
Если выводится строчка, то всё работает. Если нет, то проверьте сообщения компилятора и компоновщика при создании исполняемого файла.
Давайте разберём текст программы.
Вероятно, самое непонятное — это откуда я взял эти циферки:
mov eax,4
mov ebx,1
mov ecx,msg
mov edx,msg_size
int 0x80
По пунктам:
1) mov eax,4
Помещаю в регистр еах номер системного вызова. Номера системных вызовов можно найти исходных текстах ядра. В файле /usr/src/linux/include/asm-i386/unistd.h описаны константы номеров системных вызовов. Номер 4 описан так: #define __NR_write 4
2) mov ebx,1
Записываем файловый дескриптор стандартного вывода в еbх. Всегда существует 3 стандартных дескриптора:
0 – стандартный ввод 1 – стандартный вывод 2 – вывод ошибкок.
3) mov ecx,msg
Помещаем адрес сообщения в регистр есх.
4) mov edx,[msg_size]
Длина сообщения передаётся в регистре edx. При описании переменной msg_size мы использовали символ $. Он означает текущее смещение в программе, из него мы вычитаем смещение переменной msg и получается длина сообщения в байтах. Длину можно указать и константой, но ошибившись в числе, можно придти к нежелательным последствиям. К примеру, замените в коде msg_size на какое нибудь число, например, 4, и заново пересоберите программу. В итоге на екране получим строку из четырёх символов: Hell. 5) int 0×80 Системный вызов операционной системы. Это аналог DOS'овскому int 21h.
А теперь, самое важное — откуда я вообще узнал, что и как заносится в регистры? Наберите в консоли
$ man 2 write
Второй раздел справочника (man 2) посвящён системным вызовам ОС. Строчка, которая сразу же бросается в глаза:
Это описание вызова на языке C. Чтобы понять, куда, что помещать нужно знать способ вызова (call convention). В регистре eax передаётся номер системного вызова, параметры передаются в регистрах, в порядке очерёдности, ebx, ecx, edx.
Следующий кусок кода:
mov eax,1
xor ebx,ebx
int 0x80
По номеру системного вызова в регистре eax понимаем, что это выход из программы – системный вызов exit. XOR – операция «исключающее ИЛИ». Этой операцией в ebx заносится 0. Для простоты понимания можно записать эту строчку так:
mov ebx,0
или так:
sub ebx,ebx
По описанию функции (man 2 exit) видно, что она принимает один параметр – код выхода из программы.
В описании значения переменной msg в конце написано 0xA – это переход на следующую строку. Попробуйте это убрать и посмотреть, что получиться.
Здесь можно закончить, и направить вас штудировать учебники, техническую документацию, как это делают некоторые авторы. Но я продолжу
Сейчас попробуем записать строчку в файл. Что для этого нужно? Нужен сам файл, но, его отдельно создавать не будем — заставим код это сделать. Потом его нужно открыть, записать, закрыть, нельзя так же забывать выйти из программы. Используемые функции:
int open(const char *pathname, int flags, mode_t mode);
BITS32
section .text
global _start
_start:
; open
mov eax,5
mov ebx,path_file
mov ecx,0001002
mov edx,7666
int 0x80
mov [fd],eax
; write
mov eax,4
mov ebx,[fd]
mov ecx,msg
mov edx,[msg_size]
int 0x80
; close
mov eax,6
mov ebx,[fd]
int 0x80
; exit
mov eax,1
xor ebx,ebx
int 0x80
section .data
msg db 'Hello, YarLUG'
msg_size dd $-msg
path_file db 'file.txt',0
section .bss
fd resd 1
Не забываем, что после знака точка с запятой идет строка комментария и компилятор её пропускает.
С начала, вызывается функция open:
mov eax,5
Системный вызов open под номером 5.
mov ebx,path_file
Передаем адрес пути файла.
mov ecx,0001002
В ecx передаются битовые флаги. Здесь взведены два флага: 0001000 – если файла не существует, то создать новый; 0000002 – открыть файл в режиме чтение/запись.
mov edx,7666
В регистре edx передаются права для создаваемого файла. Подробнее можно узнать в станице man chmod.
mov dword [fd],eax
В регистре eax системный вызов open возвращает файловый дескриптор. Поскольку еах нам еще потребуется, то дескриптор необходимо сохранить в какую-нибудь промежуточную переменную. В программе для этого используется переменная fd. Дальше код достаточно простой, а местами знакомый.
Программа компилируется и компонуется аналогично предыдущей. Стоит лишь сказать о мощной утилите make, которая очень облегчит нам жизнь. Если в каталоге, где вы сохранили программу (я назвал её hellofile.asm), создать файл с именем Makefile и следующим содержимым:
build:
То после любого изменения исходного текста нам остаётся ввести
$ make build
и получим готовый исполняемый файл, а утилита make вызовет всё за нас. Если ввести
$ make clean
то в каталоге останется только файл hellofile.asm, а всё лишнее будет удалено.
За кадром осталось ещё много всего интересного. Но об этом я постараюсь изложить в следующих своих записках.
Полезные ссылки:
— ассемблер NASM;
~espensa/khexedit/ – KhexEdit;
— strace;
– коллекция GNU компиляторов
— отладчик GDB;
— русское руководство по GDB;
— много статей по программированию на ассемблепре.
Комментарии (0)
RSS свернуть / развернутьТолько зарегистрированные и авторизованные пользователи могут оставлять комментарии.