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