Ve čtvrté části článku o použití assembleru (nejenom) v Linuxu se budeme zabývat technikami využívanými pro tvorbu podmínek a taktéž různých typů programových smyček v assembleru, což je problematika, která na většině v současnosti používaných mikroprocesorových architektur vyžaduje práci s takzvanými příznaky (či příznakovými bity).

Obsah

1. Podmíněné a nepodmíněné skoky jako základ pro realizaci rozvětvení a programových smyček

2. Strojové instrukce určené pro provedení skoku na architekturách i386 a x86_64

3. Základní příznakové bity na architekturách i386 a x86_64

4. Vybrané instrukce pro podmíněné skoky založené na testování příznakových bitů

5. Použití instrukcí DEC a JZN pro implementaci počítané programové smyčky

6. Odlišná realizace počítané smyčky s instrukcemi DEC, JZ a JMP

7. Praktický příklad – jednoduchá počítaná programová smyčka

8. Další praktický příklad – počítaná programová smyčka s testem na začátku

9. Použití instrukce CMP v součinnosti s podmíněným skokem

10. Praktický příklad – počítaná smyčka s volitelným začátkem a koncem

11. Repositář s demonstračními příklady

12. Odkazy na Internetu

1. Podmíněné a nepodmíněné skoky jako základ pro realizaci rozvětvení a programových smyček

Velmi důležitým typem strojových instrukcí, které v různé podobě najdeme prakticky u všech modelů mikroprocesorů (resp. přesněji řečeno u mikroprocesorů všech dnes rozšířených mikroprocesorových architektur), jsou instrukce provádějící skoky na nějakou adresu v operační paměti. Implementace skoku není, alespoň na první pohled a u jednodušších architektur bez instrukční pipeline, vlastně nijak složitá, protože se v případě použití absolutní adresy dosadí hodnota z operačního kódu instrukce do registru PC a v případě použití relativní adresy se tato hodnota (nazývaná někdy poněkud nepřesně offset) přičte k aktuální hodnotě registru PC. Relativní adresa je v tomto případě v kódu instrukce uložena se znaménkem, proto se skok může provést dozadu i dopředu (ostatně právě použití relativní adresy uvidíme v dále popisovaných demonstračních příkladech).

Skoky většinou dělíme podle jednoho kritéria (formy zápisu adresy) na absolutní a relativní a podle kritéria druhého (za jakým okolností se skok provede) na skoky podmíněné a nepodmíněné. V závislosti na použité instrukční sadě jsou možné různé kombinace, typicky však u většiny mikroprocesorů nalezneme kombinace nepodmíněný absolutní skok, nepodmíněný relativní skok a podmíněný relativní skok. Skoky nepodmíněné jsou jednodušší a svou podstatou odpovídají příkazu goto známého z některých programovacích jazyků a také z mnoha článků, ve kterých autoři mnohdy bez hlubšího zamyšlení se nad původní myšlenkou opakují, že by se goto nemělo při strukturovaném programování používat :-). V assembleru se však skoky vesele používají, neboť právě pomocí nich se vytváří základní konstrukce strukturovaného programování – podmínky a programové smyčky.

2. Strojové instrukce určené pro provedení skoku na architekturách i386 a x86_64

U architektury mikroprocesorů 32bitové řady i386 a taktéž 64bitové řady x86-64 je základní strojovou instrukcí určenou pro provedení nepodmíněného skoku instrukce nazvaná jednoduše a přímočaře JMP (což je, jak jste zajisté zjistili, mnemotechnická zkratka slova jump). V assembleru většinou za mnemotechnickou zkratkou jména instrukce následuje návěští (label), z něhož assembler odvodí reálnou adresu. Ukažme si typický špagetový kód vznikající nadbytečným použitím této instrukce:

# asmsyntax=as
 
# Ukazka spagetoveho kodu s instrukci typu JMP
# - pouzita je "Intel" syntaxe.
#
# Autor: Pavel Tisnovsky
 
.intel_syntax noprefix
 
 
 
# Linux kernel system call table
sys_exit=1
 
 
 
#-----------------------------------------------------------------------------
.section .data
 
 
 
#-----------------------------------------------------------------------------
.section .bss
 
 
 
#-----------------------------------------------------------------------------
.section .text
        .global _start               # tento symbol ma byt dostupny i linkeru
 
_start:
        jmp   l1
l4:     
        mov   eax, sys_exit          # cislo sycallu pro funkci "exit"
        mov   ebx, 0                 # exit code = 0
        int   0x80                   # volani Linuxoveho kernelu

l2:     jmp   l3

l1:     jmp   l2

l3:     jmp   l4

