Osmá část seriálu o použití assembleru v Linuxu je věnována makrům, které je možné využít pro zjednodušení popř. pro zpřehlednění zápisů zdrojových kódů pro GNU Assembler. Seznámíme se především se způsobem předávání parametrů volaným makrům a taktéž s použitím interního čítače GNU Assembleru při deklaraci návěští (labels).

Obsah

1. Použití assembleru v Linuxu: makra v GNU Assembleru

2. Vytvoření jednoduchého makra bez parametrů

3. Vytvoření makra s parametry

4. První demonstrační příklad – makra pro vytištění zprávy a ukončení aplikace

5. Výpis zdrojového kódu po expanzi maker a překladu

6. Použití návěští v makrech

7. Druhý demonstrační příklad – nefunkční použití návěští v makru

8. Využití počitadla volání makra pro vytvoření unikátního návěští

9. Třetí demonstrační příklad – použití počitadla při tvorbě návěští v makrech

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

11. Odkazy na Internetu

1. Použití assembleru v Linuxu: makra v GNU Assembleru

Nástroje typu „assembler“ je možné podle principu jejich práce rozdělit do několika kategorií. Do první kategorie spadají assemblery interaktivní, které uživateli nabízejí poměrně komfortní vývojové prostředí, v němž je v případě potřeby možné zapisovat jednotlivé instrukce, spouštět programy, krokovat je, vypisovat obsahy pracovních registrů mikroprocesoru, prohlížet si obsah operační paměti, zásobníku atd. Výhodou byla nezávislost těchto assemblerů na rychlém externím paměťovém médiu, proto jsme se s nimi mohli setkat například na osmibitových domácích mikropočítačích či dnes na různých zařízeních typu IoT (i když zde úlohu pouhého interaktivního assembleru mnohdy přebírá interaktivní debugger). Druhý typ assemblerů je široce používán dodnes – jedná se vlastně o běžné překladače, kterým se na vstupu předloží zdrojový kód a po překladu se výsledný nativní kód taktéž uloží na paměťové médium (odkud ho lze přímo spustit, což se dělo například v operačním systému DOS, popř. ho ještě před spuštěním slinkovat, což je případ Linuxu a dalších moderních operačních systémů).

Assemblery spadající do druhé kategorie jsou mnohdy vybaveny více či méně dokonalým systémem maker; odtud ostatně pochází i jejich často používané označení macroassembler. Makra, která se většinou aplikují na zdrojový kód v první fázi překladu, je možné použít pro různé činnosti, ať již se jedná o zjednodušení zápisu kódu či o jeho zkrácení a zpřehlednění. Existují například sady poměrně složitých maker, které do assembleru přidávají některé konstrukce známé z vyšších programovacích jazyků – rozvětvení, programové smyčky, deklaraci objektů atd. GNU Assembler, ale i v předchozích dílech tohoto seriálu zmiňovaný NASM (Netwide Assembler), podobně jako prakticky všechny další moderní assemblery, práci s makry podporují, i když se způsob zápisu maker i jejich základní vlastnosti od sebe odlišují. Z tohoto důvodu se v dnešním článku budeme věnovat (prozatím) pouze makrům v GNU Assembleru. Ukážeme si i řešení některých častých problémů, které mohou při deklaraci maker nastat, například práci s návěštími (labels) apod.

2. Vytvoření jednoduchého makra bez parametrů

Makra v assembleru, tedy i v GNU Assembleru, provádí textové substituce, což mj. znamená, že expanze maker je vykonána v první fázi překladu. V GNU Assembleru deklarace makra začíná direktivou .macro a končí direktivou .endm (obě direktivy se zapisují včetně teček na začátku). Za direktivou .macro musí následovat jméno makra a popř. i jeho parametry. Na dalších řádcích je pak vlastní text makra. Použití makra je ještě jednodušší než jeho deklarace – kdekoli se prostě uvede jméno makra s případnými parametry. Jakmile GNU Assembler zjistí, že se ve zdrojovém kódu nachází jméno makra, provede jeho expanzi, takže se vlastně případné instrukce, ze kterých se text makra skládá, přímo vloží do kódu na místo volání makra.

Podívejme se nyní na velmi jednoduchý příklad, kterým je makro bez parametrů. Toto makro jsme nazvali exit a v jeho těle se zavolá syscall sloužící k ukončení procesu:

# Deklarace makra pro ukonceni aplikace (procesu)
.macro exit
        mov   eax, sys_exit          # cislo sycallu pro funkci "exit"
        mov   ebx, 0                 # exit code = 0
        int   0x80                   # volani Linuxoveho kernelu
.endm

Povšimněte si, že v těle makra se může nacházet libovolný text, včetně komentářů, symbolů deklarovaných mimo makro (sys_exit) atd. Volání makra nazvaného exit vypadá takto:

#-----------------------------------------------------------------------------
.section .text
        .global _start               # tento symbol ma byt dostupny i linkeru

_start:
        ...
        libovolné instrukce či volání jiných maker
        ...
        exit                         # ukonceni aplikace

Poznámka: ve skutečnosti se nejedná o volání makra ve smyslu volání subrutiny, ale skutečně pouze o textovou expanzi prováděnou v době překladu programu ze zdrojového kódu do kódu objektového.

