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

15. Odkazy na Internetu

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:

  1. Nejprve se vytiskne původní 32bitová hodnota předaná makru.
  2. Následně je otestován a posléze znegován bit se zvoleným indexem.
  3. Původní hodnota bitu je vypsána díky použití triku s instrukcí ADC, o níž jsme se zmínili výše.
  4. Nová 32bitová hodnota se znegovaným bitem je taktéž vypsána.
  5. 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

# Soubor Popis Odkaz do repositáře
1 main.s hlavní program pro GNU Assembler https://github.com/tisnik/presentations/blob/master/assembler/37_bsf/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/37_bsf/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/37_bsf/writeMessage.s
4 printHexNumber.s implementace makra a subrutiny pro převod hex2string https://github.com/tisnik/presentations/blob/master/assembler/37_bsf/printHexNumber.s
5 printDecimalNumber.s implementace makra a subrutiny pro převod decimal2string https://github.com/tisnik/presentations/blob/master/assembler/37_bsf/printDecimalNumber.s
6 assemble skript pro překlad na procesorech i386 https://github.com/tisnik/presentations/blob/master/assembler/37_bsf/assemble
7 disassemble skript pro disassembling https://github.com/tisnik/presentations/blob/master/assembler/37_bsf/disassemble

Čtvrtý demonstrační příklad: nalezení posledního nenulového bitu

