Ос

Основи у x86-64 Assembly

1 вправа

Про концепцію Основи

Коментарі

Трек x86-64 на Exercism використовує NASM (The Netwide Assembler) як асемблер. У NASM коментарі починаються з крапки з комою (;).

Коментарі можна розміщувати будь-де в програмі, а все, що йде після ;, асемблер ігнорує.

Константи

Константу часу асемблювання можна визначити в NASM за допомогою equ. Наприклад, ось так визначається константа з назвою UNIVERSE і значенням 42:

UNIVERSE equ 42

Константи обчислюються один раз, під час визначення, і не можуть бути перевизначені пізніше.

Регістри загального призначення (GPRs)

В асемблері замість змінних для зберігання значень ми зазвичай використовуємо регістри. Регістр процесора - це ділянка швидкої памʼяті всередині процесора компʼютера. Більшість обчислень виконується в регістрах процесора.

Частину регістрів використовують для найрізноманітніших обчислень, і їх називають регістрами загального призначення (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.

Note

Регістри зазвичай зберігають у стеку за допомогою інструкції push. А потім відновлюють перед завершенням функції за допомогою інструкції pop.

Інструкції

Інструкції - це елементарні обчислення, які вміє виконувати процесор. Зазвичай вони мають такий вигляд:

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 вихідного файлу.

Оголошення функції складається з:

  1. Мітки з назвою функції, після якої стоїть :.
  2. Інструкцій, які визначають функцію.
  3. Інструкції повернення ret.

Існують спеціальні угоди про те, як аргументи передаються у функцію та повертаються з неї. Дві основні угоди про виклик - System V AMD64 ABI і Microsoft x64.

Цей трек використовує угоду про виклик 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
Редагувати через GitHub Посилання відкривається в новому вікні або вкладці

Вивчити концепцію Основи