3. Vytvoření makra s parametry

Makra mohou v případě potřeby (ta je poměrně častá) akceptovat i parametry, ovšem práce s nimi může být zpočátku poněkud neobvyklá. Jména parametrů se zadávají již v hlavičce makra přímo za jeho názvem, tedy následujícím způsobem:

.macro jméno_makra parametr1, parametr2, ...

Důležitější ovšem je, že přímo v těle makra se před jméno parametru musí vložit znak zpětného lomítka, jinak nedojde k náhradě názvu parametru jeho skutečným obsahem (to má svůj význam, protože nemůže nastat situace, že by se nějaký text nahrazoval parametrem makra omylem). Podívejme se na praktický příklad – makro určené pro výpis zprávy na standardní výstup. Tomuto makru se předává adresa řetězce a jeho délka. Uvnitř těla makra se před parametry skutečně zapisuje zpětné lomítko:

# 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

Příklad volání tohoto makra:

writeMessage message1,message1len

To už vypadá podobně, jako by se program zapisoval ve vyšším programovacím jazyce 🙂

4. První demonstrační příklad – makra pro vytištění zprávy a ukončení aplikace

V dnešním prvním demonstračním příkladu je ukázáno jak použití maker bez parametrů, tak i použití maker s parametry. Tento demonstrační příklad by po svém spuštění měl na standardní výstup vypsat trojici zpráv (textových řádků). O výpis zpráv se stará makro nazvané writeMessage z předchozí kapitoly, o ukončení programu pak makro nazvané exit zmíněné ve druhé kapitole. Makro writeMessage navíc využívá subrutinu nazvanou write_message:

# asmsyntax=as

# Ukazka pouziti maker v GNU Assembleru
# - pouzita je "Intel" syntaxe.
#
# Autor: Pavel Tisnovsky

.intel_syntax noprefix


# Linux kernel system call table
sys_exit   = 1
sys_write  = 4

# Dalsi konstanty pouzite v programu - standardni streamy
std_input  = 0
std_output = 1



#-----------------------------------------------------------------------------

# 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



# 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



#-----------------------------------------------------------------------------
.section .data
message1:
        .string "Hello world\n"
message1len = $ - message1         # delka prvni zpravy

message2:
        .string "Vitejte na mojefedora.cz\n"
message2len = $ - message2         # delka druhe zpravy

message3:
        .string "Assembler je fajn\n"
message3len = $ - message3         # delka druhe zpravy



#-----------------------------------------------------------------------------
.section .bss



#-----------------------------------------------------------------------------
.section .text
        .global _start               # tento symbol ma byt dostupny i linkeru

_start:
        writeMessage message1,message1len
        writeMessage message2,message2len
        writeMessage message3,message3len
        exit                         # ukonceni aplikace



# 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

Překlad tohoto programu proveďte následujícím skriptem:

as -g --32 macros.s -o macros.o
ld -m elf_i386 -s macros.o

Pro zajímavost si můžete vyzkoušet i zpětný překlad (disassembling):

objdump -M intel-mnemonic -f -d -t -h macros.o

Z výsledku je patrné, jakým způsobem se makra expandovala:

macros.o:     file format elf32-i386
architecture: i386, flags 0x00000011:
HAS_RELOC, HAS_SYMS
start address 0x00000000

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         00000046  00000000  00000000  00000034  2**0
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .data         0000003a  00000000  00000000  0000007a  2**0
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000000  00000000  00000000  000000b4  2**0
                  ALLOC
  3 .debug_line   00000040  00000000  00000000  000000b4  2**0
                  CONTENTS, RELOC, READONLY, DEBUGGING
  4 .debug_info   00000063  00000000  00000000  000000f4  2**0
                  CONTENTS, RELOC, READONLY, DEBUGGING
  5 .debug_abbrev 00000014  00000000  00000000  00000157  2**0
                  CONTENTS, READONLY, DEBUGGING
  6 .debug_aranges 00000020  00000000  00000000  00000170  2**3
                  CONTENTS, RELOC, READONLY, DEBUGGING
SYMBOL TABLE:
00000000 l    d  .text  00000000 .text
00000000 l    d  .data  00000000 .data
00000000 l    d  .bss   00000000 .bss
00000001 l       *ABS*  00000000 sys_exit
00000004 l       *ABS*  00000000 sys_write
00000000 l       *ABS*  00000000 std_input
00000001 l       *ABS*  00000000 std_output
00000000 l       .data  00000000 message1
0000000d l       *ABS*  00000000 message1len
0000000d l       .data  00000000 message2
0000001a l       *ABS*  00000000 message2len
00000027 l       .data  00000000 message3
00000013 l       *ABS*  00000000 message3len
00000039 l       .text  00000000 write_message
00000000 l    d  .debug_info    00000000 .debug_info
00000000 l    d  .debug_abbrev  00000000 .debug_abbrev
00000000 l    d  .debug_line    00000000 .debug_line
00000000 l    d  .debug_aranges 00000000 .debug_aranges
00000000 g       .text  00000000 _start



Disassembly of section .text:

