Hlavní navigace

TinyCC Assembler: assembler, jenž je součástí Tiny C Compileru

26. 3. 2024
Doba čtení: 32 minut

Sdílet

 Autor: Depositphotos
Dnes navážeme na článek, v němž jsme si mimo jiné popsali překladač Tiny C Compiler. Tento překladač totiž obsahuje i vlastní assembler nazvaný TinyCC Assembler, který se podobná známému GNU Assembleru.

Obsah

1. TinyCC Assembler: assembler, jenž je součástí Tiny C Compileru

2. Assembler (jazyk symbolických adres)

3. Použití assembleru v praxi

4. Assemblery v Linuxu

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

16. Závěr

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

18. Odkazy na Internetu

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
Poznámka: povšimněte si, že nyní koncovka ve jméně souboru obsahuje „.S“ a nikoli „.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:

  1. Výpis zprávy na terminál (tedy na STDOUT)
  2. 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
Poznámka: čísla syscallů jsou na platformě x86–64 odlišná od syscallů na platformě x86! Pro x86–64 je naleznete na stránce https://blog.rchapman.org/pos­ts/Linux_System_Call_Table_for_x86_64/.

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
Poznámka: tento výpočet je proveden při překladu (assemblingu).

Ú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)
Poznámka: za návěštím <message> samozřejmě následuje text zprávy, takže se nelekněte toho, že se i tuto sekvenci bajtů snaží objdump reprezentovat formou instrukcí.

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)
Poznámka: povšimněte si, že některé informace již nejsou plně dispozici (například návěští, která jsou běžně uložena v tabulkách symbolů).

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_'
Poznámka: povšimněte si, že ani jeden z assemblerů nijak neurčil, kterého konkrétního řádku ve zdrojovém kódu se chyba týká, takže programátor musí sám pátrat.

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));
Poznámka: v případě, že vám připadají tyto zápisy matoucí (například bychom asi očekávali specifikaci vstupů a výstupů na začátku a nikoli na konci), vězte, že se jedná o způsob zpopularizovaný GNU C, který Tiny C Compiler převzal.

Vše si otestujme na tomto příkladu se dvěma variantami funkce add:

Cloud 24 - tip 1

#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/pre­sentations/. V tabulce umístěné pod tímto odstavcem jsou uvedeny odkazy na tyto příklady:

# Příklad Popis příkladu Cesta
1 hello_world_x86_64_gas.s program typu „Hello, world!“ ve variantě pro GNU Assembler https://github.com/tisnik/ocaml-examples/tree/master/assem­bler/tcc/hello_world_x86_64_gas­.s
2 hello_world_x86_64_tcc.s program typu „Hello, world!“ ve variantě pro TinyCC Assembler https://github.com/tisnik/ocaml-examples/tree/master/assem­bler/tcc/hello_world_x86_64_tcc­.s
     
3 broken_gas1.s použití neplatného návěští (label) ve variantě pro GNU Assembler https://github.com/tisnik/ocaml-examples/tree/master/assem­bler/tcc/broken_gas1.s
4 broken_gas2.s použití neplatné instrukce ve variantě pro GNU Assembler https://github.com/tisnik/ocaml-examples/tree/master/assem­bler/tcc/broken_gas2.s
5 broken_tcc1.s použití neplatného návěští (label) ve variantě pro TinyCC Assembler https://github.com/tisnik/ocaml-examples/tree/master/assem­bler/tcc/broken_tcc1.s
6 broken_tcc2.s použití neplatné instrukce ve variantě pro TinyCC Assembler https://github.com/tisnik/ocaml-examples/tree/master/assem­bler/tcc/broken_tcc2.s
     
7 preprocessor.S využití možností poskytovaných preprocesorem programovacího jazyka C https://github.com/tisnik/ocaml-examples/tree/master/assem­bler/tcc/preprocessor.S
8 syscalls.s soubor vkládaný pomocí #include do preprocessor.S https://github.com/tisnik/ocaml-examples/tree/master/assem­bler/tcc/syscalls.s
     
9 inline_asm.c kombinace C a assembleru v jediném souboru https://github.com/tisnik/ocaml-examples/tree/master/assem­bler/tcc/inline_asm.c

