Давно хотів познайомитись, але все стримувало те що в дитинстві я почав з синтаксису Intel, і хотів програмувати тут теж з синтаксисом Intel. Пройшло багато років, я забув, тому давайте вивчимо AT & T синтаксис. Потім при потребі можна перевчитись.
Ну й крім того, я знайшов книжку Programming from the Ground Up, в якій все дуже зрозуміло (як для дітей) пояснюють.
Ну дууууже коротка теорія
Перше що потрібно знати – x86 – це архітектура Фон Неймана, тобто код програми і дані зберігаються в одній і тій же пам’яті, і для комп’ютера виглядають однаково. (Щоправда операційні системи можуть обмежувати виконання лише областями з кодом, для того щоб запобігти можливим помилкам, якими можуть скористатись хитрі хакери)
Пам’ять – це довгий список байтів (можуть містити цілі числа від 0 до 255), кожен з яких має свою адресу. Адреса – це просто номер байта. Щоправда ми працюватимемо переважно не з байтами, а зі словами (в 32-х розрядній архітектурі це 4 байти, які можуть приймати значення від 0 до 4294967295). Тому що регістри процесора якраз стільки вміщають. Адреси – це теж слова (4 байти).
Найпростіша програма
Надрукуємо “Привіт, світе!”? О, ні, на ассемблері це надто складно. Давайте напишемо програму яка завершує роботу. Ага. В Unix, програма яка завершує свою роботу має повернути статус код. Статус код останньої виконаної команди можна побачити у спеціальній змінній командної оболонки: echo $?
_start
– в лінкера за замовчуванням позначає точку входу в програму. Тобто адресу з якої почнеться її виконання. Можна замінити її наприклад на main
, але тоді лінкер, якщо йому не вказати опцію -e main
скаже:
ld: warning: cannot find entry symbol _start; defaulting to 0000000008048054
Символ відсотка позначає регістр процесора (наприклад %eax
), долар позначає константу (наприклад $1
, $0x80
(= 128 в шістнадцятковому записі) ).
Переривання – це місце в коді де контроль передається в якесь інше місце (в ядро). Коли ядро виконає своє переривання – воно поверне контроль нам, і програма продовжить виконання (стан регістрів може змінитись). Щоправда після системного виклику exit
контроль нам ніколи не повернуть, але так й було задумано.
Щоб перевірити нашу програму, потрібно виконати команди:
as exit.s -o exit.o
ld exit.o -o exit
Перша створює об’єктний файл. Об’єктний файл – це вже код машинною мовою, але ще не до кінця зібраний. Потрібно передати його лінкеру – програмі яка вміє збирати багато об’єктних файлів в одну програму, та додавати інформацію для того щоб ядро знало як працювати з програмою.
Якщо все пройшло без помилок – можна перевірити роботу:
./exit
echo $?
Також можна спробувати замінити код повернення в %ebx
на якісь інші і подивитись результат.
Опис інструкцій
mov
– це інструкція переміщення, яка має два операнди – звідки й куди.
Суфікс l
означає розмір даних (long = 4 байти). Інші можливі префікси: b
byte = (8 біт); s
short = (2 байти (16 біт).
Багато інших інструкцій мають два операнди, як наприклад addl
(додавання), subl
(віднімання). Але, наприклад, інструкція div
приймає єдиний аргумент, бере вміст регістрів %edx
та %eax
як велике 64-х розрядне число (якщо треба менше, %edx
має містити нуль) ділить на переданий аргумент а тоді результат поміщує в %eax
, а остачу від ділення в %ebx
.
Регістри загального призначення, які можна використовувати як аргументи команд переміщення та арифметичних – це %eax, %ebx, %ecx, %edx, %edi, %esi
.
Контроль ходу виконання
Давайте напишемо наступну програму, яка не просто даватиме наперед заданий статус код, а даватиме максимальне число серед заданих. Нехай ці числа зберігатимуться за адресою, яку ми позначимо data_items
, і останнім числом буде нуль, щоб ми знали коли припинити пошук.
Виконання має повернути 89 (найбільше в списку). Це вже набагато цікавіша програма.
По-перше, ми маємо дані. Окрім типу .long
(4 байти), ми можемо використовувати .byte
, .int
(2 байти), та .ascii
– рядок символів, наприклад .ascii "Hello, world!\n"
.
По-друге, цікава інструкція movl data_items(,%edi,4), %eax
. Лівий її операнд означає взяти дані з адреси data_items
, але зсунутись на %edi
елементів розміру 4. Тобто це як доступ до елементу масиву. Синтаксис паскудний, в Intel здається писали б data_items + %edi * 4, що виглядає зрозуміліше, але маємо що маємо.
Також, якщо ми хочемо отримати значення за адресою – теж використовуємо дужки. Наприклад – %esp
– містить адресу вершини стеку. movl %esp, %eax
– збереже цю адресу в %eax
, а от movl (%esp), %eax
– значення з вершини стеку.
Наступна незнайома інструкція – cmpl
. Вона порівнює два чотирибайтові числа (суфікс бачите?), а результат порівняння записує в певні біти регістру %eflags
. Про нього я дуже давно написав непогану статтю на CybWiki а потім на вікіпедії, тому в деталі не вдаватимусь.
Після порівняння зазвичай йде інструкція зробити стрибок на певну адресу, якщо виконується умова порівняння. Існують такі інструкції стрибків з умовою: je
– стрибати якщо рівні, jg
– якщо друге значення було більше за перше, jge
– більше або рівне, jl
– менше, jle
– менше рівне.
incl
– збільшує аргумент на одиничку. incl %edi
– це те саме що й addl $1, %edi
, тільки коротше й швидше працює.
Далі буде
Вже скоро ранок, а hello world ми ще так і не написали. Треба розібратись з написанням і викликом функцій, викликом функцій роботи з файлами, щоб можна було писати в STDOUT. Але це вже завтра, бо це вже й так надто червоноока публікація.
Хоча в книжці там ще йде мова про рекурсію, обробку помилок, бібліотеки функцій, та їх створення, менеджер пам’яті і інші цікаві речі.
Filed under:
Кодерство Tagged:
linux