00000000 <_start>:
   0:   b9 00 00 00 00          mov    ecx,0x0
   5:   ba 0d 00 00 00          mov    edx,0xd
   a:   e8 2a 00 00 00          call   39 <write_message>
   f:   b9 0d 00 00 00          mov    ecx,0xd
  14:   ba 1a 00 00 00          mov    edx,0x1a
  19:   e8 1b 00 00 00          call   39 <write_message>
  1e:   b9 27 00 00 00          mov    ecx,0x27
  23:   ba 13 00 00 00          mov    edx,0x13
  28:   e8 0c 00 00 00          call   39 <write_message>
  2d:   b8 01 00 00 00          mov    eax,0x1
  32:   bb 00 00 00 00          mov    ebx,0x0
  37:   cd 80                   int    0x80

00000039 <write_message>:
  39:   b8 04 00 00 00          mov    eax,0x4
  3e:   bb 01 00 00 00          mov    ebx,0x1
  43:   cd 80                   int    0x80
  45:   c3                      ret

5. Výpis zdrojového kódu po expanzi maker a překladu

Při zápisu maker či při jejich volání může dojít k situaci, kdy se makro neexpanduje podle našich předpokladů a je nutné zjistit, kde nastal problém. GNU Assembler sice neexpanduje makra samostatným preprocesorem (jak je tomu v céčku a jeho preprocesoru nazvaném cpp), ovšem obsahuje možnost nechat si vygenerovat výpis původního zdrojového kódu kombinovaného s přeloženým objektovým kódem, přesněji řečeno s objektovým kódem zapsaným v hexadecimálním tvaru. Jedná se o mnohdy velmi užitečnou vlastnost, kterou nalezneme u mnoha assemblerů, a to i u některých starších nástrojů. Takový výpis se na historických mainframech bez obrazovky většinou posílal přímo na tiskárnu, takže obsahoval i vepsané chyby nalezené překladačem. A právě v tomto výpisu se mohou objevit expandovaná makra. Podívejme se, co se stane, pokud při překladu použijeme volbu -alm (resp. volbu -a s dalšími příznaky l a m) kombinovanou s volbou -g:

as -alm -g --32 macros.s -o macros.o

Na standardní výstup by se měl vypsat následující výpis:

GAS LISTING macros.s                    page 1


   1                    # asmsyntax=as
   2
   3                    # Ukazka pouziti maker v GNU Assembleru
   4                    # - pouzita je "Intel" syntaxe.
   5                    #
   6                    # Autor: Pavel Tisnovsky
   7
   8                    .intel_syntax noprefix
   9
  10
  11                    # Linux kernel system call table
  12                    sys_exit   = 1
  13                    sys_write  = 4
  14
  15                    # Dalsi konstanty pouzite v programu - standardni streamy
  16                    std_input  = 0
  17                    std_output = 1
  18
  19
  20
  21                    #-----------------------------------------------------------------------------
  22
  23                    # Deklarace makra pro ukonceni aplikace
  24                    .macro exit
  25                            mov   eax, sys_exit          # cislo sycallu pro funkci "exit"
  26                            mov   ebx, 0                 # exit code = 0
  27                            int   0x80                   # volani Linuxoveho kernelu
  28                    .endm
  29
  30
  31
  32                    # Deklarace makra pro vytisteni zpravy na standardni vystup
  33                    .macro writeMessage message,messageLength
  34                            mov   ecx, offset \message   # adresa retezce, ktery se ma vytisknout
  35                            mov   edx, \messageLength    # pocet znaku, ktere se maji vytisknout
  36                            call  write_message          # vytisknout zpravu "Zero flag not set"
  37                    .endm
  38
  39
  40
  41                    #-----------------------------------------------------------------------------
  42                    .section .data
  43                    message1:
  44 0000 48656C6C              .string "Hello world\n"
  44      6F20776F 
  44      726C640A 
  44      00
  45                    message1len = $ - message1         # delka prvni zpravy
  46
  47                    message2:
  48 000d 56697465              .string "Vitejte na mojefedora.cz\n"
  48      6A746520 
  48      6E61206D 
  48      6F6A6566 
  48      65646F72 
  49                    message2len = $ - message2         # delka druhe zpravy
  50


GAS LISTING macros.s                    page 2


  51                    message3:
  52 0027 41737365              .string "Assembler je fajn\n"
  52      6D626C65 
  52      72206A65 
  52      2066616A 
  52      6E0A00
  53                    message3len = $ - message3         # delka druhe zpravy
  54
  55
  56
  57                    #-----------------------------------------------------------------------------
  58                    .section .bss
  59
  60
  61
  62                    #-----------------------------------------------------------------------------
  63                    .section .text
  64                            .global _start               # tento symbol ma byt dostupny i linkeru
  65
  66                    _start:
  67                            writeMessage message1,message1len
  67 0000 B9000000      >  mov ecx,offset message1
  67      00
  67 0005 BA0D0000      >  mov edx,message1len
  67      00
  67 000a E82A0000      >  call write_message
  67      00
  68                            writeMessage message2,message2len
  68 000f B90D0000      >  mov ecx,offset message2
  68      00
  68 0014 BA1A0000      >  mov edx,message2len
  68      00
  68 0019 E81B0000      >  call write_message
  68      00
  69                            writeMessage message3,message3len
  69 001e B9270000      >  mov ecx,offset message3
  69      00
  69 0023 BA130000      >  mov edx,message3len
  69      00
  69 0028 E80C0000      >  call write_message
  69      00
  70                            exit                         # ukonceni aplikace
  70 002d B8010000      >  mov eax,sys_exit
  70      00
  70 0032 BB000000      >  mov ebx,0
  70      00
  70 0037 CD80          >  int 0x80
  71
  72
  73
  74                    # Podprogram pro vytisteni zpravy na standardni vystup
  75                    # Ocekava se, ze v ecx bude adresa zpravy a v edx jeji delka
  76                    write_message:
  77 0039 B8040000              mov   eax, sys_write         # cislo syscallu pro funkci "write"
  77      00
  78 003e BB010000              mov   ebx, std_output        # standardni vystup
  78      00


