Assembler (Jazyk symbolických adres)
(3. část)
Mikroprocesor si vytváří tzv. frontu instrukcí. Jedná
se o několik instrukcí, které budou následovat po právě prováděné
instrukci. Tato fronta je průběžně doplňována při operacích
nezatěžujících sběrnice z paměti. Protože se ale jedná o několik
za sebou jdoucích slabik v paměti, je při instrukcích skoku
fronta vyprázdněna. Z tohoto důvodu je vhodné, aby program
obsahoval co nejmenší počet skoků. Přesto bychom i relativně
jednoduché programy bez nich asi těžko tvořili. Abychom mohli instrukce
skoku používat, musíme umět vytvořit návěští, čili cíl skoku.
Návěští
Ve vkládaném Assembleru nejsme v názvech návěští nijak zvlášť omezováni, platí pro něj stejná pravidla jako pro proměnné. Návěští s dvojtečkou uvedeme před instrukci, na kterou se odkazujeme. Při překladu je v místech odkazu na návěští jeho název nahrazen skutečnou adresou instrukce.
Nepodmíněný skok
Je to nepodmíněný skok na jiné místo programu. To může být označené návěštím. Za instrukcí skoku je potom uveden jeho název. Jinak můžeme skákat na adresu v registru nebo zadanou přímo.
-
JMP kam - proveď skok programu na kam (návěští, registr, paměť), ve skutečnosti se jen změní obsah čítače instrukcí EIP
V programu potom nepodmíněný skok vypadá takto:
navesti: instrukce na kterou bude odkaz
.
.
JMP navesti
Když používáme skoky, hrozí vždy nebezpečí, že se program zacyklí
(a nikdy neskončí). Proto je důležité si vždy rozmyslet, zda a za
jakých okolností by k této situaci mohlo dojít.
Podmíněný skok (Jcc - jump conditional code)
Jedná se o skok podmíněný stavem jednoho nebo více bitů registru
příznaků F. Jen tímto způsobem je možné provádět v Assembleru
přímé větvení programu. Před instrukcí podmíněného skoku proto
vždy provedeme instrukci (např. CMP, TEST, aj.), která použitý příznak nastaví. V
případě, že není splněna podmínka skoku, pokračuje program dál,
jako by zde instrukce skoku vůbec nebyla. Instrukce podmíněného skoku začínají vždy
písmenkem J. Za ním je zkratka udávající na jakých bitech registru
F je skok závislý. Podmíněnými skoky lze skákat pouze na adresy
+/- 127 bajtů, vzdálenější skoky se realizují podmíněným
nepodmíněným skokem, tj. nepodmíněným skokem JMP při splnění
podmínky Jcc.
-
JE / JZ návěští - skok, je-li rovno, ZF = 1
-
JNE / JNZ návěští - skok, není-li rovno, ZF = 0
-
JS návěští - skok, je-li znaménko, SF = 1
-
JNS návěští - skok, není-li znaménko, SF = 0
-
JO návěští - skok, je-li přetečení, OF = 1
-
JNO návěští - skok, není-li přetečení, OF = 0
-
JP / JPE návěští - skok, je-li sudá parita, PF = 1
-
JNP / JPO návěští - skok, je-li lichá parita, PF = 0
Skoky po bezznaménkovém srovnání:
-
JA / JNBE návěští - skok, je-li větší, (CF = 0) AND (ZF = 0)
-
JAE / JNB / JNC návěští - skok, je-li větší nebo rovno, CF = 0
-
JB / JNAE / JC návěští - skok, je-li menší, CF = 1
-
JBE / JNA návěští - skok, je-li menší nebo rovno, (CF = 1) OR (ZF = 1)
Skoky po znaménkovém srovnání:
-
JG / JNLE návěští - skok, je-li větší, (SF = OF) AND (ZF = 0)
-
JGE / JNL návěští - skok, je-li větší nebo rovno, SF = OF
-
JL / JNGE návěští - skok, je-li menší, SF <> OF
-
JLE / JNG návěští - skok, je-li menší nebo rovno, (SF <> OF)
OR (ZF = 1)
Otázka: Proč v podmínkách skoku po znaménkovém srovnání
musí být SF = OF, resp. SF <> OF, tedy proč nestačí SF = 0,
resp. SF = 1? Uveďte příklady porovnání, na kterých je to vidět (vysvětlete).
Při hledání instrukce podmíněného skoku musíme myslet na to, za jakých okolností chceme skok vykonat. K tomu je dobré si uvědomit:
-
A < B => A - B < 0 => (CF = 1) AND (SF <> OF) AND (ZF = 0)
-
A = B => A - B = 0 => (CF = 0) AND (SF = OF) AND (ZF = 1)
-
A > B => A - B > 0 => (CF = 0) AND (SF = OF) AND (ZF = 0)
Rozdíl čísel v tomto případě provedeme nejlépe instrukcí CMP. Pro tvorbu cyklu můžeme použít jeden z registrů, který si pro krokovací proměnnou vyčleníme. Jednoduchý cyklus pak vytvoříme podmíněným skokem.
Příklad:
#include <stdio.h>
unsigned char cisla[10], i;
int main()
{
_asm {
mov edi, 9
mov cx, 10
nav:
mov cisla[edi], cl
dec edi
dec cl
jnz nav
}
for (i = 0; i < 10; ++i) printf("%i ", cisla[i]);
printf("\n");
return 0;
}
Uvedený příklad naplní pole cisla hodnotami 1-10. Registr CX je
použit ke krokování, a současně k plnění pole. Prvky pole jsou
slabiky. Proto se obsah registru EDI snižuje o jednu. V případě,
že by se jednalo o slova, musíme od registru EDI odečítat 2, atd.
Konstrukci if-then-else běžnou ve vyšších programovacích jazycích lze zapsat například takto:
;testujeme if(a<b)
mov ax, a
cmp ax, b
jnb else_blok ;skok když a>=b
...podmínka platí...
jmp end_if
else_blok:
...podmínka neplatí...
end_if:
Pozor! Instrukce skoku JMP a Jcc nikdy nemění hodnoty
žádných flagů.
Procesory 386 a novější obsahují vedle instrukcí Jcc také
instrukce SETcc, které při splnění podmínky nastavují operand do
log. 1 a při nesplnění do log. 0. Podrobnosti však zůstanou jen
pro případné zájemce.
Nepodmíněný a podmíněný cyklus
Assembler má pro cyklus i samostatnou instrukci. Její použití však
předpokládá, že si rezervujeme registr ECX pro čítání. Do něj před cyklem umístíme počet opakování.
-
LOOP návěští - od ECX odečti jedna, jestliže je ECX <> 0, skoč na návěští
Př. Přepište uvedený příklad s použitím LOOP.
Instrukcí LOOP lze skákat opět pouze na adresy +/- 127 bajtů.
Cyklu vytvořenému pomocí LOOP se můžeme programově vyhnout
instrukcí:
-
JCXZ / JECXZ návěští - jestliže je v (E)CX nula, přesuň se na
návěští
Podmínka skoku byla jen nenulové číslo v registru ECX. Assembler však umožňuje podmínky opakování obohatit testováním příznaku ZF.
-
LOOPE / LOOPZ návěští - sniž ECX o jednu (bez modifikace
vlajek) a přesuň se na návěští při (ECX <> 0) AND (ZF = 1)
-
LOOPNE / LOOPNZ návěští - sniž ECX o jednu (bez modifikace
vlajek) a přesuň se na návěští při (ECX <> 0) AND (ZF = 0)
Při použití těchto instrukcí dáváme programu možnost uniknout z cyklu i (ne)nastavením příznaku ZF. Nezapomeňte ale, že ZF se musí před koncem cyklu nastavit vhodnou instrukcí.
Př. Napište v bloku Assembleru implementace standardních funkcí strcpy, strcmp
a strchr. Řetězce a znak budou v proměnných jazyka C. Výsledek
funkce uložte také do nějaké proměnné.