Překlad vypadá takto (skoky jsou zde relativní a tudíž velmi krátké – celá instrukce má délku pouhé dva bajty):

0000000000400078 <.text>
  400078:       eb 0e                   jmp    0x400088 ; relativní skok vpřed
  40007a:       b8 01 00 00 00          mov    eax,0x1
  40007f:       bb 00 00 00 00          mov    ebx,0x0
  400084:       cd 80                   int    0x80
  400086:       eb 02                   jmp    0x40008a ; relativní skok o 3 adresy dál
  400088:       eb fc                   jmp    0x400086 ; relativní skok zpět (druhá hodnota je se znaménkem)
  40008a:       eb ee                   jmp    0x40007a ; relativní skok zpět (druhá hodnota je se znaménkem)

3. Základní příznakové bity na architekturách i386 a x86_64

Alternativně je možné použít i další způsoby adresování, čímž se například implementuje tabulka skoků (jedna z možných realizací stavového automatu) atd., ovšem tyto techniky pro účely dnešního článku prozatím nepotřebujeme znát. Mnohem zajímavější jsou podmíněné skoky, které se při programování v assembleru či ve strojovém kódu používají pro implementaci programových smyček while, do-while, for a taktéž konstrukcí typu if-then-else atd. Podmíněný skok je proveden či naopak neproveden na základě nějaké podmínky. Vzhledem k tomu, že pracujeme na té nejnižší programové úrovni, tj. na úrovni strojových instrukcí, není samozřejmě možné podmínku definovat nějakým složitým a sofistikovaným způsobem – musí se jednat o operaci, kterou mikroprocesor dokáže jednoduše a především dostatečně rychle zpracovat (i přesto představují skoky úzké místo v programech).

Z tohoto prostého důvodu – podmínky musí být realizovány dostatečně jednoduchým způsobem pro snadnou implementaci na čipu – jsou na mikroprocesorových architekturách i386 a x86-64 podmínky založeny na testování jednoho z takzvaných příznakových bitů, negací těchto bitů či dokonce jejich kombinací. Pokud z důvodu zjednodušení výkladu celé relativně rozsáhlé problematiky budeme ignorovat některé speciálnější příznaky a především pak rozdíly mezi hodnotami bez znaménka (unsigned) a se znaménkem (signed), můžeme zpočátku použít především příznaky nazvané Carry flag, Sign flag a Zero flag, tj. příznak přenosu, příznak záporného výsledku a příznak nulovosti. Význam těchto příznakových bitů se shrnut v následující tabulce:

Příznak Význam zkratky Poznámka
ZF zero flag výsledek předchozí operace je nulový
CF carry flag přenos (bezznaménková aritmetika)
SF sign flag výsledek je záporný (nastaven nejvyšší bit bajtu či slova)

V případě, že alespoň prozatím nebudeme brát v úvahu další příznakové bity, existuje šest základních variant podmíněného skoku, které jsou vypsány v tabulce v navazující kapitole. Ve chvíli, kdy podmínka není splněna (tj. testovaný příznakový bit má opačnou hodnotu než očekávanou), není skok proveden, tj. mikroprocesor instrukci skoku v podstatě ignoruje a pokračuje v načtení instrukce uvedené ihned za skokem (to, že mikroprocesor instrukci skoku ignoruje samozřejmě platí jen z pohledu logiky vytvářeného programu; samotné provedení instrukce muselo proběhnout a tudíž se program o několik strojových taktů pozdržel – i neprovedení podmíněného kroku si tedy vyžádalo svou cenu).

4. Vybrané instrukce pro podmíněné skoky založené na testování příznakových bitů

Strojové instrukce určené pro provedení podmíněných skoků jsou ve své základní variantě (existují pro ně totiž i jmenné aliasy) pojmenovány jednoduše a přímočaře – začínají písmenem J (jump), za nímž následuje volitelné písmeno N (negace) a jednoznaková zkratka příznaku. Instrukce JNC tedy znamená „proveď skok, pokud příznak Carry není nastaven“, zatímco instrukce JZ znamená „proveď skok pouze při nastavení příznaku Zero“:

Mnemotechnická zkratka instrukce Význam instrukce podmíněného skoku
JC podmíněný skok za předpokladu, že je nastaven příznak přenosu (Carry flag)
JNC podmíněný skok za předpokladu, že je vynulován příznak přenosu (Carry flag)
JZ podmíněný skok za předpokladu, že je nastaven příznak nulovosti (Zero flag)
JNZ podmíněný skok za předpokladu, že je vynulován příznak nulovosti (Zero flag)
JS podmíněný skok za předpokladu, že je nastaven příznak záporného výsledku (Sign flag)
JNS podmíněný skok za předpokladu, že je vynulován příznak záporného výsledku (Sign flag)

