V relativně velkém množství algoritmů se nepracuje pouze s bajty či ještě širšími slovy (16bitů, 32bitů, 64bitů), ale „pouze“ s jednotlivými bity. Z tohoto důvodu nalezneme u některých typů mikroprocesorových architektur speciální instrukce určené pro manipulaci s bity, konkrétně pro jejich testování, změnu hodnoty, vyhledávání jedničkového bitu v delším bitovém řetězci, prohození vybraných bitů v registru atd. Právě s tímto typem instrukcí dostupných na procesorech s architekturou i386 a x86-64 se seznámíme v dnešním článku.
Obsah
1. Použití assembleru v Linuxu: operace s jednotlivými bity, koncept Booleovského procesoru
2. Koncept plnohodnotného Booleovského procesoru versus bitové operace
3. Instrukce BT: test hodnoty vybraného bitu
4. První demonstrační příklad: test hodnoty vybraného bitu
5. Instrukce BTS, BTR a BTC: test hodnoty vybraného bitu s jeho nastavením či negací
6. Druhý demonstrační příklad: změna vybraných bitů bez použití masky
7. Instrukce BSF a BSR: hledání nenulového bitu v bitovém řetězci
8. Třetí demonstrační příklad: nalezení prvního nenulového bitu
9. Čtvrtý demonstrační příklad: nalezení posledního nenulového bitu
10. Instrukce XCHG a BSWAP: prohození bajtů ve slovech různé šířky
11. Pátý demonstrační příklad: použití instrukce BSWAP
12. Instrukce typu SETcc aneb podpora pro céčkové výrazy s logickými hodnotami
13. Pomocná makra a procedury použité v demonstračních příkladech
14. Repositář s demonstračními příklady
1. Použití assembleru v Linuxu: operace s jednotlivými bity, koncept Booleovského procesoru
V dnešním článku si popíšeme některé specifické instrukce, které je možné použít u všech soudobých mikroprocesorů s architekturou i386 i x86-64. Tyto instrukce jsou určeny pro manipulaci s jednotlivými bity, ať již se jedná o bity uložené v nějakém pracovním registru či o bity uložené přímo v operační paměti. V první skupině se nachází jediná instrukce určená pro test hodnoty vybraného bitu, ve skupině druhé nalezneme tři instrukce, které kromě testu ještě změní hodnotu bitu, ve třetí skupině můžeme najít instrukce, které dokážou vyhledat index prvního či naopak posledního nenulového bitu (předpokládá se, že registr neobsahuje samé nuly), následují instrukce určené pro prohození skupiny bitů v registru (little endian/big endian atd.) a konečně si v závěru článku popíšeme instrukce, které dokážou nastavit hodnotu ve vybraném registru na nulu či naopak na jedničku, a to na základě splnění či nesplnění nějaké podmínky. Chování některých dále popsaných instrukcí si otestujeme v pěti demonstračních příkladech, v nichž mj. využijeme i makra a subrutiny, které jsme si popsali v předchozím článku (výpis dekadické i hexadecimální hodnoty atd.).
Jen pro zajímavost a doplnění pro pamětníky: prakticky všechny instrukce, které budou popsány v následujících kapitolách (až na instrukce XCHG a BSWAP) byly do instrukčního souboru přidány „až“ v 32bitových procesorech Intel 80386, zatímco v řadě 8088, 8086, 80186 a 80286 je nenalezneme. Týká se to instrukcí BT, BTC, BTS, BTR, BSF, BSR i celé skupiny SETcc. Jedinou instrukcí přidanou později (konkrétně společně s procesory 80486) je instrukce BSWAP.
2. Koncept plnohodnotného Booleovského procesoru versus bitové operace
S konceptem takzvaných Booleovských procesorů má společnost Intel dlouhodobou zkušenost, protože ho využila například u známých a dodnes používaných osmibitových mikrořadičů řady Intel 8051 (MCS-51). Tímto termínem se označuje sada instrukcí, které dokážou pracovat na úrovni jednotlivých bitů a nikoli celých slov, a to (většinou) dokonce takovým způsobem, že i přístup do operační paměti či do speciálních řídicích registrů (SFR) periferních zařízení je prováděn po jednom bitu (například negace jediného bitu je rozdílná operace od přečtení bajtu/slova do akumulátoru, negace vybraného bitu a zápis celého bajtu/slova zpět). Jen pro zajímavost: na již zmíněném mikrořadiči MCS-51 je implementován úplný Booleovský procesor s jednobitovým akumulátorem (tím je příznak C/carry), sedmnácti instrukcemi, 128 bitovou oblastí RAM a 128 bitovou oblastí speciálních řídicích registrů (SFR).
U mikroprocesorů i386 a x86-64 je Booleovský procesor, resp. přesněji řečeno instrukce pro práci s jednotlivými bity, sice taktéž použit, ale nemá tak velký význam, a to z toho prostého důvodu, že architektura i386/x86-64 není (alespoň v naprosté většině případů) použita pro implementaci mikrořadiče s množstvím speciálních řídicích registrů, u nichž má význam číst či zapisovat jednotlivé bity. Taktéž datová sběrnice je zcela odlišná, takže instrukce BT, BTC atd. ve skutečnosti vždy přenáší celá 32bitová či 64bitová slova.
Poznámka: uvádí se, že na mikrořadiči Intel 8051 mohlo použití instrukcí Booleovského procesoru zkrátit programy až o 30%.
3. Instrukce BT: test hodnoty vybraného bitu
První strojovou instrukcí, s níž se v dnešním článku seznámíme, je instrukce nazvaná Bit Test. Zkratka této instrukce používaná v assemblerech, disassemblerech a debuggerech je BT, což je v rámci instrukční sady mikroprocesorů s architekturou i386 i x86-64 jedna z mála výjimek, protože většina instrukcí má třípísmennou či ještě delší zkratku (druhou výjimkou je instrukce nazvaná IN). Instrukce BT slouží k provedení testu vybraného bitu registru popř. bitu uloženého na specifikované adrese operační paměti. Na základě hodnoty tohoto bitu (0 či 1) je nastaven příznak CF (Carry Flag), který je následně možné použít v dalších instrukcích, například pro provedení podmíněného skoku atd. Existují celkem čtyři kombinace operandů vstupujících do instrukce BT:
První operand | Druhý operand (číslo bitu) |
---|---|
registr | registr |
registr | konstanta |
adresa | registr |
adresa | konstanta |
Poznámka: při adresování buňky paměti, v níž se nachází testovaný bit, je možné použít většinu adresovacích režimů s nimiž jsme se již setkali v předchozích částech tohoto seriálu (registr, registr+offset, registr1+registr2×konstanta+offset apod.).
4. První demonstrační příklad: test hodnoty vybraného bitu
V prvním demonstračním příkladu si ukážeme (poměrně umělé) použití instrukce BT. Pro zadané 32bitové hodnoty se vždy otestuje hodnota bitu s nejnižší váhou, hodnota bitu s indexem 1 a posléze hodnota bitu s váhou nejvyšší. Hodnoty těchto bitů jsou následně vypsány na standardní výstup, a to s použitím jednoduchého triku: vzhledem k tomu, že je výsledek testu zapsán do příznakového bitu Carry Flag, můžeme tento bit snadno přičíst k registru obsahujícího ASCII kód znaku "0" instrukcí ADC registr, 0 (ADC=Add With Carry Flag). Co se v tomto případě stane? Mohou nastat pouze dvě možnosti:
- Carry Flag není nastaven – v registru bude stále uložen ASCII kód znaku "0".
- Carry Flag je nastaven – v registru bude ASCII kód znaku "1" (v ASCII leží "1" ihned za "0").
Celý algoritmus určený pro test hodnoty vybraného bitu pracovního registru s jeho následným výpisem může vypadat následovně:
mov ebx, 32_bitová_konstanta
mov al, '0' # ASCII kód znaku, který se má "vepsat" do šablony
bt ebx, index_bitu # pokud je testovaný bit nastavený na jedničku - nastaví se i Carry Flag
adc al, 0 # přičtení Carry Flagu ke znaku "0"
mov [bitValueTemplate], al # zápis do šablony
writeMessage bitValueMessage, bitValueMessageLen # vytištění zprávy
Použitá šablona:
bitValueMessage:
.string "Bit value: " # prvni cast zpravy
bitValueTemplate: # druha cast zpravy ma vlastni navesti
.string "?\n" # otaznik bude prepsan
bitValueMessageLen = $ - bitValueMessage # delka zpravy
Následuje výpis celého zdrojového kódu dnešního prvního demonstračního příkladu:
# asmsyntax=as
# Program pro otestovani instrukce BT
# - pro zapis je pouzita "Intel" syntaxe.
#
# Autor: Pavel Tisnovsky
.intel_syntax noprefix
# Nacteni definice makra pro ukonceni aplikace
.include "exit.s"
# Nacteni maker pro (opakovany) tisk zpravy i prislusne subrutiny
.include "writeMessage.s"
# Nacteni makra pro vytisteni hexadecimalni 32bitove hodnoty
# spolecne s makrem je nactena i prislusna subrutina
.include "printHexNumber.s"
.macro testAndPrintBitValue word,bitIndex
mov ebx, \word
mov al, '0'
bt ebx, \bitIndex
adc al, 0
mov [bitValueTemplate], al
writeMessage bitValueMessage, bitValueMessageLen
.endm
#-----------------------------------------------------------------------------
.section .data
bitValueMessage:
.string "Bit value: " # prvni cast zpravy
bitValueTemplate: # druha cast zpravy ma vlastni navesti
.string "?\n" # otaznik bude prepsan
bitValueMessageLen = $ - bitValueMessage # delka zpravy
#-----------------------------------------------------------------------------
.section .bss
#-----------------------------------------------------------------------------
.section .text
.global _start # tento symbol ma byt dostupny i linkeru
_start:
printHexNumber 0x00000001
testAndPrintBitValue 0x00000001, 0
testAndPrintBitValue 0x00000001, 1
testAndPrintBitValue 0x00000001, 31
println
printHexNumber 0x80000000
testAndPrintBitValue 0x80000000, 0
testAndPrintBitValue 0x80000000, 1
testAndPrintBitValue 0x80000000, 31
println
printHexNumber 0xffffffff
testAndPrintBitValue 0xffffffff, 0
testAndPrintBitValue 0xffffffff, 1
testAndPrintBitValue 0xffffffff, 31
println
exit # ukonceni aplikace
Podívejme se na vypsané zprávy a tím pádem i výsledky testu bitů:
Hex value: 0x00000001
Bit value: 1 ; bit s nejnižší váhou
Bit value: 0 ; bit s indexem 1
Bit value: 0 ; bit s nejvyšší váhou
Hex value: 0x80000000
Bit value: 0 ; bit s nejnižší váhou
Bit value: 0 ; bit s indexem 1
Bit value: 1 ; bit s nejvyšší váhou
Hex value: 0xFFFFFFFF
Bit value: 1 ; bit s nejnižší váhou
Bit value: 1 ; bit s indexem 1
Bit value: 1 ; bit s nejvyšší váhou
Vidíme, že instrukce BT skutečně pracuje podle předpokladů.
5. Instrukce BTS, BTR a BTC: test hodnoty vybraného bitu s jeho nastavením či negací
K instrukci BT, kterou jsme si popsali v předchozím textu, existují ještě tři další varianty zapisované zkratkami BTS, BTC a BTR. Tyto instrukce provádí dvě operace. Nejdříve provedou test zvoleného bitu s uložením výsledku do příznakového bitu Carry Flag (CF), tj. stejnou činnost jako instrukce BT. Druhá operace spočívá ve změně testovaného bitu na základě zvolené instrukce; viz též následující tabulku:
# | Zkratka instrukce | Význam zkratky | Stručný popis |
---|---|---|---|
1 | BTC | Bit Test (and) Clear | testovaný bit je vynulován |
2 | BTS | Bit Test (and) Set | testovaný bit je nastaven na jedničku |
3 | BTR | Bit Test (and) Reverse | testovaný bit je znegován |
Poznámka1: povšimněte si, že společně s již popsanou instrukcí BT jsou pokryty všechny základní operace, které lze s vybraným bitem provést: ponechat jeho původní hodnotu, nastavit bit na nulu, nastavit bit na jedničku či jeho hodnotu znegovat.
Poznámka2: nejedná se o atomické operace, což znamená, že přímé použití těchto instrukcí k implementaci zámků či semaforů nemusí být u systému s větším množstvím procesorů korektní. Namísto toho lze použít specializované instrukce, například CMPXCHG atd.
6. Druhý demonstrační příklad: změna vybraných bitů bez použití masky
V dnešním druhém demonstračním příkladu je ukázáno použití instrukce BTC. Tato instrukce je volána v makru nazvaném testAndPrintBitValue, které provádí následující operace:
- Nejprve se vytiskne původní 32bitová hodnota předaná makru.
- Následně je otestován a posléze znegován bit se zvoleným indexem.
- Původní hodnota bitu je vypsána díky použití triku s instrukcí ADC, o níž jsme se zmínili výše.
- Nová 32bitová hodnota se znegovaným bitem je taktéž vypsána.
- V posledním kroku je provedeno odřádkování.
Celé makro vypadá následovně (instrukce PUSH a POP musíme použít pro úschovu hodnoty pracovního registru EBX):
.macro testAndPrintBitValue word,bitIndex
mov ebx, \word
printHexNumber ebx # vytisteni puvodni hodnoty
mov al, '0' # ASCII kod znaku, ktery se ma vepsat do sablony
btc ebx, \bitIndex # test bitu a posleze jeho negace
push ebx # nechceme prijit o hodnotu predanou do makra
adc al, 0 # pricteni Carry Flagu ke znaku "0"
mov [bitValueTemplate], al # zapis do sablony
writeMessage bitValueMessage, bitValueMessageLen # vypis celeho retezce na standardni vystup
pop ebx # obnoveni hodnoty ulozene na zasobnik instrukci push ebx
printHexNumber ebx # vytisteni nove hodnoty (po negaci vybraneho bitu)
println
.endm
Podívejme se nyní na výpis celého demonstračního příkladu:
# asmsyntax=as
# Program pro otestovani instrukce BTC
# - pro zapis je pouzita "Intel" syntaxe.
#
# Autor: Pavel Tisnovsky
.intel_syntax noprefix
# Nacteni definice makra pro ukonceni aplikace
.include "exit.s"
# Nacteni maker pro (opakovany) tisk zpravy i prislusne subrutiny
.include "writeMessage.s"
# Nacteni makra pro vytisteni hexadecimalni 32bitove hodnoty
# spolecne s makrem je nactena i prislusna subrutina
.include "printHexNumber.s"
.macro testAndPrintBitValue word,bitIndex
mov ebx, \word
printHexNumber ebx
mov al, '0'
btc ebx, \bitIndex
push ebx
adc al, 0
mov [bitValueTemplate], al
writeMessage bitValueMessage, bitValueMessageLen
pop ebx
printHexNumber ebx
println
.endm
#-----------------------------------------------------------------------------
.section .data
bitValueMessage:
.string "Bit value: " # prvni cast zpravy
bitValueTemplate: # druha cast zpravy ma vlastni navesti
.string "?\n" # otaznik bude prepsan
bitValueMessageLen = $ - bitValueMessage # delka zpravy
#-----------------------------------------------------------------------------
.section .bss
#-----------------------------------------------------------------------------
.section .text
.global _start # tento symbol ma byt dostupny i linkeru
_start:
testAndPrintBitValue 0x00000001, 0
testAndPrintBitValue 0x00000001, 1
testAndPrintBitValue 0x00000001, 31
testAndPrintBitValue 0x80000000, 0
testAndPrintBitValue 0x80000000, 1
testAndPrintBitValue 0x80000000, 31
testAndPrintBitValue 0xffffffff, 0
testAndPrintBitValue 0xffffffff, 1
testAndPrintBitValue 0xffffffff, 31
exit # ukonceni aplikace
Po spuštění příkladu se otestují hodnoty 0x00000001, 0x80000000 a 0xffffffff, přesněji řečeno bity s nejnižší váhou, bity s indexem 1 a bity s nejvyšší váhou:
Hex value: 0x00000001
Bit value: 1
Hex value: 0x00000000
Hex value: 0x00000001
Bit value: 0
Hex value: 0x00000003
Hex value: 0x00000001
Bit value: 0
Hex value: 0x80000001
Hex value: 0x80000000
Bit value: 0
Hex value: 0x80000001
Hex value: 0x80000000
Bit value: 0
Hex value: 0x80000002
Hex value: 0x80000000
Bit value: 1
Hex value: 0x00000000
Hex value: 0xFFFFFFFF
Bit value: 1
Hex value: 0xFFFFFFFE
Hex value: 0xFFFFFFFF
Bit value: 1
Hex value: 0xFFFFFFFD
Hex value: 0xFFFFFFFF
Bit value: 1
Hex value: 0x7FFFFFFF
7. Instrukce BSF a BSR: hledání nenulového bitu v bitovém řetězci
Vzhledem k tomu, že mikroprocesory s architekturou i386 a x86-64 patří mezi procesory typu CISC (Complex Instruction Set Computer), pravděpodobně nás nepřekvapí, že v jejich instrukční sadě nalezneme i poměrně exotické instrukce. Mezi tyto relativně málo používané instrukce patří i dvojice BSF a BSR. Zkratky těchto instrukcí vznikly ze sousloví Bit Scan Forward a Bit Scan Reverse, co to však znamená? Instrukce BSF (Bit Scan Forward) postupně prochází všemi bity vybraného registru (či buňky paměti) a hledá první nenulový bit. Do cílového registru posléze zapíše index tohoto bitu, přičemž bit s nejnižší váhou má index roven nule a nejvyšší bit má index buď 31 (32bitový operand) či 63 (64bitový operand). Zajímavé je, že pokud jsou všechny bity nulové, není výsledek této instrukce přesně definován a můžeme získat jakoukoli hodnotu. Je tedy nutné současně provést i test na nulovost testovaného registru či buňky paměti. Instrukce BSR (Bit Scan Reverse) pracuje podobným způsobem, ale vyhledávání začne od nejvyššího bitu směrem k bitu s nulovým indexem.
8. Třetí demonstrační příklad: nalezení prvního nenulového bitu
Ve třetím demonstračním příkladu využijeme minule popsaná makra určená pro tisk dekadické hodnoty i hexadecimální hodnoty, protože se nejdříve na standardní výstup hexadecimálně vytiskne vstupní 32bitová hodnota (uložená v pracovním registru EBX) a posléze se již dekadicky vytiskne index prvního nenulového bitu hledaný od bitu s indexem 0. Hlavní soubor s programem vypadá následovně:
# asmsyntax=as
# Program pro otestovani instrukce BSF
# - pro zapis je pouzita "Intel" syntaxe.
#
# Autor: Pavel Tisnovsky
.intel_syntax noprefix
# Nacteni definice makra pro ukonceni aplikace
.include "exit.s"
# Nacteni maker pro (opakovany) tisk zpravy i prislusne subrutiny
.include "writeMessage.s"
# Nacteni makra pro vytisteni hexadecimalni 32bitove hodnoty
# spolecne s makrem je nactena i prislusna subrutina
.include "printHexNumber.s"
# Nacteni makra pro vytisteni decimalni 32bitove hodnoty
# spolecne s makrem je nactena i prislusna subrutina
.include "printDecimalNumber.s"
#-----------------------------------------------------------------------------
.section .data
#-----------------------------------------------------------------------------
.section .bss
#-----------------------------------------------------------------------------
.section .text
.global _start # tento symbol ma byt dostupny i linkeru
_start:
mov ebx, 0x00000001
printHexNumber ebx
bsf eax, ebx
printDecimalNumber eax
println
mov ebx, 0x00000002
printHexNumber ebx
bsf eax, ebx
printDecimalNumber eax
println
mov ebx, 0x0000f000 # sada ctyr jednicek za sebou
printHexNumber ebx
bsf eax, ebx
printDecimalNumber eax
println
mov ebx, 0x00010000
printHexNumber ebx
bsf eax, ebx
printDecimalNumber eax
println
mov ebx, 0x80000000
printHexNumber ebx
bsf eax, ebx
printDecimalNumber eax
println
mov ebx, 0x80000001 # jednickovy je pouze nejnizsi a nejvyssi bit
printHexNumber ebx
bsf eax, ebx
printDecimalNumber eax
println
mov ebx, 0x00000000
printHexNumber ebx
bsf eax, ebx # nahodna hodnota
printDecimalNumber eax
println
exit # ukonceni aplikace
Podívejme se nyní na výsledek běhu tohoto programu. Zajímavá je především poslední vypsaná hodnota 2, která je sice podle specifikace náhodná, ale na mém CPU zcela konzistentní (na to se ovšem pochopitelně nedá spolehnout):
Hex value: 0x00000001
Decimal value: 0000000000
Hex value: 0x00000002
Decimal value: 0000000001
Hex value: 0x0000F000
Decimal value: 0000000012
Hex value: 0x00010000
Decimal value: 0000000016
Hex value: 0x80000000
Decimal value: 0000000031
Hex value: 0x80000001
Decimal value: 0000000000
Hex value: 0x00000000
Decimal value: 0000000002
9. Čtvrtý demonstrační příklad: nalezení posledního nenulového bitu
Čtvrtý demonstrační příklad je prakticky zcela totožný s příkladem předchozím, ovšem namísto instrukce BSF se pro hledání prvního nenulového bitu používá instrukce BSR. V některých případech budou výsledky shodné (32bitová hodnota s jediným nenulovým bitem), v dalších případech se výsledky budou podle očekávání odlišovat:
# asmsyntax=as
# Program pro otestovani instrukce BSR
# - pro zapis je pouzita "Intel" syntaxe.
#
# Autor: Pavel Tisnovsky
.intel_syntax noprefix
# Nacteni definice makra pro ukonceni aplikace
.include "exit.s"
# Nacteni maker pro (opakovany) tisk zpravy i prislusne subrutiny
.include "writeMessage.s"
# Nacteni makra pro vytisteni hexadecimalni 32bitove hodnoty
# spolecne s makrem je nactena i prislusna subrutina
.include "printHexNumber.s"
# Nacteni makra pro vytisteni decimalni 32bitove hodnoty
# spolecne s makrem je nactena i prislusna subrutina
.include "printDecimalNumber.s"
#-----------------------------------------------------------------------------
.section .data
#-----------------------------------------------------------------------------
.section .bss
#-----------------------------------------------------------------------------
.section .text
.global _start # tento symbol ma byt dostupny i linkeru
_start:
mov ebx, 0x00000001
printHexNumber ebx
bsr eax, ebx
printDecimalNumber eax
println
mov ebx, 0x00000002
printHexNumber ebx
bsr eax, ebx
printDecimalNumber eax
println
mov ebx, 0x0000f000 # sada ctyr jednicek za sebou
printHexNumber ebx
bsr eax, ebx
printDecimalNumber eax
println
mov ebx, 0x00010000
printHexNumber ebx
bsr eax, ebx
printDecimalNumber eax
println
mov ebx, 0x80000000
printHexNumber ebx
bsr eax, ebx
printDecimalNumber eax
println
mov ebx, 0x80000001 # jednickovy je pouze nejnizsi a nejvyssi bit
printHexNumber ebx
bsr eax, ebx
printDecimalNumber eax
println
mov ebx, 0x00000000
printHexNumber ebx
bsr eax, ebx # nahodna hodnota
printDecimalNumber eax
println
exit # ukonceni aplikace
Opět se podívejme na to, jaké hodnoty (indexy bitů) nalezne tento demonstrační příklad:
Hex value: 0x00000001
Decimal value: 0000000000
Hex value: 0x00000002
Decimal value: 0000000001
Hex value: 0x0000F000
Decimal value: 0000000015
Hex value: 0x00010000
Decimal value: 0000000016
Hex value: 0x80000000
Decimal value: 0000000031
Hex value: 0x80000001
Decimal value: 0000000031
Hex value: 0x00000000
Decimal value: 0000000002
10. Instrukce XCHG a BSWAP: prohození bajtů ve slovech různé šířky
Do čtvrté skupiny instrukcí, které si dnes alespoň ve stručnosti popíšeme, spadají instrukce, které dokážou ve zvoleném slovu prohodit obsah některých bitů. Do této skupiny patří instrukce XCHG (exchange) a BSWAP (byte swap). Instrukce XCHG je vlastně variantou instrukce MOV, protože taktéž přenáší data; zatímco však instrukce MOV provádí přenos jedním směrem zdroj→cíl, instrukce XCHG prohodí obsah zdroje a cíle. Speciální případ nastane, pokud jsou zdrojem a cílem dvě části jediného registru, například AH a AL. Instrukce XCHG AH, AL tedy vlastně prohodí nejnižších osm bitů registru AX (EAX, RAX) s bity s indexy 8 až 15. Instrukce BSWAP sice není tak univerzální, ovšem může být v některých případech velmi užitečná – prohazuje totiž všechny bajty ve zvoleném registru a to takovým způsobem, že převádí hodnotu z reprezentace little endian na big endian či zpět. Pokud instrukci BSWAP použijeme dvakrát za sebou se stejným registrem, hodnota tohoto registru se ve výsledku nezmění, protože se jednotlivé bajty opět vrátí na původní místo (to si ostatně ukážeme v demonstračním příkladu).
11. Pátý demonstrační příklad: použití instrukce BSWAP
Pátý a současně i dnešní poslední demonstrační příklad je vlastně velmi jednoduchý, protože se v něm použije instrukce BSWAP určená pro prohození bajtů v 32bitových slovech s hodnotami 0x12345678 a 0x000000ff. Vlastní prohození a následný návrat k původní hodnotě 32bitového slova může vypadat následovně:
mov eax, 0x12345678
printHexNumber eax # vytiskneme puvodni hodnotu
bswap eax
printHexNumber eax # vytiskneme slovo s prohozenymi bajty
bswap eax
printHexNumber eax # vytiskneme puvodni hodnotu
println
mov eax, 0x000000ff
printHexNumber eax # vytiskneme puvodni hodnotu
bswap eax
printHexNumber eax # vytiskneme slovo s prohozenymi bajty
bswap eax
printHexNumber eax # vytiskneme puvodni hodnotu
println
Příklad by měl po svém spuštění vypsat na standardní výstup následujících osm řádků:
Hex value: 0x12345678
Hex value: 0x78563412
Hex value: 0x12345678
Hex value: 0x000000FF
Hex value: 0xFF000000
Hex value: 0x000000FF
Podívejme se nyní na úplný výpis zdrojového kódu pátého příkladu:
# asmsyntax=as
# Program pro otestovani instrukce BSWAP
# - pro zapis je pouzita "Intel" syntaxe.
#
# Autor: Pavel Tisnovsky
.intel_syntax noprefix
# Nacteni definice makra pro ukonceni aplikace
.include "exit.s"
# Nacteni maker pro (opakovany) tisk zpravy i prislusne subrutiny
.include "writeMessage.s"
# Nacteni makra pro vytisteni hexadecimalni 32bitove hodnoty
# spolecne s makrem je nactena i prislusna subrutina
.include "printHexNumber.s"
#-----------------------------------------------------------------------------
.section .data
#-----------------------------------------------------------------------------
.section .bss
#-----------------------------------------------------------------------------
.section .text
.global _start # tento symbol ma byt dostupny i linkeru
_start:
mov eax, 0x12345678
printHexNumber eax # vytiskneme puvodni hodnotu
bswap eax
printHexNumber eax # vytiskneme slovo s prohozenymi bajty
bswap eax
printHexNumber eax # vytiskneme puvodni hodnotu
println
mov eax, 0x000000ff
printHexNumber eax # vytiskneme puvodni hodnotu
bswap eax
printHexNumber eax # vytiskneme slovo s prohozenymi bajty
bswap eax
printHexNumber eax # vytiskneme puvodni hodnotu
println
exit # ukonceni aplikace
12. Instrukce typu SETcc aneb podpora pro céčkové výrazy s logickými hodnotami
Poslední skupina instrukcí, s nimiž se dnes seznámíme, je poměrně rozsáhlá. Jedná se o instrukce nazývané SETcc (znaky cc se mění podle konkrétního typu instrukce), které dokážou nastavit hodnotu vybraného pracovního registru na nulu či jedničku podle toho, zda je nebo naopak není splněna nějaká podmínka. O jakou podmínku se může jednat? U těchto instrukcí se používají ty stejné podmínky, jaké nalezneme u podmíněných skoků, tedy testy příznakových bitů a jejich kombinací. Ostatně se podívejme na následující tabulku, v níž jsou instrukce patřící do této skupiny vypsány:
# | Instrukce | Testovaná podmínka |
---|---|---|
1 | SETA | CF=0 & ZF=0 |
2 | SETAE | CF=0 |
3 | SETB | CF=1 |
4 | SETBE | CF=1 | ZF=1 |
5 | SETC | CF=1 |
6 | SETE | ZF=1 |
7 | SETG | ZF=0 & SF=OF |
8 | SETGE | SF=OF |
9 | SETL | SF≠OF |
10 | SETLE | ZF=1 | SF≠OF |
11 | SETNA | CF=1 | ZF=1 |
12 | SETNAE | CF=1 |
13 | SETNB | CF=0 |
14 | SETNBE | CF=0 & ZF=0 |
15 | SETNC | CF=0 |
16 | SETNE | ZF=0 |
17 | SETNG | ZF=1 | SF≠OF |
18 | SETNGE | SF≠OF |
19 | SETNL | SF=OF |
20 | SETNLE | ZF=0 & SF=OF |
21 | SETNO | OF=0 |
22 | SETNP | PF=0 |
23 | SETNS | SF=0 |
24 | SETNZ | ZF=0 |
25 | SETO | OF=1 |
26 | SETP | PF=1 |
27 | SETPE | PF=1 |
28 | SETPO | PF=0 |
29 | SETS | SF=1 |
30 | SETZ | ZF=1 |
13. Pomocná makra a procedury použité v demonstračních příkladech
Demonstrační příklady popsané v předchozích kapitolách používají makra a subrutiny (procedury), s nimiž jsme se již seznámili minule. Tyto makra a subrutiny jsou uloženy v samostatných souborech přidávaných do hlavního programu s využitím direktivy .include.
exit.s
# asmsyntax=as
# Makro pro ukonceni procesu v Linuxu.
# - pro zapis je pouzita "Intel" syntaxe.
#
# Autor: Pavel Tisnovsky
sys_exit = 1 # cislo syscallu pro ukonceni procesu
# Deklarace makra pro ukonceni aplikace
.macro exit
mov eax, sys_exit # cislo sycallu pro funkci "exit"
mov ebx, 0 # exit code = 0
int 0x80 # volani Linuxoveho kernelu
.endm
writeMessage.s
# asmsyntax=as
# Makro pro tisk zpravy na standardni vystup.
# - pro zapis je pouzita "Intel" syntaxe.
#
# Autor: Pavel Tisnovsky
# Linux kernel system call table
sys_write = 4
std_output = 1
# Deklarace makra pro vytisteni zpravy na standardni vystup
.macro writeMessage message,messageLength
mov ecx, offset \message # adresa retezce, ktery se ma vytisknout
mov edx, \messageLength # pocet znaku, ktere se maji vytisknout
call write_message # vytisknout zpravu "Zero flag not set"
.endm
# Podprogram pro vytisteni zpravy na standardni vystup
# Ocekava se, ze v ecx bude adresa zpravy a v edx jeji delka
write_message:
mov eax, sys_write # cislo syscallu pro funkci "write"
mov ebx, std_output # standardni vystup
int 0x80
ret
# Deklarace makra pro vytisteni znaku konce radku (provede se tedy odradkovani)
.macro println
writeMessage printlnMessage,printlnLength
.endm
#-----------------------------------------------------------------------------
.section .data
# Miniretezec pouzivany makrem println
printlnMessage:
.string "\n"
printlnLength = $ - printlnMessage
printHexNumber.s
# asmsyntax=as
# Makro pro pripravu a tisk hexadecimalni hodnoty na standardni vystup.
# - pro zapis je pouzita "Intel" syntaxe.
#
# Autor: Pavel Tisnovsky
.intel_syntax noprefix
# Makro pro vypis 32bitove hexadecimalni hodnoty na standardni vystup
# Jedinym parametrem makra je hodnota (konstanta)
.macro printHexNumber value
pusha # uschovat vsechny registry na zasobnik
mov edx, \value # hodnotu pro tisk ulozit do registru EDX
mov ebx, offset hexValueTemplate # adresu pro retezec ulozit do registru EBX
call hex2string # zavolani prislusne subrutiny pro prevod na string
writeMessage hexValueMessage, hexValueMessageLen # retezec je naplnen, tak ho muzeme vytisknout
popa # obnovit obsah vsech registru
.endm
#-----------------------------------------------------------------------------
.section .data
hexValueMessage:
.string "Hex value: 0x" # prvni cast zpravy
hexValueTemplate: # druha cast zpravy ma vlastni navesti
.string "????????\n" # otazniky budou prepsany
hexValueMessageLen = $ - hexValueMessage # delka zpravy
#-----------------------------------------------------------------------------
.section .text
# Subrutina urcena pro prevod 32bitove hexadecimalni hodnoty na retezec
# Vstup: EDX - hodnota, ktera se ma prevest na retezec
# EBX - adresa jiz drive alokovaneho retezce (resp. osmice bajtu)
hex2string:
mov cl, 8 # pocet opakovani smycky
print_one_digit: rol edx, 4 # rotace doleva znamena, ze se do spodnich 4 bitu nasune dalsi cifra
mov al, dl # nechceme porusit obsah vstupni hodnoty v EDX, proto pouzijeme AL
and al, 0x0f # maskovani, potrebujeme pracovat jen s jednou cifrou
cmp al, 10 # je cifra vetsi nebo rovna 10?
jl store_digit # neni, pouze prevest 0..9 na ASCII hodnotu '0'..'9'
alpha_digit: add al, 'A'-10-'0' # prevod hodnoty 10..15 na znaky 'A'..'F'
store_digit: add al, '0'
mov byte ptr [ebx], al # ulozeni cifry do retezce
inc ebx # dalsi ulozeni v retezci o znak dale
dec cl # snizeni pocitadla smycky
jnz print_one_digit # a opakovani smycky, dokud se nedosahlo nuly
ret # navrat ze subrutiny
printDecimalNumber.s
# asmsyntax=as
# Makro pro pripravu a tisk desitkove hodnoty na standardni vystup.
# - pro zapis je pouzita "Intel" syntaxe.
#
# Autor: Pavel Tisnovsky
.intel_syntax noprefix
# Makro pro vypis 32bitove desitkove hodnoty na standardni vystup
# Jedinym parametrem makra je hodnota (konstanta)
.macro printDecimalNumber value
pusha # uschovat vsechny registry na zasobnik
mov eax, \value # hodnotu pro tisk ulozit do registru EAX
mov ebx, offset decimalValueTemplate # adresu pro retezec ulozit do registru EBX
call decimal2string # zavolani prislusne subrutiny pro prevod na string
writeMessage decimalValueMessage, decimalValueMessageLen # retezec je naplnen, tak ho muzeme vytisknout
popa # obnovit obsah vsech registru
.endm
#-----------------------------------------------------------------------------
.section .data
decimalValueMessage:
.string "Decimal value: " # prvni cast zpravy
decimalValueTemplate: # druha cast zpravy ma vlastni navesti
.string "??????????\n" # otazniky budou prepsany (musi jich byt presne deset!)
decimalValueMessageLen = $ - decimalValueMessage # delka zpravy
#-----------------------------------------------------------------------------
.section .text
# Subrutina urcena pro prevod 32bitove desitkove hodnoty na retezec
# Vstup: EDX - hodnota, ktera se ma prevest na retezec
# EBX - adresa jiz drive alokovaneho retezce (resp. minimalne deseti bajtu)
decimal2string:
mov ecx, 10 # celkovy pocet zapisovanych cifer/znaku
mov edi, ecx # instrukce DIV vyzaduje deleni registrem, pouzijme tedy EDI
next_digit:
xor edx, edx # delenec je dvojice EDX:EAX, vynulujeme tedy horni registr EDX
div edi # deleni hodnoty ulozene v EDX:EAX deseti (delitelem je EDI)
# vysledek se ulozi do EAX, zbytek do EDX
# pri deleni deseti je jistota, ze zbytek je jen cislo 0..9
add dl, '0' # prevod hodnoty 0..9 na znak '0'-'9'
mov byte ptr [ebx+ecx-1], dl # zapis retezce (od posledniho znaku)
dec ecx # presun na predchozi znak v retezci a soucasne snizeni hodnoty pocitadla
jnz next_digit # uz jsme dosli k poslednimu cislu?
ret # navrat ze subrutiny
14. Repositář s demonstračními příklady
Všech pět dnes popisovaných demonstračních příkladů bylo, podobně jako ve všech předchozích částech tohoto seriálu, společně s podpůrnými skripty určenými pro jejich překlad či naopak pro disassembling, uloženo do GIT repositáře dostupného na adrese https://github.com/tisnik/presentations/. Všechny příklady jsou určeny pro GNU Assembler a používají Intel syntaxi, která je pro mnoho programátorů čitelnější, než původní AT&T syntaxe. Následují tabulky obsahující odkazy na zdrojové kódy příkladů i na již zmíněné podpůrné skripty:
První demonstrační příklad: test hodnoty vybraného bitu
# | Soubor | Popis | Odkaz do repositáře |
---|---|---|---|
1 | main.s | hlavní program pro GNU Assembler | https://github.com/tisnik/presentations/blob/master/assembler/35_bit_test/main.s |
2 | exit.s | program pro GNU Assembler, který se vkládá do prvního souboru | https://github.com/tisnik/presentations/blob/master/assembler/35_bit_test/exit.s |
3 | writeMessage.s | program pro GNU Assembler, který se vkládá do prvního souboru | https://github.com/tisnik/presentations/blob/master/assembler/35_bit_test/writeMessage.s |
4 | printHexNumber.s | implementace makra a subrutiny pro převod hex2string | https://github.com/tisnik/presentations/blob/master/assembler/35_bit_test/printHexNumber.s |
5 | assemble | skript pro překlad na procesorech i386 | https://github.com/tisnik/presentations/blob/master/assembler/35_bit_test/assemble |
6 | disassemble | skript pro disassembling | https://github.com/tisnik/presentations/blob/master/assembler/35_bit_test/disassemble |
Druhý demonstrační příklad: změna vybraných bitů bez použití masky
# | Soubor | Popis | Odkaz do repositáře |
---|---|---|---|
1 | main.s | hlavní program pro GNU Assembler | https://github.com/tisnik/presentations/blob/master/assembler/36_btc/main.s |
2 | exit.s | program pro GNU Assembler, který se vkládá do prvního souboru | https://github.com/tisnik/presentations/blob/master/assembler/36_btc/exit.s |
3 | writeMessage.s | program pro GNU Assembler, který se vkládá do prvního souboru | https://github.com/tisnik/presentations/blob/master/assembler/36_btc/writeMessage.s |
4 | printHexNumber.s | implementace makra a subrutiny pro převod hex2string | https://github.com/tisnik/presentations/blob/master/assembler/36_btc/printHexNumber.s |
5 | assemble | skript pro překlad na procesorech i386 | https://github.com/tisnik/presentations/blob/master/assembler/36_btc/assemble |
6 | disassemble | skript pro disassembling | https://github.com/tisnik/presentations/blob/master/assembler/36_btc/disassemble |
Třetí demonstrační příklad: nalezení prvního nenulového bitu
Čtvrtý demonstrační příklad: nalezení posledního nenulového bitu
Pátý demonstrační příklad: použití instrukce BSWAP
# | Soubor | Popis | Odkaz do repositáře |
---|---|---|---|
1 | main.s | hlavní program pro GNU Assembler | https://github.com/tisnik/presentations/blob/master/assembler/39_bswap/main.s |
2 | exit.s | program pro GNU Assembler, který se vkládá do prvního souboru | https://github.com/tisnik/presentations/blob/master/assembler/39_bswap/exit.s |
3 | writeMessage.s | program pro GNU Assembler, který se vkládá do prvního souboru | https://github.com/tisnik/presentations/blob/master/assembler/39_bswap/writeMessage.s |
4 | printHexNumber.s | implementace makra a subrutiny pro převod hex2string | https://github.com/tisnik/presentations/blob/master/assembler/39_bswap/printHexNumber.s |
5 | assemble | skript pro překlad na procesorech i386 | https://github.com/tisnik/presentations/blob/master/assembler/39_bswap/assemble |
6 | disassemble | skript pro disassembling | https://github.com/tisnik/presentations/blob/master/assembler/39_bswap/disassemble |
15. Odkazy na Internetu
- x86 Instruction Set Reference: BT
http://x86.renejeschke.de/html/file_module_x86_id_22.html - x86 Instruction Set Reference: BTC
http://x86.renejeschke.de/html/file_module_x86_id_23.html - x86 Instruction Set Reference: BTR
http://x86.renejeschke.de/html/file_module_x86_id_24.html - x86 Instruction Set Reference: BTS
http://x86.renejeschke.de/html/file_module_x86_id_25.html - x86 Instruction Set Reference: BSF
http://x86.renejeschke.de/html/file_module_x86_id_19.html - x86 Instruction Set Reference: BSR
http://x86.renejeschke.de/html/file_module_x86_id_20.html - x86 Instruction Set Reference: BSWAP
http://x86.renejeschke.de/html/file_module_x86_id_21.html - x86 Instruction Set Reference: XCHG
http://x86.renejeschke.de/html/file_module_x86_id_328.html - x86 Instruction Set Reference: SETcc
http://x86.renejeschke.de/html/file_module_x86_id_288.html - X86 Assembly/Arithmetic
https://en.wikibooks.org/wiki/X86_Assembly/Arithmetic - Art of Assembly - Arithmetic Instructions
http://oopweb.com/Assembly/Documents/ArtOfAssembly/Volume/Chapter_6/CH06-2.html - The GNU Assembler Tutorial
http://tigcc.ticalc.org/doc/gnuasm.html - The GNU Assembler - macros
http://tigcc.ticalc.org/doc/gnuasm.html#SEC109 - ARM subroutines & program stack
http://www.toves.org/books/armsub/ - Generating Mixed Source and Assembly List using GCC
http://www.systutorials.com/240/generate-a-mixed-source-and-assembly-listing-using-gcc/ - Calling subroutines
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.kui0100a/armasm_cihcfigg.htm - ARM Assembly Language Programming
http://peter-cockerell.net/aalp/html/frames.html - ASM Flags
http://www.cavestory.org/guides/csasm/guide/asm_flags.html - Status Register
https://en.wikipedia.org/wiki/Status_register - Intel x86 JUMP quick reference
http://unixwiz.net/techtips/x86-jumps.html - Linux assemblers: A comparison of GAS and NASM
http://www.ibm.com/developerworks/library/l-gas-nasm/index.html - Programovani v assembleru na OS Linux
http://www.cs.vsb.cz/grygarek/asm/asmlinux.html - Is it worthwhile to learn x86 assembly language today?
https://www.quora.com/Is-it-worthwhile-to-learn-x86-assembly-language-today?share=1 - Why Learn Assembly Language?
http://www.codeproject.com/Articles/89460/Why-Learn-Assembly-Language - Is Assembly still relevant?
http://programmers.stackexchange.com/questions/95836/is-assembly-still-relevant - Why Learning Assembly Language Is Still a Good Idea
http://www.onlamp.com/pub/a/onlamp/2004/05/06/writegreatcode.html - Assembly language today
http://beust.com/weblog/2004/06/23/assembly-language-today/ - Assembler: Význam assembleru dnes
http://www.builder.cz/rubriky/assembler/vyznam-assembleru-dnes-155960cz - Assembler pod Linuxem
http://phoenix.inf.upol.cz/linux/prog/asm.html - AT&T Syntax versus Intel Syntax
https://www.sourceware.org/binutils/docs-2.12/as.info/i386-Syntax.html - Linux Assembly website
http://asm.sourceforge.net/ - Using Assembly Language in Linux
http://asm.sourceforge.net/articles/linasm.html - vasm
http://sun.hasenbraten.de/vasm/ - vasm – dokumentace
http://sun.hasenbraten.de/vasm/release/vasm.html - The Yasm Modular Assembler Project
http://yasm.tortall.net/ - 680x0:AsmOne
http://www.amigacoding.com/index.php/680x0:AsmOne - ASM-One Macro Assembler
http://en.wikipedia.org/wiki/ASM-One_Macro_Assembler - ASM-One pages
http://www.theflamearrows.info/documents/asmone.html - Základní informace o ASM-One
http://www.theflamearrows.info/documents/asminfo.html - Linux Syscall Reference
http://syscalls.kernelgrok.com/ - Programming from the Ground Up Book - Summary
http://savannah.nongnu.org/projects/pgubook/ - IBM System 360/370 Compiler and Historical Documentation
http://www.edelweb.fr/Simula/ - IBM 700/7000 series
http://en.wikipedia.org/wiki/IBM_700/7000_series - IBM System/360
http://en.wikipedia.org/wiki/IBM_System/360 - IBM System/370
http://en.wikipedia.org/wiki/IBM_System/370 - Mainframe family tree and chronology
http://www-03.ibm.com/ibm/history/exhibits/mainframe/mainframe_FT1.html - 704 Data Processing System
http://www-03.ibm.com/ibm/history/exhibits/mainframe/mainframe_PP704.html - 705 Data Processing System
http://www-03.ibm.com/ibm/history/exhibits/mainframe/mainframe_PP705.html - The IBM 704
http://www.columbia.edu/acis/history/704.html - IBM Mainframe album
http://www-03.ibm.com/ibm/history/exhibits/mainframe/mainframe_album.html - Osmibitové muzeum
http://osmi.tarbik.com/ - Tesla PMI-80
http://osmi.tarbik.com/cssr/pmi80.html - PMI-80
http://en.wikipedia.org/wiki/PMI-80 - PMI-80
http://www.old-computers.com/museum/computer.asp?st=1&c=1016 - The 6502 overflow flag explained mathematically
http://www.righto.com/2012/12/the-6502-overflow-flag-explained.html - X86 Opcode and Instruction Reference
http://ref.x86asm.net/coder32.html