GAS LISTING macros.s                    page 3


  79 0043 CD80                  int   0x80
  80 0045 C3                    ret
  81

Vidíme, že se skutečně jedná o formátovaný výstup určený pro tisk, ovšem podstatné je, že došlo jak k expanzi maker, tak i k umístění značek > do těch částí kódu, kde byla makra umístěna.

6. Použití návěští v makrech

V tělech mnoha maker se používají instrukce skoku, což mj. znamená, že se mnohdy nevyhneme použití návěští. Zkusme si například vytvořit makro, které vytiskne nějakou zprávu na standardní výstup, a to několikrát za sebou, přičemž počet opakování je zadán dalším (třetím) parametrem makra. První verze takového makra by mohla vypadat následovně:

.macro writeMessageRepeatedly message,messageLength,count
        mov   ebp, \count            # nastaveni pocitadla
loop:                                # globalni navesti!!!
        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"
        dec   ebp                    # snizeni hodnoty pocitadla
        jnz   loop                   # opakovani smycky
.endm

Toto makro sice bude (alespoň zdánlivě) fungovat, ale pouze v tom případě, že bude použito jen jedinkrát. Pokud makro použijeme dvakrát (či samozřejmě vícekrát), vytvoří se v překládaném kódu větší množství návěští se shodným názvem loop, což samozřejmě povede k chybě při překladu. Ostatně můžeme si to vyzkoušet v dalším demonstračním příkladu popsaném v navazující kapitole.

7. Druhý demonstrační příklad – nefunkční použití návěští v makru

V dnešním druhém demonstračním příkladu se pokusíme o použití makra uvedeného v předchozí kapitole, a to několikrát za sebou:

_start:
        writeMessageRepeatedly message1,message1len,10
        writeMessageRepeatedly message2,message2len,2
        writeMessageRepeatedly message3,message3len,7
        exit                         # ukonceni aplikace

Nejprve si uveďme zdrojový kód a teprve poté se podíváme na vypisovaná chybová hlášení:

# asmsyntax=as

# Ukazka pouziti maker v GNU Assembleru - problem s navestimi v makrech
# - pouzita je "Intel" syntaxe.
# - zdrojovy kod nelze prelozit (to je v poradku!)
#
# Autor: Pavel Tisnovsky

.intel_syntax noprefix


# Linux kernel system call table
sys_exit   = 1
sys_write  = 4

# Dalsi konstanty pouzite v programu - standardni streamy
std_input  = 0
std_output = 1



#-----------------------------------------------------------------------------

# 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



# 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



# Deklarace makra pro vytisteni zpravy na standardni vystup
.macro writeMessageRepeatedly message,messageLength,count
        mov   ebp, \count            # nastaveni pocitadla
loop:                                # globalni navesti!!!
        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"
        dec   ebp                    # snizeni hodnoty pocitadla
        jnz   loop                   # opakovani smycky
.endm



#-----------------------------------------------------------------------------
.section .data
message1:
        .string "Hello world\n"
message1len = $ - message1         # delka prvni zpravy

message2:
        .string "Vitejte na mojefedora.cz\n"
message2len = $ - message2         # delka druhe zpravy

message3:
        .string "Assembler je fajn\n"
message3len = $ - message3         # delka druhe zpravy



#-----------------------------------------------------------------------------
.section .bss



#-----------------------------------------------------------------------------
.section .text
        .global _start               # tento symbol ma byt dostupny i linkeru

_start:
        writeMessageRepeatedly message1,message1len,10
        writeMessageRepeatedly message2,message2len,2
        writeMessageRepeatedly message3,message3len,7
        exit                         # ukonceni aplikace



# 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

Skript pro překlad vypadá následovně:

as -g --32 macro_labels.s -o macro_labels.o
ld -m elf_i386 -s macro_labels.o

Při spuštění výše uvedeného skriptu dojde k očekávané chybě:

macro_labels.s: Assembler messages:
macro_labels.s:82: Error: symbol `loop' is already defined
macro_labels.s:83: Error: symbol `loop' is already defined
ld: cannot find macro_labels.o: No such file or directory

Pro zajímavost se podívejme, jak vypadá listing překládaného programu při výskytu takové chyby:

