IT rekvalifikácia. Seniorní programátori zarábajú až 6 000 €/mesiac a rekvalifikácia je prvým krokom. Zisti, ako na to!

9. diel - Assembler - Podmienené a nepodmienené skoky

V dnešnom ASM tutoriálu sa budeme venovať podmieneným a nepodmieneným skokom.

Skoky sa používajú pri programovaní podmienok alebo cyklov. V kompilátora MASM môžeme pre podmienky a cykly používať príkazy .if, .repeat, .while. Kompilátor MASM za nás automaticky vygeneruje inštrukcia skokov. Naopak v kompilátora NASM nie sú žiadne podmienky alebo cykly a túto funkcionalitu musíme simulovať pomocou skokov na rôzne návestí v programe a samozrejme pomocou porovnávacích inštrukcií. Práve tejto problematike sa budeme dnes venovať.

Skoky v ASM sú podmienené alebo nepodmienené.

Nepodmienené skoky

Nepodmienené skoky zloží k tomu, aby sme mohli neobmedzene skákať z jednej časti kódu do druhej. V našom Hello world programu sme takto napr. Skákali na návestie Return.

V praxi takto môžeme potrebné vytvoriť nekonečný cyklus alebo jednoducho preskočiť nejakú časť kódu.

Inštrukcia JMP (Jump)

Inštrukciu JMP už tiež poznáme, slúži práve na nepodmienečný skoku. Má iba jeden operand a tým je návestí, ktoré udáva, kam máme skočiť.

Návestí deklarujeme pridaním : za vybraný názov, napr. Takto:

sem_skoc:

V strojovom kóde samozrejme nie sú žiadne návestie, ale číselné adresy. Tie za nás vypočítava kompilátor. Pre krátke skoky vloží do inštrukcie 8, 16 alebo 32-bitovú relatívna adresu. Tá je kladná pre skoky smerom dopredu a záporná pre skoky späť. Pre dlhé skoky sa použije absolútny 16 alebo 32-bitovú adresa. V strojovom kóde preto existuje niekoľko inštrukcií skoku: E9, EA, EB, FF. V ASM jednoducho vo všetkých prípadoch používame JMP a kompilátor sa podľa dĺžky skoku rozhodne, aký kód inštrukcie použije.

Niekedy je cieľ skoku potreba určiť až za behu programu. V takýchto prípadoch sa používajú nepriame skoky, pri ktorých je adresa skoku buď v registri, alebo niekde v pamäti.

Príklad

Teraz si ukážeme príklad nekonečného cyklu:

znovu:
mov si, hello_world_message
call print_string
jmp znovu

hello_world_message db 'Hello, World!', 13, 10, 0

Kód predpokladá, že máme niekde deklarovanú aj funkciu print_string, ktorú sme si deklarovali už skôr v našom Hello world programu.

Deklarujeme si návestí znovu: a do registra SI presunieme text, ktorý pomocou funkcie print_string vypíšeme. Ďalej skočíme na návestí znovu: a celé sa to bude opakovať znovu, až do nekonečna. Samozrejme nemusíme skočiť dozadu, aby sme takto skákali stále dookola, ale môžeme skočiť aj niekam dopredu, ďalej do programu.

Výsledkom programu teda bude:

Hello, World!
Hello, World!
Hello, World!
Hello, World!
...

Z tohto nekonečného cyklu by sme za nejaké podmienky mohli vyskočiť preč. K tomu ale už potrebujeme skok nejako opodmínkovat.

Podmienené skoky

Častejšie budeme ale samozrejme potrebovať skočiť podmienečne. To znamená, že skočíme iba vtedy, ak bude splnená nejaká podmienka.

Inštrukcia CMP (Compare)

Pred vykonaním samotného skoku je potreba použiť inštrukciu CMP (Compare). Tá má dva operandy (A a B) a defakto supluje za príkaz if z vyšších programovacích jazykov. CMP operandmi porovná a na výsledok potom môžeme reagovať pomocou ďalších inštrukcií.

Celá podmienka sa zapisuje takto:

cmp [první operand], [druhý operand]
j[x] [návěstí]

Poďme si ukázať tabuľku, na aké výsledky porovnania môžeme pomocou ktorých inštrukcií reagovať:

Porovnanie A == B A! = B A> B A <B A> = B A <= B
inštrukcie JE (JZ) JNE (JNZ) JA (JNBE) JB (JNAE) JNB (JAE) JNA (JBE)
Inštrukcie pre každú možnosť sú hneď 2. Nenechajte sa zmiasť, dôvod je prostý. Procesor reálne obe čísla porovnáva tak, že je odpočíta. Môžeme sa teda pýtať, či sú napr. 2 čísla rovná alebo či je ich rozdiel 0. Oboje je to isté, len má inštrukcie pre prehľadnosť iný názov, aby bolo z kódu jasné, čo v danej chvíli myslíme.

