Az Exercism x86-64 kurzusa az NASM-t (The Netwide Assembler) használja assemblerként.
A NASM-ban a megjegyzéseket pontosvessző (;) előzi meg.
A megjegyzések a program bármely pontján szerepelhetnek, és mindent, ami egy ; után következik, figyelmen kívül hagy az assembler.
A NASM-ban az equ direktívával definiálhatunk assembler-időben kiértékelt konstanst.
Ez például egy UNIVERSE nevű konstanst definiál 42 értékkel:
UNIVERSE equ 42
A konstansok egyszer, a definiálásukkor értékelődnek ki, és később nem definiálhatók újra.
Assemblyben változók helyett jellemzően regisztereket használunk értékek tárolására. A CPU-regiszter egy gyors memóriaterület a számítógép processzorán belül. A számítások többsége a CPU regisztereiben zajlik.
Egyes regiszterek sokféle különböző számításhoz használhatók, ezeket általános célú regisztereknek (GPR) nevezzük. Másoknak különleges vagy kijelölt szerepük van.
Az x86-64-ben 16 darab 64 bites általános célú regiszter (GPR) van, amelyek 32, 16 vagy 8 bites formában is elérhetők.
A GPR-eket az alábbi táblázat írja le, ahol az rn-ben az n 8 és 15 között van: r8, r9, r10, r11, r12, r13, r14 és r15.
| 64-bit | 32-bit | 16-bit | 8-bit |
|---|---|---|---|
| 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 |
Ha 64 bitnél kevesebbet használunk, az elért bitek általában a regiszter alsó részéből valók.
Kivétel ez alól az ah, bh, ch és dh, amelyek a regiszter 16 bites részének felső 8 bitjét érik el.
Ábra arról, hogyan érhetők el a bitek a rax regiszter esetén:
+--------+---------------------------------------+
| 64-bit | rax |
+--------+-------------------+-------------------+
| 32-bit | | eax |
+--------+-------------------+---------+---------+
| 16-bit | | ax |
+--------+-----------------------------+----+----+
| 8-bit | | ah | al |
+--------+-----------------------------+----+----+
Ezek közül néhány regisztert meg kell őrizni a függvényhívásokon keresztül: rbp, rsp, rbx, r12, r13, r14 és r15.
Ha nem őrizzük meg őket, az hibához vagy nem definiált viselkedéshez vezethet.
A többieket nem kell megőrizni, és szabadon használhatók: rax, rcx, rdx, rdi, rsi, r8, r9, r10 és r11.
A regisztereket általában a veremre mentve őrizzük meg egy push utasítással.
Ezután a függvény vége előtt egy pop utasítással állítjuk vissza őket.
Az utasítások olyan számítások, amelyeket a CPU végre tud hajtani. Általában a következő formájuk van:
name destination, source
Az utasítás neve áll az első helyen, majd legalább egy szóköz, utána a céloperandus, egy vessző (,) és végül a forrásoperandus.
Az utasítás általában nem módosítja a forrásoperandust, csak a céloperandust.
Mindkét operandusnak ugyanakkora méretűnek kell lennie, hacsak nincs másképp jelezve. Például ha a forrásoperandus 16 bites, akkor a céloperandusnak is 16 bitesnek kell lennie.
Ha például értéket akarunk tárolni egy regiszterben, használhatjuk a mov utasítást:
mov rax, rdx ; rax = rdx
; mov is the name, rax is the destination operand and rdx is the source operand
A fenti kódrészlet a rdx forrásoperandus mind a 64 bitjének tartalmát a rax mind a 64 bitjébe másolja, amely az utasítás céloperandusa.
A legtöbb utasításban használhatunk konstans egész számokat forrásoperandusként (de céloperandusként nem):
mov rcx, 42 ; rcx = 42
; rcx is the destination operand and 42 is the source operand
Vegyük észre, hogy egy 32 bites regiszterbe írás a felső biteket is törli, tehát a mov eax, 42 ugyanaz, mint a mov rax, 42.
Ez nem igaz a 16 és 8 bites regiszterekre.
Az összeadás, kivonás és szorzás aritmetikai műveleteihez az add, sub és imul utasításokat használhatjuk:
add rax, rsi ; rax = rax + rsi
imul rax, rdi ; rax = rax * rdi
sub rax, r8 ; rax = rax - r8
Az utasítások függvényekbe szerveződnek. Minden függvény a forrásfájl section .text szakaszában kap helyet.
Egy függvénydeklaráció a következőkből áll:
: követ.Külön konvenciók szabályozzák, hogyan adjuk át az argumentumokat egy függvénynek, és hogyan tér vissza belőle az érték. A két fő hívási konvenció a System V AMD64 ABI és a Microsoft x64.
Ez a kurzus a System V AMD64 ABI hívási konvenciót használja, amelyben az első hat egész argumentumot regiszterekben adjuk át a függvénynek.
Átadásuk sorrendje a következő: rdi, rsi, rdx, rcx, r8 és r9.
Az egész értéket a függvény a rax regiszterben adja vissza.
Egy függvény meghívásához a call utasítást használjuk.
Egy függvényt ugyanazon forrásfájl bármely pontjáról meg lehet hívni.
Ahhoz, hogy egy függvény más forrásfájlokból is látható legyen, a global direktívát kell használni.
Ez például egy sum függvényt deklarál:
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
Ez pedig meghívja a sum függvényünket a 3 és 5 argumentummal:
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