Práce s textovými řetězci v assembleru (1)
Zdroj: SOOM.cz [ISSN 1804-7270]
Autor: Kub@z
Datum: 15.8.2004
Hodnocení/Hlasovalo: 2.56/9
Tento článek vám vysvětlí používání instrukcí pro práci s řetězci (STOSx, SCASx apod.)...
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 :))