Jmenné aliasy:

Instrukce Alias
JZ JE
JNZ JNE
JC JB, JNAE
JNC JNB, JAE
JS nemá alias
JNS nemá alias

5. Použití instrukcí DEC a JZN pro implementaci počítané programové smyčky

V předchozí kapitole jsme si řekli, že podmíněné relativní skoky jsou použity pro implementaci programových smyček a podmínek. K tomuto účelu se podmíněné skoky prakticky vždy vhodně kombinují s některou instrukcí, která modifikuje jeden příznakový bit či dokonce větší množství příznakových bitů. Například jednoduchou počítanou programovou smyčku by bylo možné implementovat s využitím kombinace strojových instrukcí DEC a JNZ. Instrukce nazvaná DEC snižuje obsah specifikovaného pracovního registru o jedničku a současně nastavuje příznak nuly, tj. Zero flag (ZF), samozřejmě ovšem pouze za předpokladu, že ten pracovní registr, jehož hodnota se snižuje, skutečně dosáhl nulové hodnoty. Instrukce JNZ znamená podmíněný skok, který je proveden pouze tehdy, pokud příznak nuly není nastaven („jump if not zero“, viz též předchozí kapitolu).

Před samotným začátkem programové smyčky je samozřejmě nutné do pracovního registru nastavit potřebný počet opakování, který by měl být ideálně odlišný od nuly. Ostatně zkuste si sami odpovědět na otázku, co by se stalo v případě, kdy by pracovní registr použitý jako počitadlo, byl před vstupem do smyčky vynulovaný. Podívejme se nyní na způsob použití 32bitového registru EAX ve funkci počitadla. Vzhledem k tomu, že se testuje pouze nulovost registru po snížení jeho hodnoty o jedničku, může tato smyčka být provedena 0× až 232-1× (ignoruje se znaménko):

        MOV EAX, počet_opakování ; počet opakování smyčky
SMYCKA: příkaz 1                 ; libovolné instrukce, jejichž celková
        příkaz 2                 ; celková délka bloku musí být menší než cca 120 bytů
        ...                      ; (kvůli omezení relativního skoku, pokud se délka překročí, použije se delší instrukce)
        ...
        ...
        příkaz X
        DEC EAX                  ; snížení čítače smyčky o jedničku
        JNZ SMYCKA               ; přeloží se jako relativní skok

6. Odlišná realizace počítané smyčky s instrukcemi DEC, JZ a JMP

Podmínku ovšem můžeme naopak přesunout i na samotný začátek smyčky. Nyní se ovšem namísto instrukce pro podmíněný skok JNZ použije instrukce JZ v kombinaci s instrukcí nepodmíněného skoku JMP:

        MOV EAX, počet_opakování ; počet opakování smyčky
SMYCKA: DEC EAX                  ; snížení čítače smyčky o jedničku
        JZ  KONEC                ; přeloží se jako relativní skok
        příkaz 1                 ; libovolné instrukce, jejichž celková
        příkaz 2                 ; celková délka bloku musí být menší než cca 120 bytů
        ...                      ; (kvůli omezení relativního skoku, pokud se délka překročí, použije se delší instrukce)
        ...
        ...
        příkaz X
        JMP SMYCKA               ; nepodmíněný skok na začátek smyčky

7. Praktický příklad – jednoduchá počítaná programová smyčka

Podívejme se nyní na způsob realizace velmi jednoduché počítané programové smyčky naprogramované v assembleru mikroprocesorů s architekturou i386 a x86_64 (používají se však pouze 32bitové registry). V tomto příkladu je dynamicky vytvořen buffer pro čtyřicet ASCII znaků. Do tohoto bufferu se v programové smyčce následně vypíše čtyřicet hvězdiček. Počitadlo smyčky je uloženo v 32bitovém pracovním registru EBX, adresa začátku bufferu v registru ECX a zapisovaný znak v osmibitovém pracovním registru AL (součást registru EAX). Řetězec čtyřiceti znaků je následně vypsán na standardní výstup nám již důvěrně známou funkcí sys_write:

# asmsyntax=as
 
# Testovaci program naprogramovany v assembleru GNU as
# - pocitana programova smycka
# - pouzita je "Intel" syntaxe.
#
# Autor: Pavel Tisnovsky
 
.intel_syntax noprefix
 
 
# Linux kernel system call table
sys_exit=1
sys_write=4
 
# pocet opakovani znaku
rep_count=40
 
 
 
#-----------------------------------------------------------------------------
.section .data
 
 
 
