Obsah
1. TinyCC Assembler: assembler, jenž je součástí Tiny C Compileru
2. Assembler (jazyk symbolických adres)
5. Assembler vestavěný to Tiny C Compileru (TinyCC Assembler)
6. „Hello, world!“ naprogramovaný v assembleru
7. Volání funkcí kernelu na architektuře x86–64
8. Porovnání výsledného programu: GNU Assembler vs TinyCC Assembler
9. Výsledky překladu a slinkování
10. Interní struktura souborů vzniklých překladem pomocí GNU Assembleru a GNU linkeru
11. Interní struktura souborů vzniklých překladem pomocí TinyCC Assembleru
12. Detekce chyb ve zdrojových kódech psaných v assembleru
13. Reakce na zápis neexistující instrukce
14. Využití céčkovského preprocesoru
15. Kombinace assembleru a kódu v C v jediném zdrojovém souboru
17. Repositář s demonstračními příklady
1. TinyCC Assembler: assembler, jenž je součástí Tiny C Compileru
Minulý týden jsme se v článku Minimalistické překladače jazyka C: tcc a Chibicc seznámili mj. i se základními vlastnostmi „malého“ překladače programovacího jazyka C, který se nazývá Tiny C Compiler. Připomeňme si, že předností tohoto překladače je jeho vysoká rychlost překladu v porovnání s GCC či Clangem. Na druhou stranu však Tiny C Compiler postrádá kvalitní optimalizaci výsledného strojového kódu. Dnes si popíšeme ještě jednu další užitečnou součást Tiny C Compileru. Tento překladač totiž obsahuje i assembler, který se nazývá TinyCC Assembler (CC je často používaná zkratka pro „C compiler“). TinyCC Assembler je do určité míry kompatibilní s GNU Assemblerem a opět se jedná o velmi rychlý nástroj. Navíc je možné TinyCC Assembler použít i pro „assembling“ kódů, které vzniknou preprocessingem s využitím klasického preprocesoru z jazyka C (což si taktéž ukážeme). A navíc Tiny C podporuje i vkládání subrutin v assembleru přímo do céčkového kódu.
2. Assembler (jazyk symbolických adres)
Naprostá většina programovacích jazyků, které se používají v současnosti, patří mezi takzvané vysokoúrovňové jazyky, což mj. znamená, že zdrojové kódy programů se v nich zapisují s využitím takových jazykových konstrukcí, které mikroprocesor nedokáže vykonat přímo. Aby bylo možné programy vytvořené například v jazyku Python či v klasickém céčku skutečně spustit, je nutné je nějakým způsobem transformovat do podoby, které již mikroprocesor dokáže provést. Výsledná (transformovaná) podoba programu se nazývá strojový kód (machine code) a pro zmíněnou transformaci se používají interpretry (což je případ jazyka Python, Perl, Ruby, BASHe atd.) nebo překladače (případ programovacího jazyka C, Go, Rustu).
V některých specifických situacích, zejména při programování mikrořadičů, však může být vhodnější vytvářet programy či jejich části v nízkoúrovňovém jazyku, který se přibližuje strojovému kódu. Důvodem může být snaha o vytvoření co nejrychlejšího a/nebo co nejmenšího programového kódu, popř. o přímý přístup k hardware (bit banging).
Vzhledem k tomu, že přímý zápis strojového kódu je pracný, náchylný k chybám a prakticky dlouhodobě neudržovatelný, používá se poněkud odlišný přístup – použití takzvaného assembleru. Assembler neboli též jazyk symbolických adres (JSA) popř. alternativně jazyk symbolických instrukcí (JSI) je nízkoúrovňovým programovacím jazykem, který na hierarchii jazyků sice stojí poměrně vysoko nad strojovým kódem, ovšem na straně druhé je umístěn hluboko pod vyššími kompilovanými programovacími jazyky typu C či interpretovanými jazyky typu Python. Typickou vlastností assembleru je jeho vazba na určitý typ mikroprocesoru, popř. řadu mikroprocesorů (architekturu). Týká se to především sady dostupných instrukcí. Programy se ve většině typech assemblerů zapisují formou symbolických jmen instrukcí, přičemž každá instrukce je představována svou mnemotechnickou zkratkou a případnými operandy (konstantami, adresami, nepřímými adresami, jmény pracovních registrů procesoru atd.).
3. Použití assembleru v praxi
Programování v jazyku symbolických adres již v současnosti není (zcela podle očekávání) nijak masivní záležitostí, a to především z toho důvodu, že tvorba aplikací ve vyšších programovacích jazycích je v porovnání s assemblerem mnohem (mnohdy o jeden až dva řády) rychlejší, aplikace jsou snáze přenositelné na jiné platformy a změna aplikací, tj. přidávání nových vlastností či jejich refaktoring, je ve vyšších programovacích jazycích významně jednodušší. Nesmíme taktéž zapomenout na to, že díky vývoji překladačů vyšších programovacích jazyků se běžně stává, že například algoritmus zapsaný v programovacím jazyku C může co do rychlosti snadno soutěžit s programem napsaným průměrným programátorem v assembleru. I přesto má assembler stále má své nezastupitelné místo, a to jak při zkoumání systémových volání v Linuxu a programování speciálního SW (části ovladačů, multimediální kodeky, některé kritické algoritmy typu FFT). V neposlední řadě se díky použití assembleru můžeme seznámit s principem funkce mikroprocesoru.
Assemblery za sebou mají velmi dlouhý vývoj, protože první nástroje, které se začaly tímto názvem označovat, vznikly již v padesátých letech minulého století, a to na mainframech vyráběných společností IBM i jejími konkurenty (UNIVAC, Burroughs, Honeywell, General Electric atd.). Před vznikem skutečných assemblerů byla situace poněkud složitá. První aplikace pro mainframy totiž byly programovány přímo ve strojovém kódu, který bylo možné přímo zadávat z takzvaného řídicího panelu (control panel) počítače či načítat z externích paměťových médií (děrných štítků, magnetických pásek atd.). Ovšem zapisovat programy přímo ve strojovém kódu je zdlouhavé, vedoucí k častým chybám a pro větší aplikace z mnoha důvodů nepraktické. Z důvodu usnadnění práce programátorů tedy vznikly první utility, jejichž úkolem bylo transformovat programy zapsané s využitím symbolických jmen strojových instrukcí do (binárního) strojového kódu určeného pro konkrétní typ počítače a jeho procesoru.
Těmto programům, jejichž možnosti se postupně vylepšovaly (například do nich přibyla podpora textových maker, řízení víceprůchodového překladu, vytváření výstupních sestav s překládanými symboly, později i skutečné linkování s knihovnami atd.), se začalo říkat assemblery a jazyku určenému pro symbolický zápis programů pak jazyk symbolických instrukcí či jazyk symbolických adres – assembly language (někdy též zkráceně nazývaný assembler, takže toto slovo má vlastně dodnes oba dva významy). Jednalo se o svým způsobem převratnou myšlenku: sám počítač byl použit pro tvorbu programů, čímž odpadla namáhavá práce s tužkou a papírem. Posléze se zjistilo, že i programování přímo v assembleru je většinou pracné a zdlouhavé, takže se na mainframech začaly používat různé vyšší programovací jazyky, zejména FORTRAN a COBOL. Použití vyšších programovacích jazyků bylo umožněno relativně vysokým výpočetním výkonem mainframů i (opět relativně) velkou kapacitou operační paměti; naopak se díky vyšším programovacím jazykům mohly aplikace přenášet na různé typy počítačů, což je nesporná výhoda.
4. Assemblery v Linuxu
V tomto odstavci budeme pod termínem „assembler“ chápat programový nástroj, jenž je určený pro transformaci zdrojového kódu naprogramovaného v jazyku symbolických adres do objektového kódu (ten je nutné ještě slinkovat) nebo přímo do strojového kódu. Pro operační systém Linux vzniklo hned několik takových nástrojů, přičemž některé nástroje jsou komerční a jiné patří mezi open source. Z nekomerčních nástrojů se jedná o známý GNU Assembler, dále pak o nástroj nazvaný Netwide assembler (NASM), nástroj Yasm Modular Assembler či až překvapivě výkonný vasm. NASM a Yasm jsou pro první krůčky v assembleru velmi dobře použitelné, neboť mají dobře zpracovaný mechanismus reakce na chyby, dají se v nich psát čitelné programy atd.
Zásadní problém při snaze o použití Netwide assembleru či Yasmu však nastává v případě, kdy je nutné vyvíjet aplikace určené pro jinou architekturu, než je i386 či x86_64, a to z toho důvodu, že tyto dva nástroje nedokážou pracovat s odlišnou instrukční sadou. Naproti tomu GNU Assembler tímto problémem ani zdaleka netrpí, takže se v následujícím textu s GNU Assemblerem ještě setkáme (a budeme ho porovnávat s TinyCC Assemblerem).
GNU Assembler (zkráceně jen gas) je součástí skupiny nástrojů nazvaných GNU Binutils. Jedná se o nástroje určené pro vytváření a správu binárních souborů obsahujících takzvaný „objektový kód“, dále nástrojů určených pro práci s nativními knihovnami i pro takzvané profilování (profilováním se testuje, ve které části programu se stráví nejvíce času). Mezi nástroje spadající do GNU Binutils patří vedle GNU Assembleru i linker ld (ten dnes použijeme), profiler gprof, správce archivů strojových funkcí ar, nástroj pro odstranění symbolů z objektových a spustitelných souborů strip a několik pomocných utilit typu nm, objdump (i ten dnes použijeme), size a v neposlední řadě také strings.
GNU Assembler je možné použít buď pro překlad uživatelem vytvořených zdrojových kódů nebo pro zpracování kódů vygenerovaných překladači vyšších programovacích jazyků (GCC atd.). Zajímavé je, že všechny moderní verze GNU Assembleru podporují jak původní AT&T syntaxi, tak i (podle mnoha programátorů čitelnější) syntaxi používanou společností Intel.
5. Assembler vestavěný to Tiny C Compileru (TinyCC Assembler)
Výše zmíněný GNU Assembler dnes porovnáme s TinyCC Assemblerem, který je součástí Tiny C Compileru. Tento assembler do značné míry podporuje stejnou syntaxi zápisu jako právě GNU Assembler (tedy AT&T syntaxi atd.). Pro překlad zdrojových programů v assembleru přímo do výsledného spustitelného kódu se používá příkaz:
$ tcc assembly.s
Alternativně je možné nejprve na zdrojový kód aplikovat preprocesor programovacího jazyka C. Potom volání assembleru vypadá nepatrně odlišně:
$ tcc assembly.S
Navíc je možné část kódu v assembleru vložit přímo do céčkovského zdrojového kódu, což je postup, který si ukážeme v navazujících kapitolách.
V případě, že se u instrukcí neuvede šířka operandů (tedy například použijeme mov a nikoli movl), pokusí se assembler v daném kontextu rozhodnout, jakou šířku má použít (podobně jako moderní GNU Assembler).
6. „Hello, world!“ naprogramovaný v assembleru
Pokusme se nyní v assembleru pro mikroprocesory s architekturou x86–64 zapsat program, který na terminál vypíše zprávu „Hello, world!“ a následně se ukončí. Budeme tedy potřebovat realizovat dvě operace:
- Výpis zprávy na terminál (tedy na STDOUT)
- Ukončení procesu
Vzhledem k tomu, že i ta nejjednodušší aplikace naprogramovaná v assembleru musí nějakým způsobem ukončit svou činnost, je nutné buď zavolat vhodnou knihovní funkci (z libc), popř. použít takzvaný „syscall“. V kontextu operačního systému Linux se pod tímto termínem skrývá volání nějaké funkce umístěné přímo v jádru operačního systému.
V praxi to funguje následovně: podle požadavků konkrétní funkce se naplní pracovní registry, popř. datové struktury uložené v paměti, následně se číslo služby uloží do pracovního registru r7 (32bitový ARM s použitím EABI), popř. x8 (ARM64) a následně se zavolá nějaká instrukce, která přepne kontext procesoru do privilegovaného režimu „jádra“ (vyvolá výjimku atd.). Na procesorech s architekturou ARM32 je to instrukce SWI 0h a u ARM64 instrukce SVC #0:
Architektura | Číslo služby v | Instrukce pro syscall | Návratová hodnota v |
---|---|---|---|
ARM 32 s EABI | r7 | SWI 0h | r0 |
ARM 64 | x8 | SVC #0 | x0 |
i386 | eax | INT 80h | eax |
x86_64 | rax | SYSCALL | rax |
Motorola 68k | d0 | TRAP #0 | d0 |
Programy, které si ukážeme a odladíme dnes, budou používat následující dvě systémová volání (syscally):
Syscall | Význam |
---|---|
sys_exit | ukončení procesu s předáním návratového kódu |
sys_write | zápis přes deskriptor souboru (například do standardního výstupu) |
7. Volání funkcí kernelu na architektuře x86–64
Prvním programem, s nímž se dnes seznámíme, je program typu „Hello world!“ zmíněný v předchozí kapitole. Tento program je možné v assembleru procesorů řady x86–64 realizovat poměrně snadno, a to z toho důvodu, že samotné jádro operačního systému obsahuje systémové volání (syscall) určené pro zápis sekvence bajtů do souboru specifikovaného svým deskriptorem, což je konkrétně volání sys_write, o kterém jsme se taktéž zmínili. My sice prozatím neumíme v assembleru pracovat se soubory, to však vůbec nevadí, protože pro každý nový proces jsou automaticky vytvořeny tři deskriptory: standardní vstup, standardní výstup a chybový výstup. A právě standardní výstup použijeme pro výpis řetězce „Hello world!“. Na 64bitovém systému vypadá příslušný syscall takto:
Registr | Význam | Obsah |
---|---|---|
rax | číslo syscallu | sys_write=1 |
rdi | číslo deskriptoru | stdout=1 |
rsi | adresa řetězce/bajtů | nastaví se do .text, .data atd. segmentu |
rdx | počet bajtů pro zápis | strlen(„Hello world!\n“), lze vypočítat při assemblingu |
A druhým voláním bude ukončení procesu. Zde je nutné naplnit registr rax číslem syscallu a registr rdi návratovým celočíselným kódem:
Registr | Význam | Obsah |
---|---|---|
rax | číslo syscallu | sys_exit=60 |
rdi | návratový kód | 0 |
8. Porovnání výsledného programu: GNU Assembler vs TinyCC Assembler
Podívejme se nyní na to, jak bude vypadat zápis programu typu „Hello world!“ napsaný v GNU Assembleru ve variantě pro mikroprocesory s architekturou x86–64. Celý program volá pouze dvě služby jádra zmíněné a popsané výše: sys_write a sys_exit. U sys_write se nastaví registry způsobem popsaným ve výše uvedené tabulce (tedy číslo syscallu, adresu řetězce, délku řetězce a číslo STDOUT). Z pohledu programátora je „nejsložitější“ naplnění registru rsi, protože ten musí obsahovat adresu řetězce (resp. bloku bajtů). V AT&T syntaxi to vypadá následovně:
mov $message, %rsi # adresa zpravy
Přičemž message je jméno návěští (label) neboli pojmenovaná adresa. Samotný řetězec může ležet buď v sekci .data nebo i .text (což je kryptické označení pro segment se strojovými instrukcemi). Povšimněte si, že řetězec není ukončen znakem s ASCII kódem 0. To není nutné, protože systémová služba přesně zná délku řetězce (bloku bajtů), na rozdíl od céčkovských funkcí, které naopak hledají ukončující nulu. Navíc ihned poté vypočítáme délku řetězce jako rozdíl mezi aktuální adresou (tečka) a adresou návěští message:
message: .ascii "Hello, world!\n" # zprava, ktera se ma vypsat length = . - message # vypocet delky zpravy primo v prubehu preklad
Úplný zdrojový kód vypadá následovně:
# asmsyntax=as # Sablona pro zdrojovy kod Linuxoveho programu naprogramovaneho # v GNU assembleru (GAS). # # Autor: Pavel Tisnovsky # Linux kernel system call table sys_write = 1 sys_exit = 60 .section .text .global _start # tento symbol ma byt dostupny i linkeru _start: mov $sys_write, %rax # cislo sycallu pro funkci "sys_write" na architekture x86-64 mov $1, %rdi # handle, 1 = STDOUT mov $message, %rsi # adresa zpravy mov $length, %rdx # delka zpravy syscall # zavolat funkci Linuxoveho kernelu mov $sys_exit, %rax # cislo sycallu pro funkci "sys_exit" na architekture x86-64 mov $0, %rdi # exit code = 0 syscall # zavolat funkci Linuxoveho kernelu message: .ascii "Hello, world!\n" # zprava, ktera se ma vypsat length = . - message # vypocet delky zpravy primo v prubehu preklad
Prakticky stejně bude vypadat kód upravený pro TinyCC Assembler. Pouze (protože se slinkování provádí odlišně) zaměníme jméno symbolu _start za main, což znamená, že vlastně programujeme obdobu céčkovské funkce main:
# asmsyntax=as # Sablona pro zdrojovy kod Linuxoveho programu naprogramovaneho # v assembleru tcc. # # Autor: Pavel Tisnovsky # Linux kernel system call table sys_write = 1 sys_exit = 60 .section .text .global main # tento symbol ma byt dostupny i linkeru main: mov $sys_write, %rax # cislo sycallu pro funkci "sys_write" na architekture x86-64 mov $1, %rdi # handle, 1 = STDOUT mov $message, %rsi # adresa zpravy mov $length, %rdx # delka zpravy syscall # zavolat funkci Linuxoveho kernelu mov $sys_exit, %rax # cislo sycallu pro funkci "sys_exit" na architekture x86-64 mov $0, %rdi # exit code = 0 syscall # zavolat funkci Linuxoveho kernelu message: .ascii "Hello, world!\n" # zprava, ktera se ma vypsat length = . - message # vypocet delky zpravy primo v prubehu preklad
9. Výsledky překladu a slinkování
Překlad a slinkování s využitím GNU Assembleru provedeme příkazy:
$ as hello_world_x86_64_gas.s -o hello_world.o $ ld -s hello_world.o
Výsledkem prvního příkazu bude soubor hello_world.o (který je určený pro linker) a výsledkem druhého příkazu pak spustitelný soubor a.out. Velikosti těchto souborů:
-rwxrwxr-x 1 ptisnovs ptisnovs 4368 Mar 23 16:54 a.out -rw-rw-r-- 1 ptisnovs ptisnovs 984 Mar 23 16:54 hello_world.o
Soubor a.out můžeme spustit a zjistit, zda se skutečně jedná o aplikaci typu „Hello, world“:
$ ./a.out Hello, world!
Překlad a slinkování s využitím TinyCC Assembleru se provede jediným příkazem:
$ tcc hello_world_x86_64_tcc.s
Výsledkem bude opět přímo spustitelný soubor a.out, který je ovšem kratší:
-rwxrwxr-x 1 ptisnovs ptisnovs 2932 Mar 23 16:56 a.out
Opět si pro jistotu otestujme, že výsledný soubor je spustitelný a provádí očekávanou činnost:
$ ./a.out Hello, world!
10. Interní struktura souborů vzniklých překladem pomocí GNU Assembleru a GNU linkeru
Na interní obsah objektového souboru i výsledného spustitelného souboru se můžeme podívat utilitkou objdump, a to následujícím způsobem:
$ objdump -f -d -t -h jméno_souboru
Nejdříve se podívejme na objektový soubor hello_world.o vygenerovaný GNU assemblerem po zadání příkazu:
$ as hello_world_x86_64_gas.s -o hello_world.o
Interní strukturu tohoto souboru si zobrazíme příkazem:
$ objdump -f -d -t -h hello_world.o
Výsledek by měl vypadat zhruba následovně:
hello_world.o: file format elf64-x86-64 architecture: i386:x86-64, flags 0x00000011: HAS_RELOC, HAS_SYMS start address 0x0000000000000000 Sections: Idx Name Size VMA LMA File off Algn 0 .text 0000003c 0000000000000000 0000000000000000 00000040 2**0 CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE 1 .data 00000000 0000000000000000 0000000000000000 0000007c 2**0 CONTENTS, ALLOC, LOAD, DATA 2 .bss 00000000 0000000000000000 0000000000000000 0000007c 2**0 ALLOC SYMBOL TABLE: 0000000000000000 l d .text 0000000000000000 .text 0000000000000000 l d .data 0000000000000000 .data 0000000000000000 l d .bss 0000000000000000 .bss 0000000000000001 l *ABS* 0000000000000000 sys_write 000000000000003c l *ABS* 0000000000000000 sys_exit 000000000000002e l .text 0000000000000000 message 000000000000000e l *ABS* 0000000000000000 length 0000000000000000 g .text 0000000000000000 _start Disassembly of section .text: 0000000000000000 <_start>: 0: 48 c7 c0 01 00 00 00 mov $0x1,%rax 7: 48 c7 c7 01 00 00 00 mov $0x1,%rdi e: 48 c7 c6 00 00 00 00 mov $0x0,%rsi 15: 48 c7 c2 0e 00 00 00 mov $0xe,%rdx 1c: 0f 05 syscall 1e: 48 c7 c0 3c 00 00 00 mov $0x3c,%rax 25: 48 c7 c7 00 00 00 00 mov $0x0,%rdi 2c: 0f 05 syscall 000000000000002e <message>: 2e: 48 rex.W 2f: 65 6c gs insb (%dx),%es:(%rdi) 31: 6c insb (%dx),%es:(%rdi) 32: 6f outsl %ds:(%rsi),(%dx) 33: 2c 20 sub $0x20,%al 35: 77 6f ja a6 <sys_exit+0x6a> 37: 72 6c jb a5 <sys_exit+0x69> 39: 64 21 0a and %ecx,%fs:(%rdx)
Pro zajímavost se pokusme zjistit, jak se liší struktura souboru a.out vygenerovaného GNU linkerem:
a.out: file format elf64-x86-64 architecture: i386:x86-64, flags 0x00000102: EXEC_P, D_PAGED start address 0x0000000000401000 Sections: Idx Name Size VMA LMA File off Algn 0 .text 0000003c 0000000000401000 0000000000401000 00001000 2**0 CONTENTS, ALLOC, LOAD, READONLY, CODE SYMBOL TABLE: no symbols Disassembly of section .text: 0000000000401000 <.text>: 401000: 48 c7 c0 01 00 00 00 mov $0x1,%rax 401007: 48 c7 c7 01 00 00 00 mov $0x1,%rdi 40100e: 48 c7 c6 2e 10 40 00 mov $0x40102e,%rsi 401015: 48 c7 c2 0e 00 00 00 mov $0xe,%rdx 40101c: 0f 05 syscall 40101e: 48 c7 c0 3c 00 00 00 mov $0x3c,%rax 401025: 48 c7 c7 00 00 00 00 mov $0x0,%rdi 40102c: 0f 05 syscall 40102e: 48 rex.W 40102f: 65 6c gs insb (%dx),%es:(%rdi) 401031: 6c insb (%dx),%es:(%rdi) 401032: 6f outsl %ds:(%rsi),(%dx) 401033: 2c 20 sub $0x20,%al 401035: 77 6f ja 0x4010a6 401037: 72 6c jb 0x4010a5 401039: 64 21 0a and %ecx,%fs:(%rdx)
11. Interní struktura souborů vzniklých překladem pomocí TinyCC Assembleru
Stejným nástrojem, tj. využitím objdump si můžeme prohlédnout i strukturu spustitelného souboru vygenerovaného s využitím TinyCC Assembleru. Z výpisu bude patrné, že linker přidal větší množství sekcí (většinou prázdných či jen minimální délky) a taktéž se kromě subrutiny main (tu jsme naprogramovali my) objevila i subrutina main-0×35 umístěná na začátku segmentu .text (linker tedy pracuje poměrně šablonovitě, ale to v praxi nevadí). Taktéž si povšimněte sekcí .init a .fini:
a.out: file format elf64-x86-64 architecture: i386:x86-64, flags 0x00000112: EXEC_P, HAS_SYMS, D_PAGED start address 0x0000000000400320 Sections: Idx Name Size VMA LMA File off Algn 0 .interp 0000001c 0000000000400158 0000000000400158 00000158 2**0 CONTENTS, ALLOC, LOAD, READONLY, DATA 1 .dynsym 00000090 0000000000400174 0000000000400174 00000174 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 2 .dynstr 00000051 0000000000400204 0000000000400204 00000204 2**0 CONTENTS, ALLOC, LOAD, READONLY, DATA 3 .hash 00000030 0000000000400258 0000000000400258 00000258 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 4 .rela.got 00000090 0000000000400288 0000000000400288 00000288 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 5 .text 000000f5 0000000000400320 0000000000400320 00000320 2**4 CONTENTS, ALLOC, LOAD, READONLY, CODE 6 .rodata.cst4 00000004 0000000000400418 0000000000400418 00000418 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 7 .eh_frame 000000d8 0000000000400420 0000000000400420 00000420 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA 8 .init 0000001b 00000000004004f8 00000000004004f8 000004f8 2**2 CONTENTS, ALLOC, LOAD, READONLY, CODE 9 .fini 0000000d 0000000000400514 0000000000400514 00000514 2**2 CONTENTS, ALLOC, LOAD, READONLY, CODE 10 .preinit_array 00000000 0000000000400528 0000000000400528 00000528 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA 11 .init_array 00000000 0000000000400528 0000000000400528 00000528 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA 12 .fini_array 00000000 0000000000400528 0000000000400528 00000528 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA 13 .data 00000004 0000000000600528 0000000000600528 00000528 2**3 CONTENTS, ALLOC, LOAD, DATA 14 .dynamic 000000a0 000000000060052c 000000000060052c 0000052c 2**2 CONTENTS, ALLOC, LOAD, DATA 15 .got 00000048 00000000006005d0 00000000006005d0 000005d0 2**3 CONTENTS, ALLOC, LOAD, DATA 16 .bss 00000000 0000000000600618 0000000000600618 00000618 2**3 ALLOC SYMBOL TABLE: no symbols Disassembly of section .text: 0000000000400320 <main-0x35>: 400320: f3 0f 1e fa endbr64 400324: 31 ed xor %ebp,%ebp 400326: 49 89 d1 mov %rdx,%r9 400329: 5e pop %rsi 40032a: 48 89 e2 mov %rsp,%rdx 40032d: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp 400331: 50 push %rax 400332: 54 push %rsp 400333: 4c 8b 05 ae 02 20 00 mov 0x2002ae(%rip),%r8 # 6005e8 <__libc_csu_fini+0x2001d8> 40033a: 48 8b 0d af 02 20 00 mov 0x2002af(%rip),%rcx # 6005f0 <__libc_csu_fini+0x2001e0> 400341: 48 8b 3d b0 02 20 00 mov 0x2002b0(%rip),%rdi # 6005f8 <__libc_csu_fini+0x2001e8> 400348: ff 15 b2 02 20 00 callq *0x2002b2(%rip) # 600600 <__libc_csu_fini+0x2001f0> 40034e: f4 hlt 40034f: 90 nop 400350: f3 0f 1e fa endbr64 400354: c3 retq 0000000000400355 <main>: 400355: 48 c7 c0 01 00 00 00 mov $0x1,%rax 40035c: 48 c7 c7 01 00 00 00 mov $0x1,%rdi 400363: 48 c7 c6 83 03 40 00 mov $0x400383,%rsi 40036a: 48 c7 c2 0e 00 00 00 mov $0xe,%rdx 400371: 0f 05 syscall 400373: 48 c7 c0 3c 00 00 00 mov $0x3c,%rax 40037a: 48 c7 c7 00 00 00 00 mov $0x0,%rdi 400381: 0f 05 syscall 400383: 48 rex.W 400384: 65 6c gs insb (%dx),%es:(%rdi) 400386: 6c insb (%dx),%es:(%rdi) 400387: 6f outsl %ds:(%rsi),(%dx) 400388: 2c 20 sub $0x20,%al 40038a: 77 6f ja 4003fb <__libc_csu_init+0x5b> 40038c: 72 6c jb 4003fa <__libc_csu_init+0x5a> 40038e: 64 21 0a and %ecx,%fs:(%rdx) ... 00000000004003a0 <__libc_csu_init>: 4003a0: f3 0f 1e fa endbr64 4003a4: 41 57 push %r15 4003a6: 4c 8d 3d 7b 01 00 00 lea 0x17b(%rip),%r15 # 400528 <__libc_csu_fini+0x118> 4003ad: 41 56 push %r14 4003af: 49 89 d6 mov %rdx,%r14 4003b2: 41 55 push %r13 4003b4: 49 89 f5 mov %rsi,%r13 4003b7: 41 54 push %r12 4003b9: 41 89 fc mov %edi,%r12d 4003bc: 55 push %rbp 4003bd: 48 8d 2d 64 01 00 00 lea 0x164(%rip),%rbp # 400528 <__libc_csu_fini+0x118> 4003c4: 53 push %rbx 4003c5: 4c 29 fd sub %r15,%rbp 4003c8: 48 83 ec 08 sub $0x8,%rsp 4003cc: e8 27 01 00 00 callq 4004f8 <__libc_csu_fini+0xe8> 4003d1: 48 c1 fd 03 sar $0x3,%rbp 4003d5: 74 1f je 4003f6 <__libc_csu_init+0x56> 4003d7: 31 db xor %ebx,%ebx 4003d9: 0f 1f 80 00 00 00 00 nopl 0x0(%rax) 4003e0: 4c 89 f2 mov %r14,%rdx 4003e3: 4c 89 ee mov %r13,%rsi 4003e6: 44 89 e7 mov %r12d,%edi 4003e9: 41 ff 14 df callq *(%r15,%rbx,8) 4003ed: 48 83 c3 01 add $0x1,%rbx 4003f1: 48 39 dd cmp %rbx,%rbp 4003f4: 75 ea jne 4003e0 <__libc_csu_init+0x40> 4003f6: 48 83 c4 08 add $0x8,%rsp 4003fa: 5b pop %rbx 4003fb: 5d pop %rbp 4003fc: 41 5c pop %r12 4003fe: 41 5d pop %r13 400400: 41 5e pop %r14 400402: 41 5f pop %r15 400404: c3 retq 400405: 66 66 2e 0f 1f 84 00 data16 nopw %cs:0x0(%rax,%rax,1) 40040c: 00 00 00 00 0000000000400410 <__libc_csu_fini>: 400410: f3 0f 1e fa endbr64 400414: c3 retq Disassembly of section .init: 00000000004004f8 <.init>: 4004f8: f3 0f 1e fa endbr64 4004fc: 48 83 ec 08 sub $0x8,%rsp 400500: 48 8b 05 09 01 20 00 mov 0x200109(%rip),%rax # 600610 <__gmon_start__> 400507: 48 85 c0 test %rax,%rax 40050a: 74 02 je 40050e <__libc_csu_fini+0xfe> 40050c: ff d0 callq *%rax 40050e: 48 83 c4 08 add $0x8,%rsp 400512: c3 retq Disassembly of section .fini: 0000000000400514 <.fini>: 400514: f3 0f 1e fa endbr64 400518: 48 83 ec 08 sub $0x8,%rsp 40051c: 48 83 c4 08 add $0x8,%rsp 400520: c3 retq
12. Detekce chyb ve zdrojových kódech psaných v assembleru
Zajímavé bude zjistit, jak kvalitně vlastně dokáže TinyCC Assembler reagovat na chyby v zápisu zdrojových kódů, protože z minula již víme, že samotný Tiny C Compiler je v tomto ohledu dosti strohý a reaguje pouze na skutečné chyby v zápisu a nikoli „pouze“ na potenciálně problematická místa ve zdrojovém kódu.
Pro tento účel nepatrně pozměníme náš ukázkový příklad, a to konkrétně tak, že při volání systémové funkce sys_write použijeme symbol message_ (s podtržítkem na konci), který ovšem není nikde definován. Nejprve si otestujeme GNU Assembler:
# Linux kernel system call table sys_write = 1 sys_exit = 60 .section .text .global _start # tento symbol ma byt dostupny i linkeru _start: mov $sys_write, %rax # cislo sycallu pro funkci "sys_write" na architekture x86-64 mov $1, %rdi # handle, 1 = STDOUT mov $message_, %rsi # adresa zpravy (spatny identifikator) mov $length, %rdx # delka zpravy syscall # zavolat funkci Linuxoveho kernelu mov $sys_exit, %rax # cislo sycallu pro funkci "sys_exit" na architekture x86-64 mov $0, %rdi # exit code = 0 syscall # zavolat funkci Linuxoveho kernelu message: .ascii "Hello, world!\n" # zprava, ktera se ma vypsat length = . - message # vypocet delky zpravy primo v prubehu preklad
Stejnou úpravu provedeme i pro variantu určenou pro TinyCC Assembler:
# Linux kernel system call table sys_write = 1 sys_exit = 60 .section .text .global main # tento symbol ma byt dostupny i linkeru main: mov $sys_write, %rax # cislo sycallu pro funkci "sys_write" na architekture x86-64 mov $1, %rdi # handle, 1 = STDOUT mov $message_, %rsi # adresa zpravy (spatny identifikator) mov $length, %rdx # delka zpravy syscall # zavolat funkci Linuxoveho kernelu mov $sys_exit, %rax # cislo sycallu pro funkci "sys_exit" na architekture x86-64 mov $0, %rdi # exit code = 0 syscall # zavolat funkci Linuxoveho kernelu message: .ascii "Hello, world!\n" # zprava, ktera se ma vypsat length = . - message # vypocet delky zpravy primo v prubehu preklad
Pokus o překlad pomocí GNU Assembleru dopadne v pořádku(!):
$ as broken_gas_1.s -o broken.o
Chyba se objeví později, konkrétně až při spuštění linkeru:
$ ld -s broken.o ld: broken.o: in function `_start': (.text+0x11): undefined reference to `message_'
TinyCC Assembler provádí překlad a linking v jednom kroku (pokud explicitně neurčíme jinak) a proto je tato chyba odhalena ihned:
$ tcc broken_tcc_1.s tcc: error: undefined symbol 'message_'
13. Reakce na zápis neexistující instrukce
Nyní náš zdrojový kód „rozbijeme“ odlišným způsobem – použijeme neznámou instrukci mvv (tato instrukce sice existuje, ale nikoli v instrukční sadě mikroprocesorů x86–64). Opět si nejdříve uveďme variantu určenou pro GNU Assembler:
# Linux kernel system call table sys_write = 1 sys_exit = 60 .section .text .global _start # tento symbol ma byt dostupny i linkeru _start: mov $sys_write, %rax # cislo sycallu pro funkci "sys_write" na architekture x86-64 mov $1, %rdi # handle, 1 = STDOUT mov $message, %rsi # adresa zpravy mvv $length, %rdx # delka zpravy (neznama instrukce) syscall # zavolat funkci Linuxoveho kernelu mov $sys_exit, %rax # cislo sycallu pro funkci "sys_exit" na architekture x86-64 mov $0, %rdi # exit code = 0 syscall # zavolat funkci Linuxoveho kernelu message: .ascii "Hello, world!\n" # zprava, ktera se ma vypsat length = . - message # vypocet delky zpravy primo v prubehu preklad
Varianta pro TinyCC Assembler vypadá následovně:
# Linux kernel system call table sys_write = 1 sys_exit = 60 .section .text .global main # tento symbol ma byt dostupny i linkeru main: mov $sys_write, %rax # cislo sycallu pro funkci "sys_write" na architekture x86-64 mov $1, %rdi # handle, 1 = STDOUT mov $message, %rsi # adresa zpravy mvv $length, %rdx # delka zpravy (neplatna instrukce) syscall # zavolat funkci Linuxoveho kernelu mov $sys_exit, %rax # cislo sycallu pro funkci "sys_exit" na architekture x86-64 mov $0, %rdi # exit code = 0 syscall # zavolat funkci Linuxoveho kernelu message: .ascii "Hello, world!\n" # zprava, ktera se ma vypsat length = . - message # vypocet delky zpravy primo v prubehu preklad
GNU Assembler již nyní chybu detekuje (na rozdíl od neznámého návěští) a dokonce přesně určí, kde tato chyba ve zdrojovém kódu leží:
$ as broken_gas_2.s -o broken.o broken_gas_2.s: Assembler messages: broken_gas_2.s:13: Error: no such instruction: `mvv $length,%rdx'
Podobně je tomu i v případě TinyCC Assembleru, kde se opět přesně dozvíme, kde chyba vznikla:
$ tcc broken_tcc_2.s broken_tcc_2.s:13: error: unknown opcode 'mvv'
14. Využití céčkovského preprocesoru
V případě, že se překládají soubory s koncovkou „.S“ (velké S), nejprve jsou takové soubory zpracovány preprocesorem jazyka C (tedy v případě GNU C by se jednalo o cpp, ovšem tcc vše implementuje v jediném spustitelném souboru). Výsledek je předáván do TinyCC Assembleru.
Ukažme si to na jednoduchém příkladu, v němž tabulku s hodnotami čísel systémových funkcí (syscallů) uložíme do souboru syscalls.s, aby bylo možné tyto hodnoty znovupoužít v dalších assemblerovských programech:
sys_write = 1 sys_exit = 60
Tuto tabulku (resp. přesněji řečeno obsah celého souboru) vložíme do překládaného programu pomocí direktivy céčkovského preprocesoru:
#include "syscalls.s"
Ještě, nyní pouze pro ilustraci, do zdrojového kódu vložíme nepřeložitelný text, který ovšem obalíme direktivou preprocesoru, která zajistí, že tento text bude preprocesorem odstraněn a assembler ho vlastně vůbec neuvidí:
#ifdef XYZZY naprosto cizí text naprosto cizí text naprosto cizí text #endif
Výsledek vypadá následovně:
# asmsyntax=as # Sablona pro zdrojovy kod Linuxoveho programu naprogramovaneho # v assembleru tcc. # # Autor: Pavel Tisnovsky # Linux kernel system call table #include "syscalls.s" #ifdef XYZZY naprosto cizí text naprosto cizí text naprosto cizí text #endif .section .text .global main # tento symbol ma byt dostupny i linkeru main: mov $sys_write, %rax # cislo sycallu pro funkci "write" mov $1, %rdi # handle, 1 = STDOUT mov $message, %rsi # adresa zpravy mov $length, %rdx # delka zpravy syscall mov $sys_exit, %eax # cislo sycallu pro funkci "exit" mov $0,%edi # exit code = 0 syscall # volani Linuxoveho kernelu message: .ascii "Hello, world!\n" length = . - message
Otestujme si, že je tento program jako celek přeložitelný a spustitelný:
$ tcc preprocessor.S $ ./a.out Hello, world!
15. Kombinace assembleru a kódu v C v jediném zdrojovém souboru
V mnoha překladačích programovacího jazyka C, popř. jazyka C++ nalezneme příkaz asm, za kterým následuje buď programový blok umístěný ve složených závorkách (tento způsob se používal například u kdysi populárních překladačů společnosti Borland) nebo blok umístěný v závorkách kulatých. V případě překladače Tiny C Compiler se používá druhý způsob, který je odvozen od GNU Assembleru. Ovšem vzhledem k tomu, že asm není rezervované klíčové slovo specifikované ve standardech C, nebude tento blok správně rozeznán při překladu v jiných překladačích s volbami -ansi a/nebo -std.
Instrukce zapsané v blocích asm či __asm__ se překladačem tcc zpracovávají způsobem, který může vzdáleně připomínat expanzi maker. V podstatě se provádí pouze náhrady čísel parametrů za jména registrů a takto upravený kód se předá do assembleru, a to dokonce včetně znaků pro konce řádků, mezer na začátcích řádků atd. Z tohoto důvodu se již tradičně celý program zapisuje formou řetězce, kde se na každém řádku explicitně uvádí znak pro odřádkování \n a znak tabulátoru \t ve chvíli, kdy se nezapisuje řádek s návěštím (label); zde by naopak tabulátor překážel. Za tímto řetězcem se zapisuje nepovinný seznam výstupních registrů, seznam vstupních registrů a konečně seznam registrů používaných uvnitř kódu. Jednotlivé seznamy se od sebe oddělují dvojtečkou. Celý zápis může vypadat následovně:
int main() { asm ( "nop \n\t" " nop \n\t" " nop \n\t" " nop \n\t" " nop # komentar \n\t" : /* zadne vystupni registry */ : /* zadne vstupni operandy */ : /* zadne registry pouzivane uvnitr kodu */ ); return 0; }
Zkusme si nyní vytvořit složitější příklad, v němž (samozřejmě v assembleru) sečteme obsah dvou celočíselných proměnných a uložíme výsledek do proměnné třetí. Pro jednoduchost se budou vstupní proměnné jmenovat x a y, proměnná výstupní se bude jmenovat result. Blok napsaný v assembleru se vlastně nebude příliš odlišovat od předchozího příkladu s nopy, ovšem musíme specifikovat výstupní operand i operandy vstupní. Používá se podobný způsob zápisu, ovšem bez znaku „=“. Vstupní operandy vytváří se vstupními operandy jednu ucelenou řadu, takže v tomto konkrétním příkladu bude mít výstupní operand označení %0, první vstupní operand označení %1 a druhý operand pochopitelně označení %2:
asm ("add %1, %2\n\t" "mov %2, %1" : "=r" (result) : "r" (x), "r" (y));
Samozřejmě je možné – a často se s tím setkáme – použít jediný registr jak pro vstupní, tak i pro výstupní operand. Celý blok psaný v assembleru se nám v takovém případě zjednoduší na jedinou instrukci a navíc nebudeme muset specifikovat žádný pracovní registr:
asm ("add %1, %2" : "=r" (result) : "r" (x), "0" (y));
Můžeme dokonce (zkráceně) specifikovat i jména použitých registrů, kde a bude odpovídat rax, b bude odpovídat rbx atd.:
asm ("add %%rbx, %%rax;" : "=a" (result) : "a" (x), "b" (y));
Vše si otestujme na tomto příkladu se dvěma variantami funkce add:
#include <stdio.h> static inline int add1(int x, int y) { int result; asm ("add %1, %2\n\t" "mov %2, %1" : "=r" (result) : "r" (x), "r" (y)); return result; } static inline int add2(int x, int y) { int result; asm ("add %1, %2" : "=r" (result) : "r" (x), "0" (y)); return result; } int main(void) { int x1 = add1(1, 2); printf("Result#1 = %d\n", x1); int x2 = add2(1, 2); printf("Result#2 = %d\n", x2); return 0; }
16. Závěr
Jak je z předchozích kapitol patrné, může být Tiny C Compiler užitečný nejenom jako „pouhý“ překladač programovacího jazyka C, ale můžeme ho využít i ve funkci assembleru. Možnosti tohoto assembleru zhruba odpovídají GNU Assembleru, a to jak použitou syntaxí, tak i kvalitou a rozsahem chybových hlášení. V neposlední řadě nabízí Tiny C Compiler i možnost vestavby subrutin psaných assembleru přímo do zdrojového céčkovského kódu, což sice poruší přenositelnost, ovšem v některých případech (SIMD atd.) se může jednat o užitečnou vlastnost.
17. Repositář s demonstračními příklady
Všechny výše popsané demonstrační příklady byly uloženy do repositáře dostupného na adrese https://github.com/tisnik/presentations/. V tabulce umístěné pod tímto odstavcem jsou uvedeny odkazy na tyto příklady:
18. Odkazy na Internetu
- Původní domovská stránka Tiny C Compileru
https://bellard.org/tcc/ - Tiny C Compiler na Wikipedii
https://en.wikipedia.org/wiki/Tiny_C_Compiler - TinyCC Assembler
https://bellard.org/tcc/tcc-doc.html#asm - Repositář Tiny C Compileru
https://repo.or.cz/w/tinycc.git - Linux System Call Table for x86 64
https://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/ - A Practical Guide to GCC Inline Assembly
https://blog.alex.balgavy.eu/a-practical-guide-to-gcc-inline-assembly/ - Bit banging
https://en.wikipedia.org/wiki/Bit_banging - Warnings Are Your Friend – A Code Quality Primer
https://hackaday.com/2018/11/06/warnings-are-your-friend-a-code-quality-primer/ - Defending Against Compiler-Based Backdoors
https://blog.regehr.org/archives/1241 - Reflections on Trusting Trust
https://www.win.tue.nl/~aeb/linux/hh/thompson/trust.html - Coding Machines (povídka)
https://www.teamten.com/lawrence/writings/coding-machines/ - Stage0
https://bootstrapping.miraheze.org/wiki/Stage0 - Projekt stage0 na GitHubu
https://github.com/oriansj/stage0 - Bootstraping wiki
https://bootstrapping.miraheze.org/wiki/Main_Page - Bootstrapped 6502 Assembler
https://github.com/robinluckey/bootstrap-6502 - IBM Basic assembly language and successors (Wikipedia)
https://en.wikipedia.org/wiki/IBM_Basic_assembly_language_and_successors - X86 Assembly/Bootloaders
https://en.wikibooks.org/wiki/X86_Assembly/Bootloaders - run6502, lib6502 — 6502 microprocessor emulator
http://piumarta.com/software/lib6502/ - Simple Computer Simulator Instruction-Set
http://www.science.smith.edu/dftwiki/index.php/Simple_Computer_Simulator_Instruction-Set - Bootstrapping#Computing (Wikipedia)
https://en.wikipedia.org/wiki/Bootstrapping#Computing - Bootstrapping (compilers)
https://en.wikipedia.org/wiki/Bootstrapping_%28compilers%29 - Bootstrapable Builds
http://bootstrappable.org/ - What is a coder's worst nightmare?
https://www.quora.com/What-is-a-coders-worst-nightmare/answer/Mick-Stute - Linux Assembly
http://asm.sourceforge.net/ - Tombstone diagram (Wikipedia)
https://en.wikipedia.org/wiki/Tombstone_diagram - History of compiler construction (Wikipedia)
https://en.wikipedia.org/wiki/History_of_compiler_construction - Self-hosting (Wikipedia)
https://en.wikipedia.org/wiki/Self-hosting - GNU Mes: Maxwell Equations of Software
https://gitlab.com/janneke/mes - Tiny C Compiler
https://bellard.org/tcc/ - Welcome to C–
https://www.cs.tufts.edu/~nr/c--/index.html - c4 – C in four functions
https://github.com/rswier/c4 - Tiny BASIC (Wikipedia)
https://en.wikipedia.org/wiki/Tiny_BASIC - ARM GCC Inline Assembler Cookbook
http://www.ethernut.de/en/documents/arm-inline-asm.html - Extended Asm – Assembler Instructions with C Expression Operands
https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html - ARM inline asm secrets
http://hardwarebug.org/2010/07/06/arm-inline-asm-secrets/ - How to Use Inline Assembly Language in C Code
https://gcc.gnu.org/onlinedocs/gcc/Using-Assembly-Language-with-C.html#Using-Assembly-Language-with-C - GCC-Inline-Assembly-HOWTO
http://ibiblio.org/gferg/ldp/GCC-Inline-Assembly-HOWTO.html - A Brief Tutorial on GCC inline asm (x86 biased)
http://www.osdever.net/tutorials/view/a-brief-tutorial-on-gcc-inline-asm - GCC Inline ASM
http://locklessinc.com/articles/gcc_asm/ - System cally pro AArch64 na Linuxu
https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/unistd.h - C Functions Without Arguments
https://eklitzke.org/c-functions-without-arguments - GNU Assembler Examples
http://cs.lmu.edu/~ray/notes/gasexamples/ - X86 Assembly/Arithmetic
https://en.wikibooks.org/wiki/X86_Assembly/Arithmetic - Art of Assembly – Arithmetic Instructions
http://oopweb.com/Assembly/Documents/ArtOfAssembly/Volume/Chapter6/CH06–2.html - The GNU Assembler Tutorial
http://tigcc.ticalc.org/doc/gnuasm.html - The GNU Assembler – macros
http://tigcc.ticalc.org/doc/gnuasm.html#SEC109 - 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 - 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/ - 680×0:AsmOne
http://www.amigacoding.com/index.php/680×0: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/