**x86 assembler** on põlvkond tagasiühilduvaid assemblerkeeli, mis põhinevad esialgse Intel 8008 protsessori assemblerkeelel ning selle järglastel. x86 assemblerkeelt kasutatakse x86 protsessorite objektkoodi kompileerimiseks. Nagu kõigis assemblerkeeltes, kasutatakse protsessori käskude esitamiseks mnemnoonilisi käske. Kõrgemate keelte kompilaatorid genereerivad tihti assemblerkoodi vaheetapina masinkoodi kompileerimisele. Assemblerkeel on madala taseme ja protsessorist sõltuv programmeerimiskeel. Assemblerkeeli kasutatakse kiirete algoritmide ja kriitilise tähtsusega programmi osade kirjutamiseks, näiteks operatsioonisüsteemi kernel, draiverid, katkestused, C standardteegi algoritmid.

Ajalugu
-------

Inteli 8088 ja 8086 seeriad olid esimesed protsessorid mis omasid käsustikku mida tänapäeval tuntakse x86 nime all. Need 16-bitised CPU-d olid 8-bitiste 8080 seeria CPU-de edasiarendus, pärides suure osa käsustikust. Inteli 8088 ja 8086 kasutasid sisemiselt 16-bitiseid registreid, kuid 8088 protsessoril oli 8-bitine andmesiin ja 8086 protsessoril 16-bitine andmesiin. x86 assembler katab mitmeid erinevaid protsessoreid, mis järgnesid 8086-le: 80188, 80186, 80286, 80386, 80486, Pentium, Pentium Pro, kuni tänapäevaste protsessoriteni välja. Lisaks on veel terve seeria x86 AMD ja Cyrix CPU-sid, mis pole Inteli poolt toodetud. Termin x86 kehtib ükskõik millise CPU kohta, mis suudab jooksutada x86 käsustiku masinkoodi.

Modernne x86 käsustik põlvneb otseselt Inteli 8086 käsustikust ja sisaldab kindalt seeriat laiendusi. Vanema põlvkonna x86 protsessoritega on täielik binaarne tagasiühilduvus. Praktikas on tüüpiline kasutada käske, mis on ühilduvad kõikide protsessoritega peale 80386 protsessorit. Alles lähiaastatel on operatsioonisüsteemid hakanud nõudma uuemaid käsustike laiendusi (nt. MMX, SSE/SSE2/SSE3).

Mnemnoonika ja käsukoodid
-------------------------

Iga x86 käsk on assembleris esitatud kindla mnemnoonikaga, millele võib järgneda üks või rohkem operandi. Mnemnoonilised käsud transleeritakse üheks või rohkemaks baidiks, mida kutsutakse opkoodiks. Käsk "NOP" transleerub baidiks 0x90, käsk "add ESP, 4" transleerub baitideks 0x83, 0xC4, 0x04. x86 käsustikus leidub ka Inteli poolt dokumenteerimata käske.

Süntaks
-------

x86 assembleril on kaks põhilist süntaksit: Inteli süntaks (originaalne x86 platformi süntaks) ja AT&T süntaks. Inteli süntaks on dominantne Windowsi platvormil ja on üldiselt selgem ning loetavam. AT&T süntaks on dominantne Unix-platvormidel, sest Unix loodi AT&T Bell Laboratooriumis. Lühike ülevaade AT&T ja Inteli süntaksi põhilistest erinevustest:

|  | Intel | AT&T |
| --- | --- | --- |
| Parameetrite järjestus | DST <- SRC
*eax := 5* on *mov eax, 5* | SRC -> DST
*eax := 5* on *mov $5,%eax* |
| Parameetri suurus | Järeldub automaatselt registri nimest *(nt. rax, eax, ax, al järeldavad QWORD, DWORD, WORD, BYTE)*.

```
add esp, 4

```
 | Mnemnoonika käskude järgi lisatakse suurusmärge, mis tähistab operandide suurust *(nt. "q" QWORD, "l" LONG(DWORD), "w" WORD, "b" BYTE)*.

```
addl $4, %esp

```
 |
| Sümbolite prefiksid | Assembler tunneb automaatselt ära sümbolite tüübid, olgu need registrid, sümbolid vms. | Konstantide prefiksiks on "$" *(nt. $0x80)* ja registrite prefiksiks on "%" *(nt %eax)*. |
| Adresseerimine | Üldine süntaks *TÜÜP[baas + indeks\*skalaar + nihe]*. Andmetüüp tuntakse ära automaatselt, kuid käsud nagu **MOVZX** vajavad lisaks tüübi deklareerimist.
Näide: 