#-----------------------------------------------------------------------------
.section .bss
        .lcomm buffer, rep_count     # rezervace bufferu pro vystup
 
 
 
#-----------------------------------------------------------------------------
.section .text
        .global _start               # tento symbol ma byt dostupny i linkeru
 
_start:
        mov   ecx, offset buffer     # zapis se bude provadet do tohoto bufferu
        mov   ebx, rep_count         # pocet opakovani znaku
        mov   al,  '*'               # zapisovany znak
loop:
        mov   [ecx], al              # zapis znaku do bufferu
        inc   ecx                    # uprava ukazatele do bufferu
        dec   ebx                    # zmenseni pocitadla
        jnz   loop                   # pokud jsme se nedostali k nule, skok na zacatek smycky
 
        mov   eax, sys_write         # cislo syscallu pro funkci "write"
        mov   ebx, 1                 # standardni vystup
        mov   ecx, offset buffer     # adresa retezce, ktery se ma vytisknout
        mov   edx, rep_count         # pocet znaku, ktere se maji vytisknout
        int   0x80                   # volani Linuxoveho kernelu
 
        mov   eax, sys_exit          # cislo sycallu pro funkci "exit"
        mov   ebx, 0                 # exit code = 0
        int   0x80                   # volani Linuxoveho kernelu

Překlad se provede takto:

as loop1.s -o loop1.o
ld -s loop1.o

Zajímavé bude se podívat na způsob překladu aplikace a na realizaci podmíněného skoku:

objdump -M intel-mnemonic -f -d -t -h a.out
a.out:     file format elf64-x86-64
architecture: i386:x86-64, flags 0x00000102:
EXEC_P, D_PAGED
start address 0x00000000004000b0

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         00000037  00000000004000b0  00000000004000b0  000000b0  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .bss          00000030  00000000006000e8  00000000006000e8  000000e8  2**3
                  ALLOC
SYMBOL TABLE:
no symbols



Disassembly of section .text:

00000000004000b0 <.text>:
  4000b0:       b9 e8 00 60 00          mov    ecx,0x6000e8
  4000b5:       bb 28 00 00 00          mov    ebx,0x28
  4000ba:       b0 2a                   mov    al,0x2a
  4000bc:       67 88 01                mov    BYTE PTR [ecx],al
  4000bf:       ff c1                   inc    ecx
  4000c1:       ff cb                   dec    ebx
  4000c3:       75 f7                   jne    0x4000bc
  4000c5:       b8 04 00 00 00          mov    eax,0x4
  4000ca:       bb 01 00 00 00          mov    ebx,0x1
  4000cf:       b9 e8 00 60 00          mov    ecx,0x6000e8
  4000d4:       ba 28 00 00 00          mov    edx,0x28
  4000d9:       cd 80                   int    0x80
  4000db:       b8 01 00 00 00          mov    eax,0x1
  4000e0:       bb 00 00 00 00          mov    ebx,0x0
  4000e5:       cd 80                   int    0x80

Poznámky:

  1. Podmíněný skok ve zdrojovém kódu realizujeme instrukcí JNZ, která se ve výpisu zobrazuje jako JNE, což je ovšem jen jmenný alias.
  2. Strojová instrukce pro podmíněný skok má délku pouhé dva bajty: 75 f7. První bajt je operační kód, druhý bajt pak číslo se znaménkem, které je větší než 0x7f, což naznačuje skok zpět.
  3. Pokud by tělo smyčky překročilo přibližně 120 bajtů, byla by instrukce pro skok delší (některé assemblery ovšem vypíšou varování či dokonce chybu a vyžadují explicitní použití „dlouhého“ skoku).

8. Další praktický příklad – počítaná programová smyčka s testem na začátku

V dalším demonstračním příkladu je ukázána počítaná programová smyčka, v níž se test na ukončení provádí na jejím začátku po odečtení jedničky od počitadla. Tento příklad je vlastně v mnoha ohledech totožný s příkladem předchozím, ovšem vzhledem k tomu, že změna stavu počitadla (snížení jeho hodnoty o jedničku) a následný test je proveden na začátku smyčky, je nutné při inicializaci počitadla do něj vložit hodnotu 41 a nikoli 40, jinak by se vytisklo pouze 39 hvězdiček následovaných ASCII znakem s kódem nula:

# asmsyntax=as
 
# Testovaci program naprogramovany v assembleru GNU as
# - pocitana programova smycka s testem na zacatku
# - pouzita je "Intel" syntaxe.
#
# Autor: Pavel Tisnovsky
 
.intel_syntax noprefix
 
 
# Linux kernel system call table
sys_exit=1
sys_write=4
 
# pocet opakovani znaku
rep_count=40
 
 
 