as -alm -g --32 macro_labels.s -o macro_labels.o
ld -m elf_i386 -s macro_labels.o
GAS LISTING macro_labels.s                      page 1


   1                    # asmsyntax=as
   2
   3                    # Ukazka pouziti maker v GNU Assembleru - problem s navestimi v makrech
   4                    # - pouzita je "Intel" syntaxe.
   5                    # - zdrojovy kod nelze prelozit (to je v poradku!)
   6                    #
   7                    # Autor: Pavel Tisnovsky
   8
   9                    .intel_syntax noprefix
  10
  11
  12                    # Linux kernel system call table
  13                    sys_exit   = 1
  14                    sys_write  = 4
  15
  16                    # Dalsi konstanty pouzite v programu - standardni streamy
  17                    std_input  = 0
  18                    std_output = 1
  19
  20
  21
  22                    #-----------------------------------------------------------------------------
  23
  24                    # Deklarace makra pro ukonceni aplikace
  25                    .macro exit
  26                            mov   eax, sys_exit          # cislo sycallu pro funkci "exit"
  27                            mov   ebx, 0                 # exit code = 0
  28                            int   0x80                   # volani Linuxoveho kernelu
  29                    .endm
  30
  31
  32
  33                    # Deklarace makra pro vytisteni zpravy na standardni vystup
  34                    .macro writeMessage message,messageLength
  35                            mov   ecx, offset \message   # adresa retezce, ktery se ma vytisknout
  36                            mov   edx, \messageLength    # pocet znaku, ktere se maji vytisknout
  37                            call  write_message          # vytisknout zpravu "Zero flag not set"
  38                    .endm
  39
  40
  41
  42                    # Deklarace makra pro vytisteni zpravy na standardni vystup
  43                    .macro writeMessageRepeatedly message,messageLength,count
  44                            mov   ebp, \count            # nastaveni pocitadla
  45                    loop:                                # globalni navesti!!!
  46                            mov   ecx, offset \message   # adresa retezce, ktery se ma vytisknout
  47                            mov   edx, \messageLength    # pocet znaku, ktere se maji vytisknout
  48                            call  write_message          # vytisknout zpravu "Zero flag not set"
  49                            dec   ebp                    # snizeni hodnoty pocitadla
  50                            jnz   loop                   # opakovani smycky
  51                    .endm
  52
  53
  54
  55                    #-----------------------------------------------------------------------------
  56                    .section .data
  57                    message1:
 


GAS LISTING macro_labels.s                      page 2


  58 ???? 48656C6C              .string "Hello world\n"
  58      6F20776F 
  58      726C640A 
  58      00
  59                    message1len = $ - message1         # delka prvni zpravy
  60
  61                    message2:
  62 ???? 56697465              .string "Vitejte na mojefedora.cz\n"
  62      6A746520 
  62      6E61206D 
  62      6F6A6566 
  62      65646F72 
  63                    message2len = $ - message2         # delka druhe zpravy
  64
  65                    message3:
  66 ???? 41737365              .string "Assembler je fajn\n"
  66      6D626C65 
  66      72206A65 
  66      2066616A 
  66      6E0A00
  67                    message3len = $ - message3         # delka druhe zpravy
  68
  69
  70
  71                    #-----------------------------------------------------------------------------
  72                    .section .bss
  73
  74
  75
  76                    #-----------------------------------------------------------------------------
  77                    .section .text
  78                            .global _start               # tento symbol ma byt dostupny i linkeru
  79
  80                    _start:
  81                            writeMessageRepeatedly message1,message1len,10
  81 ???? BD0A0000      >  mov ebp,10
  81      00
  81                    > loop:
  81 ???? B9000000      >  mov ecx,offset message1
  81      00
  81 ???? BA0D0000      >  mov edx,message1len
  81      00
  81 ???? E8000000      >  call write_message
  81      00
  81 ???? 4D            >  dec ebp
  81 ???? 75            >  jnz loop
  82                            writeMessageRepeatedly message2,message2len,2
  82 ???? BD020000      >  mov ebp,2
  82      00
  82                    > loop:
  82 ???? B9000000      >  mov ecx,offset message2
  82      00
  82 ???? BA1A0000      >  mov edx,message2len
  82      00
  82 ???? E8000000      >  call write_message
  82      00
  82 ???? 4D            >  dec ebp


GAS LISTING macro_labels.s                      page 3


  82 ???? 75            >  jnz loop
  83                            writeMessageRepeatedly message3,message3len,7
  83 ???? BD070000      >  mov ebp,7
  83      00
  83                    > loop:
  83 ???? B9000000      >  mov ecx,offset message3
  83      00
  83 ???? BA130000      >  mov edx,message3len
  83      00
  83 ???? E8000000      >  call write_message
  83      00
  83 ???? 4D            >  dec ebp
  83 ???? 75            >  jnz loop
  84                            exit                         # ukonceni aplikace
  84 ???? B8010000      >  mov eax,sys_exit
  84      00
  84 ???? BB000000      >  mov ebx,0
  84      00
  84 ???? CD80          >  int 0x80
  85
  86
  87
  88                    # Podprogram pro vytisteni zpravy na standardni vystup
  89                    # Ocekava se, ze v ecx bude adresa zpravy a v edx jeji delka
  90                    write_message:
  91 ???? B8040000              mov   eax, sys_write         # cislo syscallu pro funkci "write"
  91      00
  92 ???? BB010000              mov   ebx, std_output        # standardni vystup
  92      00
  93 ???? CD80                  int   0x80
  94 ???? C3                    ret
  95