```
mov eax, [ebx + ecx\*4 + offset]
movzx eax, byte[ebx + ecx\*4 + offset]

```
 | Üldine süntaks *NIHE(baas, indeks, skalaar)*. Andmetüübi määravad opkoodi sufiksid q,l,w,b.

Näide:

```
movl offset(%ebx,%ecx,4), %eax
movzxb offset(%ebx,%ecx,4), %eax

```
 |

Valdav enamik x86 assembleritest kasutavad just Inteli süntaksit: MASM, NASM, FASM, TASM, YASM. GNU Assembler (GAS) toetab Inteli süntaksit alates versioonist 2.10 läbi *.intel\_syntax* direktiivi. Vaikimisi kasutab GAS AT&T süntaksit.

Üldiselt on Inteli süntaks programmeerija aspektist selgem ja lihtsam, sellest tulenevalt ka populaarsem.

Registrid
---------

x86 protsessoritel on saadaval mitmed registrid binaarsete andmete ajutiseks mahutamiseks ning töötlemiseks. Peamiste registrite hulka kuuluvad andme- ja aadressiregistrid. Igal registril on eritähendus või eriotstarve. Mitme opkoodi korral sõltub kindla eelmääratud registri sisust käsu täitmine (nt. **LOOP** käsk sõltub registrist ECX).

Andmeregistrid:

* EAX – akumulaator – aritmeetika ja üldregister
* EBX – baasregister – üldine baasregister adresseerimisel
* ECX – loendregister – tsükliloendur ja võrdlusregister
* EDX – andmeregister – laiend-akumulaator, aritmeetika ja andmete üldregister

Aadressiregistrid:

* ESP – pinuviit – viitab programmi pinu otsale (*stack pointer*)
* EBP – raamiviit – viitab funktsiooni raamile pinus (*frame pointer*)
* ESI – lähteindeks – lähteviit plokkoperatsioonide jaoks
* EDI – siirdeindeks – siirdeviit plokkoperatsioonide jaoks

Igal üldregistril on vastav 64-bitine ja 16-bitine osa *(nt. EAX: RAX ja AX)*, andmeregistritel on ka 8-bitised osad *(nt. EAX: AL ja AH)* nagu näha järgnevalt skeemilt:

Andmeregistrid (A, B, C ja D) | 64 | 56 | 48 | 40 | 32 | 24 | 16 | 8 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| R?X |
|  | E?X |
|  | ?X |
|  | ?H | ?L |

Lisaks üldregistritele leidub veel:

* EIP käsuviit – viitab hetkel käivitatavale käsule.
* FLAGS – protsessori staatuslipud.
* Segmendi registrid (CS, DS, ES, FS, GS, SS), mis on olulised ainult 16-bitises koodis. Need määravad 64-kilobaidiste segmentide algusindeksid. Ilma segmentregistriteta polnud võimalik > 64 kB mälu adresseerida.
* Laienduste registrid (MMX, SSE jms).

x86 registreid saab kasutada vastavate käskudega, näiteks käsuga MOV. Et teha arvutusi, peab andmed lugema mälust registrisse ja siis pärast vajadusel tagasi mällu. Üldreeglina ei saa mäluga otseselt opereerida ja on vaja registreid. Näiteks:

```
mov eax, [integer\_value] ; load dword[integer\_value]
add eax, 4 ; integer\_value += 4
mov ebx, [other\_value] ; load dword[other\_value]
add eax, ebx ; integer\_value += other\_value
mov [integer\_value], eax ; write dword[integer\_value]

```

Loeb globaalse muutuja **integer\_value** registrisse *EAX*, liidab juurde **4**, loeb globaalse muutuja **other\_value** ja liidab selle *EAX*-ile. Lõpuks kirjutatakse tulemus globaalsesse muutujasse **integer\_value**.

Programmeerimiskeeles C oleks vastav koodijupp: "integer\_value = integer\_value + 4 + other\_value;".

Käsutüübid
----------

x86 protsessoril on suur kogus erinevaid opkoode, kuid tähtsamad neist tuleks ka esile tuua.

### MOV ja adresseermine

**MOV** – *Move data*. Kopeerib andmeid registrite ja mälu vahel. Inteli assembler süntaks järgib alati kuju **DST <- SRC**. Paar lihtsat näidet: 

```
mov eax, edx ; EAX dword <- EDX dword 
mov dx, ax ; EDX word <- EAX word 
mov ah, dl ; EAX hi <- EDX lo

```