18. Odkazy na Internetu

  1. Původní domovská stránka Tiny C Compileru
    https://bellard.org/tcc/
  2. Tiny C Compiler na Wikipedii
    https://en.wikipedia.org/wi­ki/Tiny_C_Compiler
  3. TinyCC Assembler
    https://bellard.org/tcc/tcc-doc.html#asm
  4. Repositář Tiny C Compileru
    https://repo.or.cz/w/tinycc.git
  5. Linux System Call Table for x86 64
    https://blog.rchapman.org/pos­ts/Linux_System_Call_Table_for_x86_64/
  6. A Practical Guide to GCC Inline Assembly
    https://blog.alex.balgavy.eu/a-practical-guide-to-gcc-inline-assembly/
  7. Bit banging
    https://en.wikipedia.org/wi­ki/Bit_banging
  8. Warnings Are Your Friend – A Code Quality Primer
    https://hackaday.com/2018/11/06/war­nings-are-your-friend-a-code-quality-primer/
  9. Defending Against Compiler-Based Backdoors
    https://blog.regehr.org/archives/1241
  10. Reflections on Trusting Trust
    https://www.win.tue.nl/~a­eb/linux/hh/thompson/trus­t.html
  11. Coding Machines (povídka)
    https://www.teamten.com/law­rence/writings/coding-machines/
  12. Stage0
    https://bootstrapping.mira­heze.org/wiki/Stage0
  13. Projekt stage0 na GitHubu
    https://github.com/oriansj/stage0
  14. Bootstraping wiki
    https://bootstrapping.mira­heze.org/wiki/Main_Page
  15. Bootstrapped 6502 Assembler
    https://github.com/robinluc­key/bootstrap-6502
  16. IBM Basic assembly language and successors (Wikipedia)
    https://en.wikipedia.org/wi­ki/IBM_Basic_assembly_lan­guage_and_successors
  17. X86 Assembly/Bootloaders
    https://en.wikibooks.org/wi­ki/X86_Assembly/Bootloaders
  18. run6502, lib6502 — 6502 microprocessor emulator
    http://piumarta.com/software/lib6502/
  19. Simple Computer Simulator Instruction-Set
    http://www.science.smith.e­du/dftwiki/index.php/Simple_Com­puter_Simulator_Instructi­on-Set
  20. Bootstrapping#Computing (Wikipedia)
    https://en.wikipedia.org/wi­ki/Bootstrapping#Computing
  21. Bootstrapping (compilers)
    https://en.wikipedia.org/wi­ki/Bootstrapping_%28compi­lers%29
  22. Bootstrapable Builds
    http://bootstrappable.org/
  23. What is a coder's worst nightmare?
    https://www.quora.com/What-is-a-coders-worst-nightmare/answer/Mick-Stute
  24. Linux Assembly
    http://asm.sourceforge.net/
  25. Tombstone diagram (Wikipedia)
    https://en.wikipedia.org/wi­ki/Tombstone_diagram
  26. History of compiler construction (Wikipedia)
    https://en.wikipedia.org/wi­ki/History_of_compiler_con­struction
  27. Self-hosting (Wikipedia)
    https://en.wikipedia.org/wiki/Self-hosting
  28. GNU Mes: Maxwell Equations of Software
    https://gitlab.com/janneke/mes
  29. Tiny C Compiler
    https://bellard.org/tcc/
  30. Welcome to C–
    https://www.cs.tufts.edu/~nr/c--/index.html
  31. c4 – C in four functions
    https://github.com/rswier/c4
  32. Tiny BASIC (Wikipedia)
    https://en.wikipedia.org/wi­ki/Tiny_BASIC
  33. ARM GCC Inline Assembler Cookbook
    http://www.ethernut.de/en/do­cuments/arm-inline-asm.html
  34. Extended Asm – Assembler Instructions with C Expression Operands
    https://gcc.gnu.org/online­docs/gcc/Extended-Asm.html
  35. ARM inline asm secrets
    http://hardwarebug.org/2010/07/06/arm-inline-asm-secrets/
  36. How to Use Inline Assembly Language in C Code
    https://gcc.gnu.org/online­docs/gcc/Using-Assembly-Language-with-C.html#Using-Assembly-Language-with-C
  37. GCC-Inline-Assembly-HOWTO
    http://ibiblio.org/gferg/ldp/GCC-Inline-Assembly-HOWTO.html
  38. A Brief Tutorial on GCC inline asm (x86 biased)
    http://www.osdever.net/tu­torials/view/a-brief-tutorial-on-gcc-inline-asm
  39. GCC Inline ASM
    http://locklessinc.com/ar­ticles/gcc_asm/
  40. System cally pro AArch64 na Linuxu
    https://github.com/torval­ds/linux/blob/master/inclu­de/uapi/asm-generic/unistd.h
  41. C Functions Without Arguments
    https://eklitzke.org/c-functions-without-arguments
  42. GNU Assembler Examples
    http://cs.lmu.edu/~ray/no­tes/gasexamples/
  43. X86 Assembly/Arithmetic
    https://en.wikibooks.org/wi­ki/X86_Assembly/Arithmetic
  44. Art of Assembly – Arithmetic Instructions
    http://oopweb.com/Assembly/Do­cuments/ArtOfAssembly/Volu­me/Chapter6/CH06–2.html
  45. The GNU Assembler Tutorial
    http://tigcc.ticalc.org/doc/gnu­asm.html
  46. The GNU Assembler – macros
    http://tigcc.ticalc.org/doc/gnu­asm.html#SEC109
  47. Generating Mixed Source and Assembly List using GCC
    http://www.systutorials.com/240/ge­nerate-a-mixed-source-and-assembly-listing-using-gcc/
  48. Calling subroutines
    http://infocenter.arm.com/hel­p/index.jsp?topic=/com.ar­m.doc.kui0100a/armasm_cih­cfigg.htm
  49. ARM Assembly Language Programming
    http://peter-cockerell.net/aalp/html/frames.html
  50. ASM Flags
    http://www.cavestory.org/gu­ides/csasm/guide/asm_flag­s.html
  51. Status Register
    https://en.wikipedia.org/wi­ki/Status_register
  52. Linux assemblers: A comparison of GAS and NASM
    http://www.ibm.com/develo­perworks/library/l-gas-nasm/index.html
  53. Programovani v assembleru na OS Linux
    http://www.cs.vsb.cz/gryga­rek/asm/asmlinux.html
  54. Is it worthwhile to learn x86 assembly language today?
    https://www.quora.com/Is-it-worthwhile-to-learn-x86-assembly-language-today?share=1
  55. Why Learn Assembly Language?
    http://www.codeproject.com/Ar­ticles/89460/Why-Learn-Assembly-Language
  56. Is Assembly still relevant?
    http://programmers.stackex­change.com/questions/95836/is-assembly-still-relevant
  57. Why Learning Assembly Language Is Still a Good Idea
    http://www.onlamp.com/pub/a/on­lamp/2004/05/06/writegreat­code.html
  58. Assembly language today
    http://beust.com/weblog/2004/06/23/as­sembly-language-today/
  59. Assembler: Význam assembleru dnes
    http://www.builder.cz/rubri­ky/assembler/vyznam-assembleru-dnes-155960cz
  60. Assembler pod Linuxem
    http://phoenix.inf.upol.cz/li­nux/prog/asm.html
  61. AT&T Syntax versus Intel Syntax
    https://www.sourceware.or­g/binutils/docs-2.12/as.info/i386-Syntax.html
  62. Linux Assembly website
    http://asm.sourceforge.net/
  63. Using Assembly Language in Linux
    http://asm.sourceforge.net/ar­ticles/linasm.html
  64. vasm
    http://sun.hasenbraten.de/vasm/
  65. vasm – dokumentace
    http://sun.hasenbraten.de/vas­m/release/vasm.html
  66. The Yasm Modular Assembler Project
    http://yasm.tortall.net/
  67. 680×0:AsmOne
    http://www.amigacoding.com/in­dex.php/680×0:AsmOne
  68. ASM-One Macro Assembler
    http://en.wikipedia.org/wiki/ASM-One_Macro_Assembler
  69. ASM-One pages
    http://www.theflamearrows­.info/documents/asmone.html
  70. Základní informace o ASM-One
    http://www.theflamearrows­.info/documents/asminfo.html
  71. Linux Syscall Reference
    http://syscalls.kernelgrok.com/
  72. Programming from the Ground Up Book – Summary
    http://savannah.nongnu.or­g/projects/pgubook/

Byl pro vás článek přínosný?

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.