#-----------------------------------------------------------------------------
.section .data
 
 
 
#-----------------------------------------------------------------------------
.section .bss
        .lcomm buffer, rep_count     # rezervace bufferu pro vystup
 
 
 
#-----------------------------------------------------------------------------
.section .text
        .global _start               # tento symbol ma byt dostupny i linkeru
 
_start:
        mov   ecx, offset buffer     # zapis se bude provadet do tohoto bufferu
        mov   ebx, rep_count+1       # pocet opakovani znaku+1 (protoze se dec+test provadi na zacatku)
        mov   al,  '*'               # zapisovany znak
loop:
        dec   ebx                    # zmenseni pocitadla
        jz    konec                  # pokud jsme se dostali k nule, konec smycky
        mov   [ecx], al              # zapis znaku do bufferu
        inc   ecx                    # uprava ukazatele do bufferu
        jmp   loop                   # nepodmineny skok na zacatek smycky
konec:

        mov   eax, sys_write         # cislo syscallu pro funkci "write"
        mov   ebx, 1                 # standardni vystup
        mov   ecx, offset buffer     # adresa retezce, ktery se ma vytisknout
        mov   edx, rep_count         # pocet znaku, ktere se maji vytisknout
        int   0x80                   # volani Linuxoveho kernelu
 
        mov   eax, sys_exit          # cislo sycallu pro funkci "exit"
        mov   ebx, 0                 # exit code = 0
        int   0x80                   # volani Linuxoveho kernelu

Překlad se provede takto:

as loop2.s -o loop2.o
ld -s loop2.o

Opět se podívejme na zpětný překlad (disassembling), aby bylo patrné, jakým způsobem se přeloží podmíněný i nepodmíněný skok:

objdump -M intel-mnemonic -f -d -t -h a.out
a.out:     file format elf64-x86-64
architecture: i386:x86-64, flags 0x00000102:
EXEC_P, D_PAGED
start address 0x00000000004000b0

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         00000039  00000000004000b0  00000000004000b0  000000b0  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .bss          00000030  00000000006000f0  00000000006000f0  000000f0  2**3
                  ALLOC
SYMBOL TABLE:
no symbols



Disassembly of section .text:

00000000004000b0 <.text>:
  4000b0:       b9 f0 00 60 00          mov    ecx,0x6000f0
  4000b5:       bb 29 00 00 00          mov    ebx,0x29
  4000ba:       b0 2a                   mov    al,0x2a
  4000bc:       ff cb                   dec    ebx
  4000be:       74 07                   je     0x4000c7
  4000c0:       67 88 01                mov    BYTE PTR [ecx],al
  4000c3:       ff c1                   inc    ecx
  4000c5:       eb f5                   jmp    0x4000bc
  4000c7:       b8 04 00 00 00          mov    eax,0x4
  4000cc:       bb 01 00 00 00          mov    ebx,0x1
  4000d1:       b9 f0 00 60 00          mov    ecx,0x6000f0
  4000d6:       ba 28 00 00 00          mov    edx,0x28
  4000db:       cd 80                   int    0x80
  4000dd:       b8 01 00 00 00          mov    eax,0x1
  4000e2:       bb 00 00 00 00          mov    ebx,0x0
  4000e7:       cd 80                   int    0x80

Poznámky:

  1. Podmíněný skok ve zdrojovém kódu realizujeme instrukcí JZ, která se ve výpisu zobrazuje jako JE, což je opět pouze jmenný alias.
  2. Strojová instrukce pro podmíněný skok má znovu délku pouhé dva bajty a z druhého bajtu (0x07) vidíme, že je proveden skok vpřed (o osm bajtů počítáno od začátku skoku).
  3. Pokud by tělo smyčky překročilo přibližně 120 bajtů, byla by instrukce pro skok delší (některé assemblery ovšem vypíšou varování či dokonce chybu a vyžadují explicitní použití „dlouhého“ skoku).
  4. Nepodmíněný skok je taktéž realizován s použitím relativní adresy, takže délka této instrukce je pouhé dva bajty. Druhý bajt instrukce je větší než 0x7f, takže se jedná o skok zpět.

9. Použití instrukce CMP v součinnosti s podmíněným skokem

Společně s podmíněnými skoky se velmi často používá doposud nepopsaná instrukce nazvaná CMP (mnemotechnická zkratka od slova compare). V podstatě se jedná o běžné celočíselné odečítání, tj. o instrukci nazvanou SUB, ovšem s tím důležitým rozdílem, že výsledek, tj. samotný rozdíl, není nikam zapsán, což znamená, že se obsah běžných pracovních registrů provedením této instrukce nezmění. Na první pohled může vypadat tato instrukce nesmyslně – proč se vůbec mají odečítat dvě hodnoty, když se výsledek hned zapomene? Ovšem samotný výsledek není ve skutečnosti vše, protože při odečítání si mikroprocesor v příznakových registrech zapamatuje i to, zda byla odečítaná čísla shodná (tehdy se nastaví Zero flag na jedničku) či zda byla druhá hodnota větší než hodnota první (příznak Carry flag bude v tomto případě roven jedné).