**MOVZX** – *Move with zero extend*. Kopeerib andmed väiksema laiusega registrist suuremasse ja väärtustab ülejäänud bitid nullidega. Näiteks bait 0x28 (40) loetaks sisse kui 0x00000028:

```
mov bl, 40 ; BL := 0x28 
movzx eax, bl ; EAX := 0x00000028

```

**MOVSX** – *Move with sign extend*. Töötab nagu **MOVZX**, aga arvestab *sign*-bitti. Näiteks negatiivne bait 0xE0 (-32) loetaks sisse kui 0xFFFFFFE0:

```
mov bl, -32 ; BL := 0xE0 
movsx eax, bl ; EAX := 0xFFFFFFE0

```

#### Adresseermimine

x86 mäluadresseerimise saab kokku võtta valemiga **[baas + indeks\*skalaar + nihe]**, kus:

* Baas – baasregister (nt. EBX), mis määrab baasaadressi.
* Indeks – indeksregistri (nt. EAX) poolt määratud lisanihe.
* Skalaar – indeksregistri kordaja (ainult 1,2,4,8).
* Nihe – konstantne lisanihe (nt. 32 vms täisarv).

Lühike näide koos analoogse C programmiga:

```
; static int array[4] = { 0, 1, 2, 3 };
; int\* ptr = array;
; int i = 2;
; int a = ptr[i + 1];
mov ebx, array ; baasregister viitab array-le
mov eax, 2 ; indeksregister i = #2 element
mov eax, [ebx + eax\*4 + 4] ; a = ptr[i + 1]
 ; skalaar on 4 sest sizeof(int) == 4
 ; nihe on 4 sest sizeof(int)\*1 == 4

```

Üleval toodud näide katab kõige keerulisema juhu adresseermisest, kuid muidugi saab ka adresseerida kasutades ainult ühte väikest osa tervest adresseerimisest. Näiteks:

```
mov eax, [array + 12] ; a = array[3]

```

Seega on võimalikke kombinatsioone piisavalt iga vastava juhu jaoks.

### Põhikäsud

**push** – *Push to stack*. Lükkab väärtuse *stack*-segmenti ja lahutab ESP registrist väärtuse laiuse.

```
push eax ; lükkab EAX-i väärtuse pinusse
push dword[array] ; lükkab 4 baiti aadressilt @array pinusse

```

**pop** – *Pop from stack*. Võtab väärtuse *stack*-segmendist ja liidab ESP registrile väärtuse laiuse.

```
pop dword[array] ; korjab pinust väärtuse ja kirjutab aadressile @array
pop eax ; korjab pinust väärtuse ja kirjutab registrisse EAX

```

**lea** – *Load effective address*. Arvutab adresseeringu aadressi ning salvestab selle aadressi. Mälust ei loeta midagi.

```
lea ebx, [array] ; @array aadress liigutatud EBX-i, sama mis ''mov ebx, array''
lea ebx, [ebx + eax\*4] ; arvutatud keeruline aadress EBX-i

```

### Aritmeetika ja Loogika

**add,sub** – *Integer Addition/Subtraction*. Liidab/lahutab kaks operandi ja salvestab tulemuse esimesse operandi.

```
add eax, 4 ; eax += 4
add eax, ebx ; eax += ebx
add [ebx], eax ; [ebx] += eax
sub eax, 10 ; eax -= 10

```

**inc,dec** – *Integer Increment/Decrement*. Suurendab/vähendab operaatorit 1 võrra.

```
inc eax ; ++eax
dec ebx ; --ebx
inc dword[ebx] ; ++[ebx]

```

**imul** – *Integer Signed Multiplication*. Omab kahte erinevat süntaksit. Esimene variant korrutab kaks operandi ja paigutab tulemuse esimesse operandi. Teine süntaks korrutab teise ja kolmanda operandi ning paigutab tulemuse esimesse operandi.

```
imul eax, edx ; eax \*= edx
imul eax, [ebx] ; eax \*= [ebx]
imul eax, edx, 25 ; eax = edx \* 25
imul eax, [ebx], 10 ; eax = [ebx]\*10

```

**idiv** – *Integer Signed Division*. Jagab 64-bitise täisarvu EDX:EAX määratud väärtusega läbi. EDX tuleb vajadusel käsitsi nullida. Tulem on alati EAX registris ja jagatise jääk on EDX registris. Operand ei saa olla EDX register või vahetu väärtus.