Vidíme, že se assembleru nepodařilo zjistit všechny adresy (poslední krok překladu), takže na určitá místa musel pouze doplnit otazníky.

8. Využití počitadla volání makra pro vytvoření unikátního návěští

Vzhledem k tomu, že se při práci s makry velmi často setkáme s nutností vytvořit symboly (například právě návěští) s unikátními jmény, obsahuje GNU Assembler velmi jednoduše použitelný nástroj, který je možné v makrech využít. Jedná se o počitadlo použití maker – při každém použití makra se toto počitadlo automaticky zvýší o jedničku. Pokud tedy hodnotu počitadla spojíme s prefixem návěští, budeme mít jistotu, že se vždycky vytvoří unikátní jméno – nové použití makra zvýší počitadlo o jedničku. Počitadlo je v makrech představováno znakem @, před nějž musíme zapsat zpětné lomítko, ostatně jako i v případě parametrů atd. Upravená verze makra pro výpis zprávy může vypadat následovně:

# Deklarace makra pro vytisteni zpravy na standardni vystup
.macro writeMessageRepeatedly message,messageLength,count
        mov   ebp, \count            # nastaveni pocitadla
loop\@:                              # lokalni navesti (unikatni)
        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"
        dec   ebp                    # snizeni hodnoty pocitadla
        jnz   loop\@                 # opakovani smycky
.endm

Poznámka: symbol @ je platný pouze uvnitř maker.

9. Třetí demonstrační příklad – použití počitadla při tvorbě návěští v makrech

Výše popsané makro writeMessageRepeatedly používající počitadlo je použito v dnešním třetím demonstračním příkladu, který je na rozdíl od předchozího příkladu, již bez problémů přeložitelný:

# asmsyntax=as

# Ukazka pouziti maker v GNU Assembleru - tvorba lokalnich navesti
# - pouzita je "Intel" syntaxe.
#
# Autor: Pavel Tisnovsky

.intel_syntax noprefix


# Linux kernel system call table
sys_exit   = 1
sys_write  = 4

# Dalsi konstanty pouzite v programu - standardni streamy
std_input  = 0
std_output = 1



#-----------------------------------------------------------------------------

# 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



# 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



# Deklarace makra pro vytisteni zpravy na standardni vystup
.macro writeMessageRepeatedly message,messageLength,count
        mov   ebp, \count            # nastaveni pocitadla
loop\@:                              # lokalni navesti (unikatni)
        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"
        dec   ebp                    # snizeni hodnoty pocitadla
        jnz   loop\@                 # opakovani smycky
.endm



#-----------------------------------------------------------------------------
.section .data
message1:
        .string "Hello world\n"
message1len = $ - message1         # delka prvni zpravy

message2:
        .string "Vitejte na mojefedora.cz\n"
message2len = $ - message2         # delka druhe zpravy

message3:
        .string "Assembler je fajn\n"
message3len = $ - message3         # delka druhe zpravy



#-----------------------------------------------------------------------------
.section .bss



#-----------------------------------------------------------------------------
.section .text
        .global _start               # tento symbol ma byt dostupny i linkeru

_start:
        writeMessageRepeatedly message1,message1len,10
        writeMessageRepeatedly message2,message2len,2
        writeMessageRepeatedly message3,message3len,7
        exit                         # ukonceni aplikace



# 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

Překlad se provede tímto skriptem:

as -g --32 local_labels.s -o local_labels.o
ld -m elf_i386 -s local_labels.o

Zpětný překlad (disassembling) ukazuje, že se skutečně vygenerovalo větší množství unikátních návěští loop0, loop1 a loop2:

objdump -M intel-mnemonic -f -d -t -h local_labels.o
local_labels.o:     file format elf32-i386
architecture: i386, flags 0x00000011:
HAS_RELOC, HAS_SYMS
start address 0x00000000

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         0000005e  00000000  00000000  00000034  2**0
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .data         0000003a  00000000  00000000  00000092  2**0
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000000  00000000  00000000  000000cc  2**0
                  ALLOC
  3 .debug_line   00000049  00000000  00000000  000000cc  2**0
                  CONTENTS, RELOC, READONLY, DEBUGGING
  4 .debug_info   0000006f  00000000  00000000  00000115  2**0
                  CONTENTS, RELOC, READONLY, DEBUGGING
  5 .debug_abbrev 00000014  00000000  00000000  00000184  2**0
                  CONTENTS, READONLY, DEBUGGING
  6 .debug_aranges 00000020  00000000  00000000  00000198  2**3
                  CONTENTS, RELOC, READONLY, DEBUGGING
SYMBOL TABLE:
00000000 l    d  .text	00000000 .text
00000000 l    d  .data	00000000 .data
00000000 l    d  .bss	00000000 .bss
00000001 l       *ABS*	00000000 sys_exit
00000004 l       *ABS*	00000000 sys_write
00000000 l       *ABS*	00000000 std_input
00000001 l       *ABS*	00000000 std_output
00000000 l       .data	00000000 message1
0000000d l       *ABS*	00000000 message1len
0000000d l       .data	00000000 message2
0000001a l       *ABS*	00000000 message2len
00000027 l       .data	00000000 message3
00000013 l       *ABS*	00000000 message3len
00000005 l       .text	00000000 loop0
00000051 l       .text	00000000 write_message
0000001c l       .text	00000000 loop1
00000033 l       .text	00000000 loop2
00000000 l    d  .debug_info	00000000 .debug_info
00000000 l    d  .debug_abbrev	00000000 .debug_abbrev
00000000 l    d  .debug_line	00000000 .debug_line
00000000 l    d  .debug_aranges	00000000 .debug_aranges
00000000 g       .text	00000000 _start