Inštrukcie JE (Jump if Equal) a JZ (Jump if Zero)

Skočí na dané návestie, ak sú porovnávaná čísla rovnaké (A == B).

Inštrukcie JNE (Jump if Not Equal) a JNZ (Jump if Not Zero)

Skočí na dané návestie, ak sú porovnávaná čísla rôzne (A != B).

Inštrukcie JA (Jump if Above) a JNBE (Jump if Not Below or Equal)

Skočí na dané návestie, ak je prvá porovnávanú číslo väčšie ako druhé (A > B).

Inštrukcie JB (Jump if Below) a JNAE (Jump if Not Above or Equal)

Skočí na dané návestie, ak je prvá porovnávanú číslo menšie ako druhé (A > B).

Inštrukcie JNB (Jump if Not Below) a JAE (Jump if Above or Equal)

Skočí na dané návestie, ak je prvá porovnávanú číslo väčšie alebo rovné ako druhé (A >= B).

Inštrukcie JNA (Jump if Not Above) a JBE (Jump if Below or Equal)

Skočí na dané návestie, ak je prvá porovnávanú číslo menšie alebo rovné ako druhé (A <= B).

Ak viete niečo o reprezentáciu čísel v počítači, tak čísla, ktoré môžu mať aj zápornú hodnotu, sú v pamäti uložené inak (pretože musí mať vymedzenú pamäť aj pre záporné hodnoty, sú posunuté o polovicu rozsahu). Toto má vplyv na porovnávanie čísiel a ASM za nás problém nerieši. Operácie pre skoky, ktoré sme si ukázali, platí len pre unsigned čísla (teda tá, ktorá môžu byť len nezáporná). Ako sa s tým vysporiadať u signed čísel si ukážeme nabudúce.

Príklad

Určite si teraz zaslúžime príklad. Tu je:

mov ax, 1
mov bx, 2
cmp ax, bx
jb bx_je_vetsi
;v této části programu bx není větší
jmp konec
bx_je_vetsi:
;v této části programu je bx větší
konec:

Program skočí na dané návestie podľa toho, či je hodnota 1 > 2.

Inštrukcie TEST

Najčastejšie sa inštrukcie TEST používa pre zistenie, či je hodnota v registri kladná, záporná, alebo nulová.

Porovnanie A == 0 A! = 0 A <0 A> = 0
inštrukcie JZ JNZ JS JNS
Použitie potom vyzerá nasledovne:
test ax,ax
jz ax_je_nulove
js ax_je_zaporne

Ak sú operandy inštrukcie TEST rôzne, vykoná sa logická operácia AND. Tým môžeme testovať jednotlivé bity prvého operandu. Najčastejšie sa testuje posledný bit, čím sa zistí, či je číslo párne alebo nepárne:

test ax,1
jz ax_je_sude
jnz ax_je_liche

Cykly

U všetkých typov cyklov môžeme používať ktorejkoľvek všeobecné registre. U for cyklov ale musíme dávať pozor, aby sme nechtiac vnútri cyklu nezmenili hodnotu registra, v ktorom máme riadiacej premennú cyklu.

Ukážme si, ako pomocou skokov nahradiť rôzne druhy cyklov, ktoré určite poznáte z vyšších programovacích jazykov.

Cyklus s podmienkou na konci (do - while):

muj_cyklus:
; Vnitřek cyklu
cmp ax,bx
ja muj_cyklus

Cyklus s podmienkou na začiatku (while):

jmp porovnani
muj_cyklus:
; Vnitřek cyklu
porovnani:
cmp ax,bx
ja muj_cyklus

Cyklus od 10 do 1 (for):

mov cx, 10
muj_cyklus:
; Vnitřek cyklu
dec cx
jnz muj_cyklus

Cyklus od 1 do 10 (for):

mov cx, 1
muj_cyklus:
; Vnitřek cyklu
inc cx
cmp cx,10
jbe muj_cyklus

V starších programoch sa tiež často používala inštrukcia loop. Tá robí úplne to isté ako dve inštrukcie dec cx a jnz návěstí. Jediný rozdiel je v tom, že je asi štyrikrát pomalší. Druhá nevýhoda je v tom, že ju nemožno použiť s iným registrom než cx.

V budúcej lekcii, Assembler - Kombinácia skokov a príznaky , sa naučíme kombinovať podmienené a nepodmienené skoky, zistíme, ako skokové inštrukcie získajú výsledok z CMP a ako porovnávať signed čísla.


 

Všetky články v sekcii
Základy assembleri
Preskočiť článok
(neodporúčame)
Assembler - Kombinácia skokov a príznaky
Článok pre vás napísal Jakub Verner
Avatar
Užívateľské hodnotenie:
Ešte nikto nehodnotil, buď prvý!
Autor se věnuje programování v x86 Assembleru.
Aktivity