Instrukce CMP akceptuje různé typy operandů; může se jednat o běžné pracovní registry, ovšem použít lze i konstanty či obsah získaný ze zvolené adresy operační paměti. V následující tabulce si ukážeme, jaké příznaky se nastaví při provedení různých variant funkce CMP:

První operand Druhý operand Zero flag Carry flag Význam
00 00 1 0 obě hodnoty jsou shodné
42 42 1 0 obě hodnoty jsou shodné
20 10 0 0 první hodnota je větší (nedošlo k přenosu)
10 20 0 1 druhá hodnota je větší (došlo k přenosu)

Poznámka: ve skutečnosti by v tomto případě (odečítání a porovnávání) bylo korektnější mluvit o takzvané výpůjčce (borrow) a nikoli o přenosu (carry).

Z těchto příkladů současně nepřímo vyplývá i to, jak můžeme příznakové bity použít. Jestliže je zapotřebí testovat dvě hodnoty na rovnost, postačí zjistit, zda je Zero flag nastavený na jedničku (ostatně i z tohoto důvodu má podmíněný skok JZ alias JE – jump if equal). Pokud potřebujeme otestovat, jestli je první hodnota menší než druhá, lze zjistit hodnotu příznaku Carry flag atd. Test na nulovou hodnotu lze provést odečtením nuly – výsledek bude uložen v Zero flagu (ve skutečnosti se ovšem tento test většinou neprovádí, protože i některé další instrukce dokážou nastavit tento příznak automaticky a vlastně „mimochodem“). Podobně je tomu u testování, zda je hodnota kladná či záporná. Podívejme se na několik možností použití:

; test na rovnost dvou hodnot
MOV EAX, hodnota 1
MOV EBX, hodnota 2
CMP EAX, EBX
JZ  JE_ROVNO    ; skok na kód provedený v případě rovnosti
JNZ NENI_ROVNO  ; skok na kód provedený v případě nerovnosti
; zjištění relace dvou čísel
MOV EAX, hodnota 1
MOV EBX, hodnota 2
CMP EAX, EBX
JNC EAX_JE_VETSI_NEBO_ROVNO_EBX
JC  EAX_JE_MENSI_NEZ_EBX
; test na nulovost
MOV EAX, hodnota
SUB EBX,EBX       ; vynulování registru EBX (lze i XOR EBX,EBX)
CMP EAX,EBX
JZ  EAX_JE_NULOVE
; test na nulovost
MOV EAX, hodnota
CMP EAX,0
JZ  EAX_JE_NULOVE
; test na kladnou či zápornou hodnou
MOV EAX, hodnota
CMP EAX,0
JS  EAX_JE_ZAPORNE

10. Praktický příklad – počítaná smyčka s volitelným začátkem a koncem

Dnešní poslední demonstrační příklad opět ukazuje realizaci počítané programové smyčky, tentokrát se ovšem ve smyčce vytváří řetězec obsahující znaky od 'a' do 'z'. Pro test na ukončení smyčky se používá kombinace instrukcí CMP+JNZ pro porovnání, zda aktuálně zapisovaný znak nepřekročil hodnotu 'z' (v předchozích příkladech se prováděl test na nulu). Povšimněte si, že se ve zdrojovém kódu mohou zapisovat i složitější konstantní výrazy typu 'z'+1 či dokonce 'z'-'a'+1:

# asmsyntax=as
 
# Testovaci program naprogramovany v assembleru GNU as
# - smycka vyuzivajici instrukci CMP
# - pouzita je "Intel" syntaxe.
#
# Autor: Pavel Tisnovsky
 
.intel_syntax noprefix
 
 
# Linux kernel system call table
sys_exit=1
sys_write=4
 
# pocet opakovani znaku
rep_count='z'-'a'+1
 
 
 
#-----------------------------------------------------------------------------
.section .data
 
 
 
#-----------------------------------------------------------------------------
.section .bss
        .lcomm buffer, rep_count     # rezervace bufferu pro vystup
 
 
 
#-----------------------------------------------------------------------------
.section .text
        .global _start               # tento symbol ma byt dostupny i linkeru
 