```
xor edx, edx ; edx ^= edx; -> edx = 0
idiv ebx ; eax /= ebx
xor edx, edx
idiv eax ; eax /= eax

```

**and, or, xor** – *Bitwise logical and, or, exclusive or*. Teostab bitt-loogilised tehted kahe operandi vahel. Tulem paigutatakse esimesse operandi.

```
xor edx, edx ; edx ^= edx
and ecx, eax ; ecx &= eax
or ebx, edx ; ebx |= edx
and eax, 0x0F ; eax &= 0x0F

```

**not** – *Bitwise logical not*. Pöörab bitid ümber. Väärtus võib olla register või adresseeritud mälu.

```
not eax ; eax = !eax
not dword[ebx] ; \*ebx = !\*ebx

```

**neg** – *Arithmetic negate*. Väärtustab täisarvu selle vastandarvuga i = -i.

```
neg eax ; eax = -eax

```

**shl,shr** – *Logical Shift Left/Right*. Nihutab bitte loogiliselt vasakule/paremale, üle ääre nihutatud bitid kaovad ja lisatud bitid on nullid. Näiteks 0b10011100 << 2 = 0b00111000

```
shl eax, 1 ; eax = eax << 1 ; sama kui eax\*2
shl ebx, eax ; ebx = ebx << eax
shr eax, 2 ; eax = eax >> 2 ; sama kui eax/4

```

**sar,sal** – *Arithmetic Shift Left/Right*. Nihutab bitte aritmeetiliselt vasakule/paremale. Arvestab, et tegu võib olla negatiivse arvuga ning säilitab vajadusel negatiivsusbiti.

```
movsx eax, 0b10000000
sar eax, 1 ; eax = eax >> 1
 ; AL (0b11000000)

```

### Programmivoo käsud

Programmivoo käske kasutatakse programmi loogiliseks juhtimiseks. Madalamal tasemel implementeeritakse nende käskudega if/else/while käitumist.

**jmp** – *Unconditional jump*. Hüppab määratud sümbolile, muutes programmi käivitusasukohta. Otseselt muutub EIP register.

```
jmp begin ; hüppab sildi "begin" juurde

```

**jcondition** – *Conditional jump*. Hüppab sümbolile ainul siis, kui kindel tingimus on täidetud. Need käsud sõltuvad protsessori FLAGS registrist. Kõige tähtsamad bitid on Zero Flag (ZF) ja Sign Flag (SF). Kõik aritmeetilised käsud mõjutavad protsessori FLAGS registrit. Näiteks käsk *xor eax, eax* tõstab Zero Flagi (ZF). Täpsemalt saab lugeda FLAGS registrist Wikipedias.

```
je label ; jump if equal (ZF=1)
jne label ; jump if not equal (ZF=0)
jz label ; jump if zero (ZF=1)
jnz label ; jump if not zero (ZF=0)
jg label ; jump if greater (signed)
jge label ; jump if greater or equal (signed)
jl label ; jump if less (signed)
jle label ; jump if less or equal (signed)
ja label ; jump if above (unsigned)
jae label ; jump if above or equal (unsigned)
jb label ; jump if below (unsigned)
jbe label ; jump if below or equal (unsigned)
jcxz label ; jump if CX register == 0
jecxz label ; jump if ECX register == 0

```

**cmp** – *Signed compare*. Teostab märgitundliku lahutustehte kahe operandi vahel ja uuendab protsessori FLAGS registrit. Märgatavalt ZF ja SF. Võrdluskäsku kasutame ainult siis, kui on vaja võrrelda kahe operandi vahet (suurem/väiksem?).

```
cmp [len], 0 ; len ?? 0
jl .f\_exit ; len < 0 ? .f\_exit
cmp eax, edx ; eax ?? edx
jg .loop ; eax > edx ? .loop

```

**test** – *Equality test*. Teostab loogilise AND tehte kahe operandi vahel ja uuendab FLAGS registrit. Uuenevad ZF ja SF. Test käsku kasutame ainult siis kui on vaja testida kahe operandi võrdsust või juhul kui mõni register on väärtusega 0.

```
test eax, ecx ; eax ?? ecx
je .f\_exit ; eax == ecx ? .f\_exit
test eax, eax ; eax ?? eax
jz .loop1 ; eax == 0 ? .loop1

```

**loop** – *Looping instruction*. Teostab tsüklihüppe juhul kui ECX != 0 ja vähendab ECX-i 1 võrra.

```
mov eax, 0 ; sum = 0
mov ecx, 10 ; n = 10
.loop1 ; do {
 inc eax ; ++sum
loop .loop1 ; } while(n--);
 ; sum == 10

```

