Давно хотів познайомитись, але все стримувало те що в дитинстві я почав з синтаксису 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