X86 assembler

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.

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

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 pinussepush    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 @arraypop     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 += 4add     eax, ebx       ; eax += ebxadd     [ebx], eax     ; [ebx] += eaxsub     eax, 10        ; eax -= 10
inc,dec – Integer Increment/Decrement. Suurendab/vähendab operaatorit 1 võrra.
inc     eax            ; ++eaxdec     ebx            ; --ebxinc     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 *= edximul    eax, [ebx]     ; eax *= [ebx]imul    eax, edx, 25   ; eax = edx * 25imul    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 = 0idiv    ebx            ; eax /= ebxxor     edx, edxidiv    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 ^= edxand     ecx, eax       ; ecx &= eaxor      ebx, edx       ; ebx |= edxand     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 = !eaxnot     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*2shl     ebx, eax       ; ebx = ebx << eaxshr     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, 0b10000000sar     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 == 0jecxz   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 ?? 0jl      .f_exit        ; len < 0 ? .f_exitcmp     eax, edx       ; eax ?? edxjg      .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 ?? ecxje      .f_exit        ; eax == ecx ? .f_exittest    eax, eax       ; eax ?? eaxjz      .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 = 0mov  ecx, 10           ; n = 10.loop1                 ; do {
    inc eax            ;   ++sumloop .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 funktsioonmain:                   ; 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)