_start:
        mov   ecx, offset buffer     # zapis se bude provadet do tohoto bufferu
        mov   al, 'a'                # kod prvniho zapisovaneho znaku
loop:
        mov   [ecx], al              # zapis znaku do bufferu
        inc   al                     # ASCII kod dalsiho znaku
        inc   ecx                    # uprava ukazatele do bufferu
        cmp   al, 'z'+1              # ma se smycka ukoncit?
        jnz   loop                   # pokud jsme neprekrocili kod 'z', opakovat smycku
 
        mov   eax, sys_write         # cislo syscallu pro funkci "write"
        mov   ebx, 1                 # standardni vystup
        mov   ecx, offset buffer     # adresa retezce, ktery se ma vytisknout
        mov   edx, rep_count         # pocet znaku, ktere se maji vytisknout
        int   0x80                   # volani Linuxoveho kernelu
 
        mov   eax, sys_exit          # cislo sycallu pro funkci "exit"
        mov   ebx, 0                 # exit code = 0
        int   0x80                   # volani Linuxoveho kernelu

Překlad se provede následovně:

as loop3.s -o loop3.o
ld -s loop3.o

Zpětný překlad (disassembling) prozradí způsob překladu:

objdump -M intel-mnemonic -f -d -t -h a.out
a.out:     file format elf64-x86-64
architecture: i386:x86-64, flags 0x00000102:
EXEC_P, D_PAGED
start address 0x00000000004000b0

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         00000034  00000000004000b0  00000000004000b0  000000b0  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .bss          00000020  00000000006000e8  00000000006000e8  000000e8  2**3
                  ALLOC
SYMBOL TABLE:
no symbols



Disassembly of section .text:

00000000004000b0 <.text>:
  4000b0:       b9 e8 00 60 00          mov    ecx,0x6000e8
  4000b5:       b0 61                   mov    al,0x61
  4000b7:       67 88 01                mov    BYTE PTR [ecx],al
  4000ba:       fe c0                   inc    al
  4000bc:       ff c1                   inc    ecx
  4000be:       3c 7b                   cmp    al,0x7b
  4000c0:       75 f5                   jne    0x4000b7
  4000c2:       b8 04 00 00 00          mov    eax,0x4
  4000c7:       bb 01 00 00 00          mov    ebx,0x1
  4000cc:       b9 e8 00 60 00          mov    ecx,0x6000e8
  4000d1:       ba 1a 00 00 00          mov    edx,0x1a
  4000d6:       cd 80                   int    0x80
  4000d8:       b8 01 00 00 00          mov    eax,0x1
  4000dd:       bb 00 00 00 00          mov    ebx,0x0
  4000e2:       cd 80                   int    0x80

Díky tomu, že zapisovaný znak je uložený v osmibitovém registru AL, je zvýšení tohoto registru o jedničku a test na mezní hodnotu realizován velmi krátkými strojovými instrukcemi inc al a cmp al, 0x7b.

11. Repositář s demonstračními příklady

Všechny čtyři dnes popisované demonstrační příklady byly společně s podpůrnými skripty určenými pro překlad či naopak pro disassembling uloženy do GIT repositáře dostupného na adrese https://github.com/tisnik/presentations/. Všechny dnešní příklady jsou, na rozdíl od některých příkladů vysvětlených minule a předminule, určené pro GNU Assembler a používají „Intel“ syntaxi. Následuje tabulka s odkazy na zdrojové kódy příkladů i na již zmíněné podpůrné skripty:

Špagetový kód realizovaný instrukcí nepodmíněného skoku JMP

# Soubor Popis Odkaz do repositáře
1 jumps.s program pro GNU Assembler https://github.com/tisnik/presentations/blob/master/assembler/14_spaghetti_code/jumps.s
2 assemble skript pro překlad na i386/x86_64 https://github.com/tisnik/presentations/blob/master/assembler/14_spaghetti_code/assemble
3 disassemble skript pro disassembling https://github.com/tisnik/presentations/blob/master/assembler/14_spaghetti_code/disassemble

Počítaná programová smyčka s testem prováděným na začátku

# Soubor Popis Odkaz do repositáře
1 loop1.s program pro GNU Assembler https://github.com/tisnik/presentations/blob/master/assembler/11_gas_loop/loop1.s
2 assemble skript pro překlad na i386/x86_64 https://github.com/tisnik/presentations/blob/master/assembler/11_gas_loop/assemble
3 disassemble skript pro disassembling https://github.com/tisnik/presentations/blob/master/assembler/11_gas_loop/disassemble

Počítaná programová smyčka s testem prováděným na konci