Disassembly of section .text:

00000000 <_start>:
   0:	bd 0a 00 00 00       	mov    ebp,0xa

00000005 <loop0>:
   5:	b9 00 00 00 00       	mov    ecx,0x0
   a:	ba 0d 00 00 00       	mov    edx,0xd
   f:	e8 3d 00 00 00       	call   51 <write_message>
  14:	4d                   	dec    ebp
  15:	75 ee                	jne    5 <loop0>
  17:	bd 02 00 00 00       	mov    ebp,0x2

0000001c <loop1>:
  1c:	b9 0d 00 00 00       	mov    ecx,0xd
  21:	ba 1a 00 00 00       	mov    edx,0x1a
  26:	e8 26 00 00 00       	call   51 <write_message>
  2b:	4d                   	dec    ebp
  2c:	75 ee                	jne    1c <loop1>
  2e:	bd 07 00 00 00       	mov    ebp,0x7

00000033 <loop2>:
  33:	b9 27 00 00 00       	mov    ecx,0x27
  38:	ba 13 00 00 00       	mov    edx,0x13
  3d:	e8 0f 00 00 00       	call   51 <write_message>
  42:	4d                   	dec    ebp
  43:	75 ee                	jne    33 <loop2>
  45:	b8 01 00 00 00       	mov    eax,0x1
  4a:	bb 00 00 00 00       	mov    ebx,0x0
  4f:	cd 80                	int    0x80

00000051 <write_message>:
  51:	b8 04 00 00 00       	mov    eax,0x4
  56:	bb 01 00 00 00       	mov    ebx,0x1
  5b:	cd 80                	int    0x80
  5d:	c3                   	ret    

Pro úplnost se ještě podívejme na listing s výsledkem expanze maker a překladu. Opět si povšimněte použití trojice unikátních návěští loop0, loop1 a loop2:

GAS LISTING local_labels.s                      page 1


   1                    # asmsyntax=as
   2
   3                    # Ukazka pouziti maker v GNU Assembleru - tvorba lokalnich navesti
   4                    # - pouzita je "Intel" syntaxe.
   5                    #
   6                    # Autor: Pavel Tisnovsky
   7
   8                    .intel_syntax noprefix
   9
  10
  11                    # Linux kernel system call table
  12                    sys_exit   = 1
  13                    sys_write  = 4
  14
  15                    # Dalsi konstanty pouzite v programu - standardni streamy
  16                    std_input  = 0
  17                    std_output = 1
  18
  19
  20
  21                    #-----------------------------------------------------------------------------
  22
  23                    # Deklarace makra pro ukonceni aplikace
  24                    .macro exit
  25                            mov   eax, sys_exit          # cislo sycallu pro funkci "exit"
  26                            mov   ebx, 0                 # exit code = 0
  27                            int   0x80                   # volani Linuxoveho kernelu
  28                    .endm
  29
  30
  31
  32                    # Deklarace makra pro vytisteni zpravy na standardni vystup
  33                    .macro writeMessage message,messageLength
  34                            mov   ecx, offset \message   # adresa retezce, ktery se ma vytisknout
  35                            mov   edx, \messageLength    # pocet znaku, ktere se maji vytisknout
  36                            call  write_message          # vytisknout zpravu "Zero flag not set"
  37                    .endm
  38
  39
  40
  41                    # Deklarace makra pro vytisteni zpravy na standardni vystup
  42                    .macro writeMessageRepeatedly message,messageLength,count
  43                            mov   ebp, \count            # nastaveni pocitadla
  44                    loop\@:                              # lokalni navesti (unikatni)
  45                            mov   ecx, offset \message   # adresa retezce, ktery se ma vytisknout
  46                            mov   edx, \messageLength    # pocet znaku, ktere se maji vytisknout
  47                            call  write_message          # vytisknout zpravu "Zero flag not set"
  48                            dec   ebp                    # snizeni hodnoty pocitadla
  49                            jnz   loop\@                 # opakovani smycky
  50                    .endm
  51
  52
  53
  54                    #-----------------------------------------------------------------------------
  55                    .section .data
  56                    message1:
  57 0000 48656C6C              .string "Hello world\n"