Näited
------

Paar näidet Windowsi ja Linuxi platvormi peal lihtsast Hello world programmist. **Microsoft Macro Assembler** *(MASM)* töötab ainult Windowsi platvormil ning on üpriski keerukas ja detailne.

**Netwide Assembler** *(NASM)* töötab mitme platvormi peal ning on sisult väga lihtne. NASM on populaarne just Linux/Unix-keskkonnas. Linuxi peal on kompileerimine väga sarnane, seega on NASM väga hea valik multiplatvormseks arenduseks.

### "Hello world!" 32-bitine MASM (Windows)

```
; Hello World programm
; Kompileerimine:
; \masm32\bin\ml.exe /c /coff helloworld.asm
; Linkimine:
; \masm32\bin\link.exe /ENTRY:main /SUBSYSTEM:CONSOLE helloworld.obj
;
.386 ; kasuta 386 käsustikku
.model flat, stdcall ; 32-bit, stdcall win32 API jaoks

.data ; andmesegmendi algus
 hello byte 'Hello world!',10,0

.code ; koodisegmendi algus
 public main ; 'main' sümbol linkerile nähtavaks
 includelib \masm32\lib\msvcrt.lib ; lingi Visual C Runtime library
 extrn printf:near ; lingi C printf funktsioon

main: ; main sümbol (programmi algus)

 ; printf("Hello world!\n");
 push offset hello ; lükka 1 argument pinusse
 call printf
 add esp, 4 ; pinu balansseerimine
 
 ; return 0;
 mov eax, 0 ; liiguta EAX registrisse 0
 ret ; naase Operatsioonisüsteemi (exit)

END ; assemblerfaili lõpp

```

### "Hello world!" 32-bit NASM (Windows)

```
; ----------------------------------------------------
; Hello World programm
; Kompileerimine:
; \NASM\nasm -f win32 helloworld.asm -o helloworld.obj
; Linkimine:
; \MinGW\bin\gcc -Wl,-s helloworld.obj -o helloworld.exe
;
; ----------------------------------------------------

section .data
 hello db 'Hello world!', 10, 0
 
; -------------------------------
section .text
 global \_main ; tee \_main sümbol linkerile nähtavaks
 extern \_printf ; lingi C stdlib \_printf-i
 
\_main: ; main sümbol (programmi algus)

 ; printf("Hello world!\n");
 push hello ; lükka argument pinusse
 call \_printf ; kasutame C printf funktsiooni
 add esp, 4 ; pinu balansseerimine
 
 ; return 0;
 mov eax, 0 ; EAX := 0
 ret ; naaseme Operatsioonisüsteemi (exit)

```

### "Hello world!" 32-bitine NASM (Linux)

```
; ----------------------------------------------------
; Hello World programm
; Kompileerimine:
; nasm -f elf helloworld.asm -o helloworld.o
; Linkimine:
; gcc -Wl,-s helloworld.o -o helloworld
;
; ----------------------------------------------------

section .data
 hello db 'Hello world!', 10, 0
 
; -------------------------------
section .text
 global main ; tee \_main sümbol linkerile nähtavaks
 extern printf ; lingi C stdlib printf-i
 
main: ; main sümbol (programmi algus)

 ; printf("Hello world!\n");
 push hello ; lükka argument pinusse
 call printf ; kasutame C printf funktsiooni
 add esp, 4 ; pinu balansseerimine
 
 ; return 0;
 mov eax, 0 ; EAX := 0
 ret ; naaseme Operatsioonisüsteemi (exit)

```

Vaata ka
--------

* Assembler
* Programmeerimiskeel Assembler
* C

Viited
------

1. ↑  "Intel Secrets, Bugs, and Undocumented Opcode". Originaali arhiivikoopia seisuga 9. mai 2013. Vaadatud 7. detsembril 2013.
2. ↑  "The Creation of Unix". Originaali arhiivikoopia seisuga 28. märts 2014. Vaadatud 7. detsembril 2013.
3. ↑  Intel ja AT&T süntaks
4. ↑  x86 arhitektuuri registrid

Välislingid
-----------

* Wikibooks: X86 Assembly
* University of Virginia: X86 Assembly
* OSDev: The Netwide Assembler (NASM)
* OSDev: Microsoft Macro Assembler (MASM)
* Intel: Intel 64 and IA-32 Architectures Software Developer Manuals
* NASM: Netwide Assembler-i kursus Tartu Ülikoolis