# Soubor Popis Odkaz do repositáře
1 loop2.s program pro GNU Assembler https://github.com/tisnik/presentations/blob/master/assembler/12_gas_loop/loop2.s
2 assemble skript pro překlad na i386/x86_64 https://github.com/tisnik/presentations/blob/master/assembler/12_gas_loop/assemble
3 disassemble skript pro disassembling https://github.com/tisnik/presentations/blob/master/assembler/12_gas_loop/disassemble

Počítaná smyčka s volitelným začátkem a koncem

# Soubor Popis Odkaz do repositáře
1 loop3.s program pro GNU Assembler https://github.com/tisnik/presentations/blob/master/assembler/13_gas_loop/loop3.s
2 assemble skript pro překlad na i386/x86_64 https://github.com/tisnik/presentations/blob/master/assembler/13_gas_loop/assemble
3 disassemble skript pro disassembling https://github.com/tisnik/presentations/blob/master/assembler/13_gas_loop/disassemble

12. Odkazy na Internetu

  1. Intel x86 JUMP quick reference
    http://unixwiz.net/techtips/x86-jumps.html
  2. Linux assemblers: A comparison of GAS and NASM
    http://www.ibm.com/developerworks/library/l-gas-nasm/index.html
  3. Programovani v assembleru na OS Linux
    http://www.cs.vsb.cz/grygarek/asm/asmlinux.html
  4. Is it worthwhile to learn x86 assembly language today?
    https://www.quora.com/Is-it-worthwhile-to-learn-x86-assembly-language-today?share=1
  5. Why Learn Assembly Language?
    http://www.codeproject.com/Articles/89460/Why-Learn-Assembly-Language
  6. Is Assembly still relevant?
    http://programmers.stackexchange.com/questions/95836/is-assembly-still-relevant
  7. Why Learning Assembly Language Is Still a Good Idea
    http://www.onlamp.com/pub/a/onlamp/2004/05/06/writegreatcode.html
  8. Assembly language today
    http://beust.com/weblog/2004/06/23/assembly-language-today/
  9. Assembler: Význam assembleru dnes
    http://www.builder.cz/rubriky/assembler/vyznam-assembleru-dnes-155960cz
  10. Assembler pod Linuxem
    http://phoenix.inf.upol.cz/linux/prog/asm.html
  11. AT&T Syntax versus Intel Syntax
    https://www.sourceware.org/binutils/docs-2.12/as.info/i386-Syntax.html
  12. Linux Assembly website
    http://asm.sourceforge.net/
  13. Using Assembly Language in Linux
    http://asm.sourceforge.net/articles/linasm.html
  14. vasm
    http://sun.hasenbraten.de/vasm/
  15. vasm – dokumentace
    http://sun.hasenbraten.de/vasm/release/vasm.html
  16. The Yasm Modular Assembler Project
    http://yasm.tortall.net/
  17. 680x0:AsmOne
    http://www.amigacoding.com/index.php/680x0:AsmOne
  18. ASM-One Macro Assembler
    http://en.wikipedia.org/wiki/ASM-One_Macro_Assembler
  19. ASM-One pages
    http://www.theflamearrows.info/documents/asmone.html
  20. Základní informace o ASM-One
    http://www.theflamearrows.info/documents/asminfo.html
  21. Linux Syscall Reference
    http://syscalls.kernelgrok.com/
  22. Programming from the Ground Up Book - Summary
    http://savannah.nongnu.org/projects/pgubook/
  23. IBM System 360/370 Compiler and Historical Documentation
    http://www.edelweb.fr/Simula/
  24. IBM 700/7000 series
    http://en.wikipedia.org/wiki/IBM_700/7000_series
  25. IBM System/360
    http://en.wikipedia.org/wiki/IBM_System/360
  26. IBM System/370
    http://en.wikipedia.org/wiki/IBM_System/370
  27. Mainframe family tree and chronology
    http://www-03.ibm.com/ibm/history/exhibits/mainframe/mainframe_FT1.html
  28. 704 Data Processing System
    http://www-03.ibm.com/ibm/history/exhibits/mainframe/mainframe_PP704.html
  29. 705 Data Processing System
    http://www-03.ibm.com/ibm/history/exhibits/mainframe/mainframe_PP705.html
  30. The IBM 704
    http://www.columbia.edu/acis/history/704.html
  31. IBM Mainframe album
    http://www-03.ibm.com/ibm/history/exhibits/mainframe/mainframe_album.html
  32. Osmibitové muzeum
    http://osmi.tarbik.com/
  33. Tesla PMI-80
    http://osmi.tarbik.com/cssr/pmi80.html
  34. PMI-80
    http://en.wikipedia.org/wiki/PMI-80
  35. PMI-80
    http://www.old-computers.com/museum/computer.asp?st=1&c=1016