GAS LISTING local_labels.s                    page 2


  57      6F20776F 
  57      726C640A 
  57      00
  58                    message1len = $ - message1         # delka prvni zpravy
  59
  60                    message2:
  61 000d 56697465              .string "Vitejte na mojefedora.cz\n"
  61      6A746520 
  61      6E61206D 
  61      6F6A6566 
  61      65646F72 
  62                    message2len = $ - message2         # delka druhe zpravy
  63
  64                    message3:
  65 0027 41737365              .string "Assembler je fajn\n"
  65      6D626C65 
  65      72206A65 
  65      2066616A 
  65      6E0A00
  66                    message3len = $ - message3         # delka druhe zpravy
  67
  68
  69
  70                    #-----------------------------------------------------------------------------
  71                    .section .bss
  72
  73
  74
  75                    #-----------------------------------------------------------------------------
  76                    .section .text
  77                            .global _start               # tento symbol ma byt dostupny i linkeru
  78
  79                    _start:
  80                            writeMessageRepeatedly message1,message1len,10
  80 0000 BD0A0000      >  mov ebp,10
  80      00
  80                    > loop0:
  80 0005 B9000000      >  mov ecx,offset message1
  80      00
  80 000a BA0D0000      >  mov edx,message1len
  80      00
  80 000f E83D0000      >  call write_message
  80      00
  80 0014 4D            >  dec ebp
  80 0015 75EE          >  jnz loop0
  81                            writeMessageRepeatedly message2,message2len,2
  81 0017 BD020000      >  mov ebp,2
  81      00
  81                    > loop1:
  81 001c B90D0000      >  mov ecx,offset message2
  81      00
  81 0021 BA1A0000      >  mov edx,message2len
  81      00
  81 0026 E8260000      >  call write_message
  81      00
  81 002b 4D            >  dec ebp
  81 002c 75EE          >  jnz loop1


GAS LISTING local_labels.s                    page 3


  82                            writeMessageRepeatedly message3,message3len,7
  82 002e BD070000      >  mov ebp,7
  82      00
  82                    > loop2:
  82 0033 B9270000      >  mov ecx,offset message3
  82      00
  82 0038 BA130000      >  mov edx,message3len
  82      00
  82 003d E80F0000      >  call write_message
  82      00
  82 0042 4D            >  dec ebp
  82 0043 75EE          >  jnz loop2
  83                            exit                         # ukonceni aplikace
  83 0045 B8010000      >  mov eax,sys_exit
  83      00
  83 004a BB000000      >  mov ebx,0
  83      00
  83 004f CD80          >  int 0x80
  84
  85
  86
  87                    # Podprogram pro vytisteni zpravy na standardni vystup
  88                    # Ocekava se, ze v ecx bude adresa zpravy a v edx jeji delka
  89                    write_message:
  90 0051 B8040000              mov   eax, sys_write         # cislo syscallu pro funkci "write"
  90      00
  91 0056 BB010000              mov   ebx, std_output        # standardni vystup
  91      00
  92 005b CD80                  int   0x80
  93 005d C3                    ret
  94

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

Všechny dnes popisované demonstrační příklady byly, podobně jako v 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ženy 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. Následuje tabulka s odkazy na zdrojové kódy příkladů i na již zmíněné podpůrné skripty:

První demonstrační příklad – makra pro vytištění zprávy a ukončení aplikace

# Soubor Popis Odkaz do repositáře
1 macros.s program pro GNU Assembler https://github.com/tisnik/presentations/blob/master/assembler/20_macros/macros.s
2 assemble skript pro překlad na procesorech i386 https://github.com/tisnik/presentations/blob/master/assembler/20_macros/assemble
3 assemble_list skript pro překlad a vygenerování listingu https://github.com/tisnik/presentations/blob/master/assembler/20_macros/assemble_list
4 disassemble skript pro disassembling https://github.com/tisnik/presentations/blob/master/assembler/20_macros/disassemble
5 macros.list výsledek skriptu assemble.list https://github.com/tisnik/presentations/blob/master/assembler/20_macros/macros.list

Druhý demonstrační příklad – nefunkční použití návěští v makru

# Soubor Popis Odkaz do repositáře
1 macro_labels.s program pro GNU Assembler https://github.com/tisnik/presentations/blob/master/assembler/21_macro_labels/macro_labels.s
2 assemble skript pro překlad na procesorech i386 https://github.com/tisnik/presentations/blob/master/assembler/21_macro_labels/assemble
3 assemble_list skript pro překlad a vygenerování listingu https://github.com/tisnik/presentations/blob/master/assembler/21_macro_labels/assemble_list
4 disassemble skript pro disassembling https://github.com/tisnik/presentations/blob/master/assembler/21_macro_labels/disassemble
5 macro_labels.list výsledek skriptu assemble.list https://github.com/tisnik/presentations/blob/master/assembler/21_macro_labels/macro_labels.list

Třetí demonstrační příklad – použití počitadla při tvorbě návěští v makrech

# Soubor Popis Odkaz do repositáře
1 local_labels.s program pro GNU Assembler https://github.com/tisnik/presentations/blob/master/assembler/22_local_labels/local_labels.s
2 assemble skript pro překlad na procesorech i386 https://github.com/tisnik/presentations/blob/master/assembler/22_local_labels/assemble
3 assemble_list skript pro překlad a vygenerování listingu https://github.com/tisnik/presentations/blob/master/assembler/22_local_labels/assemble_list
4 disassemble skript pro disassembling https://github.com/tisnik/presentations/blob/master/assembler/22_local_labels/disassemble
5 local_labels.list výsledek skriptu assemble.list https://github.com/tisnik/presentations/blob/master/assembler/22_local_labels/local_labels.list

11. Odkazy na Internetu

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