Трек x86-64 в Exercism використовує NASM (The Netwide Assembler) як асемблер.
У NASM коментарі позначаються крапкою з комою (;).
Коментарі можна розміщувати будь-де в програмі, і все, що йде після ;, асемблер ігнорує.
Константу, що визначається під час асемблювання, можна задати в NASM за допомогою equ.
Наприклад, ось так визначається константа з назвою UNIVERSE зі значенням 42:
UNIVERSE equ 42
Константи обчислюються один раз, під час визначення, і їх не можна перевизначити пізніше.
В асемблері замість змінних для зберігання значень ми зазвичай використовуємо регістри. Регістр процесора - це ділянка швидкої памʼяті всередині процесора компʼютера. Більшість обчислень відбувається в регістрах процесора.
Деякі з регістрів використовуються для різноманітних обчислень і називаються регістрами загального призначення (GPRs). Інші мають спеціальне або вузьке призначення.
У x86-64 є 16 64-бітних регістрів загального призначення (GPRs), до яких можна також звертатися як до 32-бітних, 16-бітних або 8-бітних.
Ці регістри загального призначення описано нижче, де n у rn набуває значень від 8 до 15: r8, r9, r10, r11, r12, r13, r14 і r15.
| 64-бітний | 32-бітний | 16-бітний | 8-бітний |
|---|---|---|---|
| rax | eax | ax | ah/al |
| rbx | ebx | bx | bh/bl |
| rcx | ecx | cx | ch/cl |
| rdx | edx | dx | dh/dl |
| rsi | esi | si | sil |
| rdi | edi | di | dil |
| rbp | ebp | bp | bpl |
| rsp | esp | sp | spl |
| rn | rnd | rnw | rnb |
Коли використовується менше ніж 64 біти, доступ зазвичай відбувається до молодшої частини регістра.
Виняток із цього правила становлять ah, bh, ch і dh, які дають доступ до старших 8 бітів 16-бітної частини регістра.
Ілюстрація того, як відбувається доступ до бітів регістра rax:
+--------+---------------------------------------+
| 64-bit | rax |
+--------+-------------------+-------------------+
| 32-bit | | eax |
+--------+-------------------+---------+---------+
| 16-bit | | ax |
+--------+-----------------------------+----+----+
| 8-bit | | ah | al |
+--------+-----------------------------+----+----+
Деякі з цих регістрів потрібно зберігати під час викликів функцій: rbp, rsp, rbx, r12, r13, r14 і r15.
Якщо їх не зберегти, це може призвести до помилки або до невизначеної поведінки.
Решту зберігати не потрібно, і їх можна використовувати вільно: rax, rcx, rdx, rdi, rsi, r8, r9, r10 і r11.
Пізніше в силабусі ми навчимося зберігати регістри.
А поки що використовуйте лише другий тип регістрів: rax, rcx, rdx, rdi, rsi, r8, r9, r10 і r11.
Інструкції - це частини обчислень, які може виконувати процесор. Зазвичай вони мають такий вигляд:
name destination, source
Отже, спочатку йде назва інструкції, потім щонайменше один пробільний символ, далі операнд призначення, кома (,) і нарешті операнд джерела.
Зазвичай інструкція не змінює операнд джерела, лише операнд призначення.
Обидва операнди повинні мати однаковий розмір, якщо не зазначено інше. Наприклад, якщо операнд джерела має 16 бітів, то й операнд призначення повинен мати 16 бітів.
Наприклад, щоб зберегти значення в регістрі, можна використати інструкцію mov:
mov rax, rdx ; rax = rdx
; mov is the name, rax is the destination operand and rdx is the source operand
Наведений фрагмент копіює вміст усіх 64 бітів rdx, операнда джерела, до всіх 64 бітів rax, який є операндом призначення цієї інструкції.
У більшості інструкцій можна використовувати сталі цілі числа як операнди джерела (але не як операнди призначення):
mov rcx, 42 ; rcx = 42
; rcx is the destination operand and 42 is the source operand
Зауважмо, що запис у 32-бітний регістр також очищає старші біти, тож mov eax, 42 рівнозначно mov rax, 42.
Для 16-бітних і 8-бітних регістрів це не так.
Для арифметичних операцій додавання, віднімання та множення можна використовувати інструкції add, sub та imul:
add rax, rsi ; rax = rax + rsi
imul rax, rdi ; rax = rax * rdi
sub rax, r8 ; rax = rax - r8
Інструкції організовано у функції. Усі функції розміщуються в секції section .text вихідного файлу.
Оголошення функції складається з:
:.ret.Цей трек використовує угоду про виклики System V AMD64 ABI, і перші шість цілочисельних аргументів передаються у функцію в регістрах.
Вони передаються в такому порядку: rdi, rsi, rdx, rcx, r8 і r9.
Цілочисельне значення повертається з функції в регістрі rax.
Щоб викликати функцію, ми використовуємо інструкцію call.
Функцію можна викликати будь-де в тому самому вихідному файлі.
Щоб зробити функцію видимою для інших вихідних файлів, потрібно використати директиву global.
Наприклад, ось так оголошується функція sum:
section .text ; functions are placed here
global sum ; sum is visible to other source files
sum:
; first argument is passed in rdi
; second argument is passed in rsi
; return value is placed in rax
mov rax, rdi ; rax is now equal to rdi
add rax, rsi ; rax = rax + rsi
ret ; function return
А ось так викликається наша функція sum з аргументами 3 і 5:
mov rdi, 3 ; First argument in rdi
mov rsi, 5 ; Second argument in rsi
call sum
; The rax register now contains the value 8 (3 + 5), after sum returns
У цій вправі ми напишемо трохи коду, який допоможе приготувати чудову лазанью з улюбленої кулінарної книги.
На нас чекають чотири завдання, і всі вони повʼязані з часом, витраченим на приготування лазаньї.
Це інструкції, згадані в цій концепції:
| Інструкція | Опис |
|---|---|
| mov a, b | копіює вміст з b в a |
| add a, b | a = a + b |
| sub a, b | a = a - b |
| imul a, b | a = a * b |
| call a | викликає функцію a |
| ret | повертається з функції |
Визначте функцію expected_minutes_in_oven, яка не приймає жодних параметрів і повертає, скільки хвилин лазанья має провести в духовці.
Згідно з кулінарною книгою, очікуваний час у духовці становить 40 хвилин:
expected_minutes_in_oven();
// => 40
Визначте функцію remaining_minutes_in_oven, яка приймає як параметр фактичну кількість хвилин, які лазанья вже провела в духовці, і повертає, скільки хвилин їй ще залишилося там провести, виходячи з очікуваного часу в духовці з попереднього завдання.
remaining_minutes_in_oven(30);
// => 10
Визначте функцію preparation_time_in_minutes, яка приймає як параметр кількість шарів, доданих до лазаньї, і повертає, скільки хвилин ми витратили на підготовку лазаньї, якщо на кожен шар іде 2 хвилини.
preparation_time_in_minutes(2);
// => 4
Визначте функцію elapsed_time_in_minutes, яка приймає два параметри в такому порядку:
Функція має повернути, скільки хвилин ми витратили на приготування лазаньї. Це сума часу підготовки в хвилинах і часу в хвилинах, який лазанья на цей момент провела в духовці.
elapsed_time_in_minutes(3, 20);
// => 26
Зареєструйтеся на Exercism, щоб вивчати й опановувати x86-64 Assembly, а також 22 концепції130 вправ та справжнє наставництво від людей, і все це безкоштовно.