Assembler
Práce s textovými řetězci v assembleru (1)
Autor: Kub@z
Možná si říkáte proč píši o něčem trochu pokročilejším a ne o základech assembleru, když tady zatím žádný článek na toto téma není - je to ze dvou důvodů: 1) často si tu někdo stěžuje, že sem umisťujeme jenom návody typu hello world, které jsou úplně všude, ale nic jiného sem nenapíšeme. 2) kdybych psal o základech assembleru, musel bych napsat i něco o jeho historii apod. a to se mi nechce, jelikož jí neznám a nechce se mi ji hledat :))) (možná někdy v budoucnu něco na toto téma napíšu).
Tak... vraťme se k práci s textovými řetězci. Možná si říkáte, jak můžeme v assembleru pracovat s řetězci?? Vždyť je to tak primitivní jazyk... v něm přece řetězce nejsou. Odpověď zní: ale jsou. Samozřejmě, že práce s nimi bude o dost ložitější, než třeba práce se stringy v Pascalu, ale není to nic nepochopitelného a těžkého. Nejdřív si musíme vysvětlit, co to vlastně ten řetězec v assembleru je - je to posloupnost bytů, zakončená nulovým bytem - ne znakem "0", ale (Z jazyka C) znakem \0, tedy byte s hodnotou 0. Mluvíme zde vlastně o řetězích používaných v jazyce C, a jelikož je většina moderních operačních systémů napsaná v jazyce C (a tudíž používá tento typ řetězců), nemá smysl sy vymýšlet nějaké vlastní podivné struktury, které by byly v těchto systémech nepoužitelné. Zde je základní příklad programu v assembleru pracujícího z řetězcem:
org 100h
mov eax, retezec ; do eax offset retezce
zn:
cmp byte [cs:eax], 0 ; není už nulový byte?
je konec ; pokud ano, zkončíme
inc eax ; pokud ne, eax = eax + 1
jmp zn ; a znovu opakujeme testování
konec:
int 20h
retezec db "Ahoj lidi",0
Tento program jenom problikne a nic nevypíše. Po skončení bude eax ukazovat na konec řetězce (na nulový byte). Jen pro upřesnění - tento program je určen pro zkompilování jako .com soubor, tudíž předpokládá, že segmentové registry budou zprávně nastaveny a je proto také možné ukončit ho pomocí int 20h. V tomto programu jsme zatím nepoužili instrukce pro práci s řetězci. Procesor totiž obsahuje instrukce, které nám práci s řetězci usnadní. Všechny tyto instrukce nemají žádný operand. Syntaxe je následující: "jmeno_instikce+velikost" Toto vypadá trochu zvláštně - hned vysvětlím. Jedna z těchto instrukcí se jmenuje stos. Chceme ji použít s velikostí jeden byte. Použijeme tedy instrukci stosb. Stejně tak je možné použít instrukci stosw a stosd. A co tedy tato instrikce dělá?
Instrukce STOSb/w/d
Instrukce stosb nahraje hodnotu v al na adresu [es:di] (nebo es:edi, zavisí na režimu procesoru - to platí u všech následujících instrukcí). StoswNahraje na tuto adresu hodnotu v ax a stosd hodnotu v eax. Po zkončení sníží nebo zvýší instrukce stos(x) hodnotu registru (e)di o 1, 2 nebo 4 byty (podle použité instrukce). To, jestli se hodnota bude zvyšovat, nebo snižovat závisí na nastavení příznaku směru (DF). Instrukci např. stosw lze tedy rozepsat jako:
mov [es:di], ax
add di, 2 ; případně sub di, 2 - podle df
Příznak směru a instrukce STD a CLD
Nebudu rozebírat podrobnosti, stačí pouze říci, že po innstrukci cld (nastaví df na 0) se bude hodnota di (příp. si) zvyšovat o danou hodnotu a po instrukci std se bude di (si) snižovat (což moc často nevyužijeme).
Instrukce LODSb/w/d
Instrukce lods pracuje opačně, než instrukce stos. Hodnotu do paměti neukládá, ale naopak ji z paměti nahrává (zase závisí na zadané velikosti, jestli se hodnota uloží do al, ax, nebo eax a o kolik se změní registr si). Ano - předchozí text je správně - registr si. Intrukce lods totiž nahrává z adresy [ds:(e)si]
Instrukce CMPSb/w/d
Instrukce cmps funguje stejně jako normální cmp, ovšem porovnává hodnoty na adresách [es:(e)di] a [ds:(e)si]. Po zkončení provede změnu registrů (e)di a (e)si podle příznaku směru a velikosti "operandu". Např. cmpsb by bylo možné rozepsat jako:
cmp byte [es:di], byte [ds:si]
Což nám samozřejmě kompilátor nedovolí, protože není možné porovnávat dvě hosnoty v paměti, ale museli bychom použít pomocný registr, čímž by se program celkem zkomplikoval, zpomalil, a zvětšil, kdežto instrukce cmps zabírá pouze jeden byte.
Instrukce SCASb/w/d
Instrukce funguje podobně jako cmps, ale [es:(e)di] s al/ax/eax, ne s hodnotou v paměti. Po zkončení samozřejmě upraví obsah registru (e)di
Instrukce MOVSb/w/d
Instrukce zkopíruje hodnotu z [ds:(e)si] na adresu [es:(e)di]. Např. movsw lze tedy rozepsat jako:
mov word [ds:si], word [es:di]
Což nám samozřejmě překladač nevezme a bylo by nutné použít pomocný registr. Nakonec samozřejmě zase upraví registry (e)si a (e)di
Instrukce REP, REPZ a REPNZ
Zde nám už končí řetězcové instrukce s b/w/d na konci :). Instrukce rep a repz funguje obdobně, jako instrukce loop a loopz. Na rozdíl od těchto instrukcí však neskáče stále dokola na zadanou adresu, ale jsou to tzv. prefixové instrukce - vykonávají instrukci napsanou za sebou. Tedy např. "rep mov ax,0" lze rozepsat pomocí loop jako:
z: mov ax, 0 loop z
Jenom pro vysvětlení - pokud nevíte, co dělá instrukce loop. Tato instrukce skočí na danou adresu (návěští), pokud není v cx hodnota 0 a po každém průběhu sníží cx o 1. Takže:
xor ax, ax ; ax = 0 (stejné jako mov ax, 0)
mov cx, 5 ; cyklus se zopakuje 5x
n: inc ax
loop n
Po skončení programu bude v ax hodnota 5. Instrukce repz (obdobně jako loopz) funguje stejně, jako rep, ale podmínka je nejen nenulový cx, ale zároveň nesmí být nastaven příznak ZF. Podmínku o příznaku ZF lze negovat přidáním "n" - repnz. Zjednodušeně - repz je něco jako:
n:
instrukce
cmp cx,0
jnz n
Výhodně lze instrukci rep použít například s instrukcemi movsb nebo stosb. Instrukce repz se zase používá s instrukcemi scasb nebo cmpsb.
Závěrem
Tyto instrukce velice usnadňují práci s řetězci. Užitečné rutiny pro práci s řetězci si ukážeme v dalším dílu tohoto seriálu. Jen na ukázku: Takto by vypadal program z úvodu přepsaný pomocí těchto instrukcí:
org 100h
mov di, retezec ; do di offset retezce
xor al, al ; vynulujeme al
xor ecx, ecx
dec ecx ; v ecx máme nyní 0FFFFFFFFh - největší možný počet opakování
cld
repnz scasb ; najdeme konec
int 20h
retezec db "Ahoj lidi",0
Po skonční programu bude v es:di adresa konce řetězce. Tento program je o dost kratší, než ten z úvodu, co říkáte? :) (no... přesněji - je kratší o 5 bytů, ale u delších programů se to projeví daleko více :))
zdroj: soom.cz