# Soubor Popis Odkaz do repositáře
1 main.s hlavní program pro GNU Assembler https://github.com/tisnik/presentations/blob/master/assembler/38_bsr/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/38_bsr/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/38_bsr/writeMessage.s
4 printHexNumber.s implementace makra a subrutiny pro převod hex2string https://github.com/tisnik/presentations/blob/master/assembler/38_bsr/printHexNumber.s
5 printDecimalNumber.s implementace makra a subrutiny pro převod decimal2string https://github.com/tisnik/presentations/blob/master/assembler/38_bsr/printDecimalNumber.s
6 assemble skript pro překlad na procesorech i386 https://github.com/tisnik/presentations/blob/master/assembler/38_bsr/assemble
7 disassemble skript pro disassembling https://github.com/tisnik/presentations/blob/master/assembler/38_bsr/disassemble

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

  1. x86 Instruction Set Reference: BT
    http://x86.renejeschke.de/html/file_module_x86_id_22.html
  2. x86 Instruction Set Reference: BTC
    http://x86.renejeschke.de/html/file_module_x86_id_23.html
  3. x86 Instruction Set Reference: BTR
    http://x86.renejeschke.de/html/file_module_x86_id_24.html
  4. x86 Instruction Set Reference: BTS
    http://x86.renejeschke.de/html/file_module_x86_id_25.html
  5. x86 Instruction Set Reference: BSF
    http://x86.renejeschke.de/html/file_module_x86_id_19.html
  6. x86 Instruction Set Reference: BSR
    http://x86.renejeschke.de/html/file_module_x86_id_20.html
  7. x86 Instruction Set Reference: BSWAP
    http://x86.renejeschke.de/html/file_module_x86_id_21.html
  8. x86 Instruction Set Reference: XCHG
    http://x86.renejeschke.de/html/file_module_x86_id_328.html
  9. x86 Instruction Set Reference: SETcc
    http://x86.renejeschke.de/html/file_module_x86_id_288.html
  10. X86 Assembly/Arithmetic
    https://en.wikibooks.org/wiki/X86_Assembly/Arithmetic
  11. Art of Assembly – Arithmetic Instructions
    http://oopweb.com/Assembly/Documents/ArtOfAssembly/Volume/Chapter_6/CH06-2.html
  12. The GNU Assembler Tutorial
    http://tigcc.ticalc.org/doc/gnuasm.html
  13. The GNU Assembler – macros
    http://tigcc.ticalc.org/doc/gnuasm.html#SEC109
  14. ARM subroutines & program stack
    http://www.toves.org/books/armsub/
  15. Generating Mixed Source and Assembly List using GCC
    http://www.systutorials.com/240/generate-a-mixed-source-and-assembly-listing-using-gcc/
  16. Calling subroutines
    http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.kui0100a/armasm_cihcfigg.htm
  17. ARM Assembly Language Programming
    http://peter-cockerell.net/aalp/html/frames.html
  18. ASM Flags
    http://www.cavestory.org/guides/csasm/guide/asm_flags.html
  19. Status Register
    https://en.wikipedia.org/wiki/Status_register
  20. Intel x86 JUMP quick reference
    http://unixwiz.net/techtips/x86-jumps.html
  21. Linux assemblers: A comparison of GAS and NASM
    http://www.ibm.com/developerworks/library/l-gas-nasm/index.html
  22. Programovani v assembleru na OS Linux
    http://www.cs.vsb.cz/grygarek/asm/asmlinux.html
  23. Is it worthwhile to learn x86 assembly language today?
    https://www.quora.com/Is-it-worthwhile-to-learn-x86-assembly-language-today?share=1
  24. Why Learn Assembly Language?
    http://www.codeproject.com/Articles/89460/Why-Learn-Assembly-Language
  25. Is Assembly still relevant?
    http://programmers.stackexchange.com/questions/95836/is-assembly-still-relevant
  26. Why Learning Assembly Language Is Still a Good Idea
    http://www.onlamp.com/pub/a/onlamp/2004/05/06/writegreatcode.html
  27. Assembly language today
    http://beust.com/weblog/2004/06/23/assembly-language-today/
  28. Assembler: Význam assembleru dnes
    http://www.builder.cz/rubriky/assembler/vyznam-assembleru-dnes-155960cz
  29. Assembler pod Linuxem
    http://phoenix.inf.upol.cz/linux/prog/asm.html
  30. AT&T Syntax versus Intel Syntax
    https://www.sourceware.org/binutils/docs-2.12/as.info/i386-Syntax.html
  31. Linux Assembly website
    http://asm.sourceforge.net/
  32. Using Assembly Language in Linux
    http://asm.sourceforge.net/articles/linasm.html
  33. vasm
    http://sun.hasenbraten.de/vasm/
  34. vasm – dokumentace
    http://sun.hasenbraten.de/vasm/release/vasm.html
  35. The Yasm Modular Assembler Project
    http://yasm.tortall.net/
  36. 680×0:AsmOne
    http://www.amigacoding.com/index.php/680×0:AsmOne
  37. ASM-One Macro Assembler
    http://en.wikipedia.org/wiki/ASM-One_Macro_Assembler
  38. ASM-One pages
    http://www.theflamearrows.info/documents/asmone.html
  39. Základní informace o ASM-One
    http://www.theflamearrows.info/documents/asminfo.html
  40. Linux Syscall Reference
    http://syscalls.kernelgrok.com/
  41. Programming from the Ground Up Book – Summary
    http://savannah.nongnu.org/projects/pgubook/
  42. IBM System 360/370 Compiler and Historical Documentation
    http://www.edelweb.fr/Simula/
  43. IBM 700/7000 series
    http://en.wikipedia.org/wiki/IBM_700/7000_series
  44. IBM System/360
    http://en.wikipedia.org/wiki/IBM_System/360
  45. IBM System/370
    http://en.wikipedia.org/wiki/IBM_System/370
  46. Mainframe family tree and chronology
    http://www-03.ibm.com/ibm/history/exhibits/mainframe/mainframe_FT1.html
  47. 704 Data Processing System
    http://www-03.ibm.com/ibm/history/exhibits/mainframe/mainframe_PP704.html
  48. 705 Data Processing System
    http://www-03.ibm.com/ibm/history/exhibits/mainframe/mainframe_PP705.html
  49. The IBM 704
    http://www.columbia.edu/acis/history/704.html
  50. IBM Mainframe album
    http://www-03.ibm.com/ibm/history/exhibits/mainframe/mainframe_album.html
  51. Osmibitové muzeum
    http://osmi.tarbik.com/
  52. Tesla PMI-80
    http://osmi.tarbik.com/cssr/pmi80.html
  53. PMI-80
    http://en.wikipedia.org/wiki/PMI-80
  54. PMI-80
    http://www.old-computers.com/museum/computer.asp?st=1&c=1016
  55. The 6502 overflow flag explained mathematically
    http://www.righto.com/2012/12/the-6502-overflow-flag-explained.html
  56. X86 Opcode and Instruction Reference
    http://ref.x86asm.net/coder32.html