Aleš Keprt - Elektronická učebnice Assembleru
http://student.inf.upol.cz/keprt/vyuka/asm/asm2.aspx
Assembler - 2.část poslední změna této stránky: 9.2.2007 Zpět
1. Příznaky (flagy) Flagy (česky podivně "příznaky", proto používám výhradně anglický název) jsou výlučnou záležitostí assembleru a ve vyšších jazycích se nevyskytují. Jsou to bity pro dva různé účely. První typ flagu, aritmetický, se nastavuje automaticky při provádění určitých instrukcí. Potom lze flagy číst a zjišťovat tak určité situace. Druhý typ flagu se nastavuje speciální instrukcí a slouží k přepínání CPU do určitých režimů práce. Flagy jsou enormně důležité, takže se jim budeme věnovat podrobně. Flagy jsou uloženy v samostatném 32bitovém registru eflags alias f. Je jich tedy 32 (co bit, to flag), ovšem některé z nich se používají dohromady (když jeden bit poskytující jen dva stavy nestačí). Některé flagy určují režim práce CPU, takže jejich změnou docílíte jen zhroucení počítače (v DOSu) nebo vyvolání výjimky (v normálním systému). Jelikož CPU nemá žádné příkazy jako if nebo while, musíme cykly a podmíněné příkazy řešit pomocí flagů. Následuje seznam aritmetických flagů, které jsou ovlivňovány výsledkem aritmetických instrukcí. značka název
popis
Z=E
zero equal
výsledek je nula operandy jsou si rovny
A
above
1.operand je větší (u)
B=C
below carry
1.operand je menší (u) přetečení nebo podtečení
G
greater 1.operand je větší (s)
L
lower
1.operand je menší (s)
Rozdíl mezi A-B a G-L je jasný. První dvojice je unsigned, zatímco druhá je signed. Aby to šlo dokázat, můsíme si ukázat, jak se s flagy pracuje. Z uvedených flagů A-B a G-L navíc žádný fyzicky neexistuje (snad kromě B, který je identickýž s C), jsou to jen symbolické zkratky zahrnující ve skutrečnosti několik jiných flagů. To však pro programátora není podstatné.
1z9
19.2.2007 7:51
Aleš Keprt - Elektronická učebnice Assembleru
http://student.inf.upol.cz/keprt/vyuka/asm/asm2.aspx
Instrukce Jcc (podmíněný skok = skok při splnění určité podmínky)
Jcc address ("jump conditional-code") Pro podmíněné příkazy máme k dispozici příkazy skoku označované souborně jako Jcc, které mají název složený z písmene j (jump) a písmene flagu (conditional-code). Flagy také můžete kombinovat a dostat tak vlastnosti jako le (menší nebo rovno) apod. Můžete taky testovat, zda je flag negovaný předřazením písmene n. Tak vznikne instrukce jng (jump if not greater), která je totožná s jle (jump if lower or equal). Podmíněnými skoky můžete skákat pouze na adresy +/-127 bajtů (signed byte), ovšem toto omezení v praxi nepoznáte, jelikož assembler automaticky nahrazuje vzdálené skoky "opisem" a nové procesory mají již vzdálené podmíněné skoky přímo v instrukční sadě. (Pojem "vzdálený podmíněný skok" zde znamená skok za hranici +/- 127 bajtů.) Ukážeme si příklad jak inkrementovat 64bitovou proměnnou. hyper superlong; //64bitová proměnná v MSVC _asm { inc dword ptr superlong //inkrementuje dolních 32 bitů jnz skip //přeskočí další příkaz, pokud nedošlo k přetečení inc dword ptr superlong+4 //inkrementuje horních 32 bitů } skip:
Konstukce if-then-else Konstrukci if-then-else běžnou ve vyšších programovacích jazycích lze zapsat například takto: ;testujeme if(a
=b ...podmínka platí... jmp end_if else_blok: ...podmínka neplatí... end_if:
Nevýhodou při porovnávání je, že musíme načíst do registru jedno z porovnávaných čísel a provedením sub hodnotu ztratíme. Proto existuje 2z9
19.2.2007 7:51
Aleš Keprt - Elektronická učebnice Assembleru
http://student.inf.upol.cz/keprt/vyuka/asm/asm2.aspx
instrukce cmp.
Instrukce cmp ("compare") Instrukce test ("test") Instrukce cmp provede sub bez uložení výsledku. Používá se pro obvyklé aritmetické porovnávání (větší, meněí, rovno). Instrukce test provede and bez uložení výsledku. Používá se pro bitové porovnávání. Můžeme pak nahradit začátek příkazu if takto: mov eax,a cmp eax,b jnb else_blok
;skok když a>=b
Poznámka: Instrukce jnc a jnb jsou totožné, protože carry je nastaveno právě když první operand je menší.
Důležité! Instrukce jmp, jcc a mov nikdy nemění hodnoty flagů. Ostatní instrukce flagy většinou mění, ale zase ne vždy všechny, takže je potřeba se to naučit podrobně - viz referenční tabulku instrukcí. Dívat se do helpu kvůli tak základním instrukcím není z praktického hlediska (časově) možné.
Pozor! Typickou a jednou z nejčastějších chyb je následující kód: //toto fungovat nebude inc dword ptr superlong jnc skip inc dword ptr superlong+4 skip:
Instrukce inc a dec nikdy nemění flag carry!
Instrukce adc ("add with carry") Práci si nejvíc ušetříte, když budete v takových situacích používat instrukci adc, která k výsledku součtu ještě přičte hodnotu carry před provedením instrukce.
3z9
19.2.2007 7:51
Aleš Keprt - Elektronická učebnice Assembleru
http://student.inf.upol.cz/keprt/vyuka/asm/asm2.aspx
hyper superlong; //64bitová proměnná v MSVC _asm { add dword ptr superlong,1 //inkrementuje dolních 32 bitů adc dword ptr superlong+4,0 //přičte carry flag k horním 32 bitům } skip:
Pozor! První ze dvou řádků asm jsme museli původní inc nahradit za add, protože inc/dec nemění carry flag. Strukturované konstrukce while a for si teď už snadno odvodíte. Taky už můžete dokázat, proč Z=E a B=C.
Následující tabulka ukazuje všechny podporované kombinace flagů pro podmíněné skoky.
4z9
19.2.2007 7:51
Aleš Keprt - Elektronická učebnice Assembleru
5z9
http://student.inf.upol.cz/keprt/vyuka/asm/asm2.aspx
19.2.2007 7:51
Aleš Keprt - Elektronická učebnice Assembleru
http://student.inf.upol.cz/keprt/vyuka/asm/asm2.aspx
A na závěr kompletní seznam flagů tak, jak jsou fyzicky uloženy v eflags registru. Tato struktura je závazná pro všechny x86 procesory.
Závěrečné shrnutí 6z9
19.2.2007 7:51
Aleš Keprt - Elektronická učebnice Assembleru
http://student.inf.upol.cz/keprt/vyuka/asm/asm2.aspx
Na závěr se tedy přestaňme vodit za nos a podívejme se na SKUTEČNOU funkci flagů.
Flagy pro aritmetické operace (add, sub, cmp, and, or, xor, bitové posuny a rotace): C (Carry)
Při sčítání (add): CF je nastaven, když dojde k přetečení výsledku, tj. výsledek se nevleze do paměťové buňky. Při odčítání (sub, cmp): CF je nastaven, když dojde k podtečení výsledku, tj. odčítá se větší číslo od menšího a výsledek se nevleze do paměťové buňky. CF je základním a nejdůležitějším flagem. Nastavují ho všechny instrukce, u kterých může dojít k přetečení nebo podtečení bitu, tedy i instrukce násobení, dělení, bitových posunů a rotací, atp. Pouze instrukce inc a dec tento flag neovlivňují (nechávají předešlou hodnotu). Příklady (sčítání a odčítání 8bitových hodnot): 2+5=7, CF=0 250+10=4, CF=1 5-2=3, CF=0 10-250=16, CF=1
P (Parity)
Určuje paritu dolních 8bitů výsledku. Parita závisí na počtu jedničkových bitů v dolních 8 bitech výsledku. P=1 při sudé paritě (sudý počet jedniček), P=0 při liché paritě (lichý počet jedniček). Příklady: 0=00b: P=1 1=01b: P=0 2=10b: P=0 3=11b: P=1 257=100000001b: P=0, protože se započítává jen dolních 8 bitů
A (Auxiliary) Funguje přesně stejně jako CF, ale pro přenosy mezi 3. a 4. bitem. Má využití v matematických operacích v BCD a ASCII kódu. Tyto instrukce probírat nebudeme, takže AF vás nemusí zajímat. POZOR! Zde uvedený AF nemá nic společného s písmenem A použitým pro podmíněné skoky (ja/jna/jae/jnae). Pro AF neexistují podmíněné skoky. Z (Zero) S (Sign)
7z9
ZF je nastaven, právě když je výsledkem nula. Lze ho použít ve všech aritmetických instrukcích. Při porovnávání (cmp) je ZF nastaven právě když jsou si porovnávané hodnoty rovny (vyplývá to z toho, že cmp odpovídá odčítání sub). SF vždy obsahuje kopii nejvyššího bitu výsledku, tedy znaménko. SF=0 pro kladná čísla, SF=1 pro záporná čísla.
19.2.2007 7:51
Aleš Keprt - Elektronická učebnice Assembleru
http://student.inf.upol.cz/keprt/vyuka/asm/asm2.aspx
O (Overflow) OF je komplikovaný flag, nutný pro aritmetiku v doplňkovém kódu, čili pro práci se znaménkovými (signed) hodnotami. OF=1 když v operaci došlo k přenosu do nejvyššího bitu výsledku, ale nikoliv z nejvyššího bitu, nebo opačně. OF=0 ve všech ostatních případech. Otázkou je, nakolik je zde šťastné používat vazbu "přenos do..." při jednotlivých operacích. Možná by bylo z hlediska programátora jednodušší OF definovat takto: OF=1 když v operaci došlo k přetečení (signed) - čili signed výsledek je mimo rozsah platných hodnot OF=0 když k přetečení nedošlo, tedy signed výsledek se vejde do rozsahu platných hodnot Poznámka: Funkce OF je naprosto klíčová. Stojí na něm totiž porovnávání signed čísel a podmíněné skoky typu G-L. Pro programátora naštěstí většinou není klíčové přesně rozumět tomuto flagu, stačí chápat princip porovnávání pomocí cmp a skoků G-L. Rozdíl mezi CF a OF: Na obyčejném sčítání si ukážeme rozdíl signed a unsigned aritmetiky. operace 255+1: výsledek=0, CF=1, OF=0 operace 127+1: výsledek=(-)128, CF=0, OF=1 Úplně stejné rozdíly jsou i v dalších podmíněných skocích (dvojice podmíněných skoků jb-jl a ja-jg).
Řídící flagy (nekompletní seznam, ostatní používá jen operační systém): Pokud je nastaven, procesor se přeruší po vykonání každé instrukce. Používá se při krokování (což je určitý způsob ladění, protipirátské ochrany, anebo naopak jejího odstaňování). Při běžném programování nemá pro programátora význam. Použití: Nikdy nezapisujete ani nečtete. Většinou je TF=0, ale nespoléhejte na to!!! I (Interrupt) Nulování IF způsobí maskování (zakázání) všech přerušení typu MI (maskable interrupts). Operační systém by měl tento flag blokovat, tj. nedovolit aplikaci maskovat přerušení. Výjimkou je MS-DOS, kde se IF používá velmi často. Použití: Ve starém DOSu povolujete přerušení instrukcí sti, zakazujete instrukcí cli. Ve Windows je vždy IF=1, obě instrukce jsou privilegované, takže proces nemá právo hodnotu IF měnit (kromě DOS-VM, kde instrukce vyvolá výjimku a obsluha ji ignoruje a pokračuje v provádění kódu). D (Direction) DF=0 způsobí zvyšování adres při provádění blokových (řetězcových) instrukcí. DF=1 způsobí snižování adres při provádění blokových (řetězcových) instrukcí.
T (Trap)
POZOR! Platí nepsané pravidlo, že vyšší programovací jazyky vždy udržují DF=0. Pokud budete ve svém kódu potřebovat DF=1, nezapomeňte po provedení blokové operace vrátit stav zpět na DF=0. Použití: Udržjete stav DF=0. Flag nastavíte na jedničku pomocí std, potom co nejdříve zase vrátíte zpět na nulu pomocí cld,
8z9
19.2.2007 7:51
Aleš Keprt - Elektronická učebnice Assembleru
http://student.inf.upol.cz/keprt/vyuka/asm/asm2.aspx
alternativně můžete (a je to tak bazpečnější) použít pushf/popf. IOPL
Určuje úroveň oprávnění procesu. V DOSu pochopitelně nemá význam, ve Windows je vždy úroveň 3 (nejnižší), a to včetně operačního systému a jeho driverů, pouze s výjimkou kernelu (jádra systému, ke kterému se stejně nikdy nedostanete). Použití: IOPL nelze přímo měnit. (Viz literatura - IOPL lze měnit jen skokem do jiného segmentu, jehož deskriptor má IOPL vetší nebo rovno volajícímu procesu, anebo pomocí brány (gate) do procesu s nižším IOPL.) Zpět
9z9
19.2.2007 7:51