Assembler v Linuxu se v současnosti používá převážně v těch situacích, kdy je zapotřebí efektivně provést pouze určité specifické paměťově či výpočetně náročné operace. Zbytek aplikace se přitom vytváří v některém vyšším programovacím jazyku, například v C či C++. Z tohoto důvodu je užitečné vědět, jak je možné assembler (resp. kód psaný v assembleru) kombinovat se zdrojovým kódem psaným v C či C++. Právě této problematice je věnován dnešní článek.

Obsah

1. Použití assembleru v Linuxu: assembler a jazyk C

2. Příkazy asm a __asm__

3. Jak se příkazy asm a __asm__ zpracovávají?

4. Blok vytvořený v assembleru bez vstupních a výstupních operandů

5. Specifikace výstupních operandů

6. Přesné určení registrů pro výstupní operandy

7. Specifikace vstupních operandů

8. Explicitní určení registru pro výstupní operand

9. Explicitní určení registrů pro vstupní operandy i výstupní operand

10. Stejný registr použitý pro vstupní i výstupní operand

11. Vliv optimalizací na generovaný kód

12. Použití syntaxe používané firmou Intel

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

14. Odkazy na Internetu

1. Použití assembleru v Linuxu: assembler a jazyk C

Pokud je nutné v nějaké nativní aplikaci, tj. v aplikaci překládané do strojového kódu, určitou kritickou část naprogramovat v assembleru, mají vývojáři k dispozici hned několik možností:

  • Ta část, která je psaná v assembleru, může být přeložena ze samostatných zdrojových kódů libovolným assemblerem (GNU Assembler, NASM atd.) a následně staticky slinkována se zbytkem aplikace, což jsme si již v tomto seriálu ukázali. Připomeňme si, že kód v assembleru lze v případě potřeby přímo překládat zavoláním gcc, nikoli as, což nám mj. umožní implicitně volat linker (pokud je to zapotřebí). V assembleru jsme v tomto případě omezeni na tvorbu subrutin, které se volají z céčkového kódu, tj. nelze například naprogramovat jen vnitřní smyčku v assembleru.
  • Část psanou v assembleru lze přeložit běžným způsobem (opět s využitím GNU Assembleru, NASMu atd.) a následně objektový kód uložit do dynamické knihovny (so). Tato knihovna se bude k aplikaci linkovat až po spuštění programu. Platí pro nás prakticky stejná omezení, jaká byla zmíněna v předchozím bodu.
  • V jediném zdrojovém kódu lze kombinovat céčko (či C++) a assembler. Tato možnost přináší některé výhody, například zápis instrukcí v assembleru přímo do funkce (optimalizace vnitřních smyček), ovšem i některé nevýhody, protože zdrojový kód se stane nepřenositelným na další platformy (s využitím podmíněného překladu je však možné pro další platformy připravit alternativní kód). I přesto si tento způsob dnes vysvětlíme; taktéž si ukážeme, jak se do části psané v assembleru předávají parametry.

Poznámka: všechny demonstrační příklady byly vyzkoušeny s překladačem GCC a nemusí být plně přenositelné na další typy překladačů:

gcc --version
gcc (GCC) 6.3.1 20161221 (Red Hat 6.3.1-1)
Copyright (C) 2016 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

2. Příkazy asm a __asm__

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 GNU C se používá druhý způsob. 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 s volbami -ansi a/nebo -std. Z tohoto důvodu se doporučuje namísto asm používat __asm__ a pro překlad zdrojového kódu na jiných překladačích navíc do pro jistotu hlavičky či na úvod doplnit:

#ifndef __GNUC__
#define __asm__ asm
#endif

Bližší informace o této problematice je možné v případě potřeby najít například na stránce Alternate Keywords.

Poznámka: v demonstračních příkladech navíc používám i deklaraci __volatile__ zabezpečující, že se blok v assembleru nebude žádným způsobem optimalizovat (a tím pádem ani odstraňovat). To je důležité, protože se vyhnete nemilým překvapením při překladu s volbami -Ox apod.

3. Jak se příkazy asm a __asm__ zpracovávají?

Instrukce zapsané v blocích asm či __asm__ se překladačem céčka 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 (tuto problematiku si blíže vysvětlíme v dalším textu, takže se následujícího kódu moc nelekněte). Jednotlivé seznamy se od sebe oddělují dvojtečkou. Celý zápis může vypadat následovně:

#include <stdio.h>

int main()
{
    __asm__ __volatile__(
        "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;
}

Podívejme se, jakým způsobem se tento blok zpracuje překladačem céčka. Překladač musíme zavolat s volbou -S, aby se ze zdrojového kódu vygeneroval mezivýsledek předávaný interně do GNU assembleru as. Tento mezivýsledek obsahuje přeložený céčkový kód, případné ladicí informace a taktéž expandované bloky asm:

gcc -S asm_in_c_0.c

Mezivýsledek vypadá zhruba následovně. Povšimněte si zvýrazněných řádků, z nichž je patrné, že se náš „program“ skutečně pouze vložil na správné místo, a to včetně všech mezer a komentářů:

        .file   "asm_in_c_0.c"
        .text
        .globl  main
        .type   main, @function
main:
.LFB0:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
#APP
# 5 "asm_in_c_0.c" 1
        nop
         nop
          nop
            nop
            nop  # komentar 

# 0 "" 2
#NO_APP
        movl    $0, %eax
        popq    %rbp
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
.LFE0:
        .size   main, .-main
        .ident  "GCC: (GNU) 6.3.1 20161221 (Red Hat 6.3.1-1)"
        .section        .note.GNU-stack,"",@progbits

Poznámka: pozor na to, že komentáře jsou na jiných platformách zapisovány odlišnými znaky!

4. Blok vytvořený v assembleru bez vstupních a výstupních operandů

V případě, že je v assemblerovském bloku potřeba vykonat nějaké instrukce, a to nezávisle na okolním céčkovém kódu, není nutné specifikovat žádné vstupní registry ani výstupní operandy. Jestliže se navíc v takovém bloku nemění obsah jiných registrů (samozřejmě kromě PC neboli čítače instrukcí), je i poslední seznam registrů prázdný. Celý blok lze zapsat dvěma způsoby. Rozsáhlejším s explicitním uvedením prázdných seznamů (k vidění je méně často):

#include <stdio.h>

int main()
{
    __asm__ __volatile__(
        "nop   \n\t"
        : /* zadne vystupni registry */
        : /* zadne vstupni operandy */
        : /* zadne registry pouzivane uvnitr kodu */
    );

    return 0;
}

Nebo jednodušším a taktéž mnohem kratším způsobem, kde zcela chybí tři seznamy oddělené dvojtečkami. Celý assemblerovský blok je tedy tvořen jediným (obecně víceřádkovým) řetězcem:

#include <stdio.h>

int main()
{
    __asm__ __volatile__(
        "nop   \n\t"
    );

    return 0;
}

5. Specifikace výstupních operandů

Nyní se podívejme na poněkud složitější příklad, v němž bude vytvořen blok v assembleru, jehož úkolem bude zapsat konstantu 42 do globální proměnné nazvané result. Postup je jednoduchý:

  1. Načteme konstantu 42 do pracovního registru RBX (připomeňme si, že se nacházíme na platformě x86-64).
  2. Uložíme obsah registru RBX do proměnné result.

Při zápisu tohoto bloku musíme vyřešit dva problémy. První problém spočívá ve specifikaci proměnné result, což lze řešit zápisem „=“ (result) v seznamu výstupních operandů. Tento zápis znamená: ulož obsah automaticky vybraného pracovního registru (například RAX) do proměnné result. Interně je první výstupní operand v assembleru reprezentován znaky %0, případný druhý operand znaky %1 atd. Druhý problém spočívá v tom, že přepisujeme obsah registru RBX, což překladač céčka neví. Musíme ho o tom informovat, aby překladač věděl, že nesmí počítat s tím, že bude obsah tohoto registru nezměněn. To se provede jednoduše – v posledním (třetím) seznamu se uvede jméno tohoto registru popř. většího množství registrů:

#include <stdio.h>

unsigned long long result;

int main()
{
    __asm__ __volatile__(
        "mov    $42, %%rbx;   \n\t"
        "mov    %%rbx, %0;    \n\t"
        : "=r" (result)  /* vystupni operand */
        :                /* zadne vstupni operandy */
        : "%rbx"         /* registry pouzivane uvnitr kodu */
    );

    printf("%Ld\n", result);

    return 0;
}

Ve skutečnosti se náš assemblerovský blok transformuje do třech strojových instrukcí, protože je nutné uložit obsah automaticky vybraného pracovního registru do proměnné result. Konkrétně může vypadat výsledek transformace takto (poslední instrukce ukládá obsah registru RAX do globální proměnné):

#APP
# 7 "asm_in_c_3.c" 1
        mov    $42, %rbx;   
        mov    %rbx, %rax;    

# 0 "" 2
#NO_APP
        movq    %rax, result(%rip)

Poznámka: pokud vám nevyhovuje používání %0 pro výstupní operand, lze provést jeho pojmenování následujícím způsobem:

    __asm__ (
            "mov $42, %%rbx  \n\t"
            "mov %%rbx, %[result_ident]  \n\t"
            : [result_ident] "=r" (result)
            :
            : "%rbx");
    printf("%Ld\n", result);
    return 0;

6. Přesné určení registrů pro výstupní operandy

V předchozím příkladu jsme nechali na céčkovém překladači, ať si sám zvolí registr používaný pro výstupní operand. Samozřejmě je však možné registr zvolit explicitně, a to náhradou znaku „r“ v řetězci „=r“ za jiný znak podle následující tabulky. Povšimněte si, že (alespoň prozatím) není možné explicitně použít vyšších osm pracovních registrů, tj. registry pojmenované R8R15. V samotném assemblerovském kódu však tyto registry lze využít:

Náhrada „r“ Význam
a %rax, %eax, %ax, %al
b %rbx, %ebx, %bx, %bl
c %rcx, %ecx, %cx, %cl
d %rdx, %edx, %dx, %dl
S %rsi, %esi, %si
D %rdi, %edi, %di

Podívejme se nyní na to, co se stane, když pro výstupní operand explicitně zvolíme registr RCX:

#include <stdio.h>

unsigned long long result;

int main()
{
    __asm__ __volatile__(
        "mov    $42, %%rbx;   \n\t"
        "mov    %%rbx, %0;    \n\t"
        : "=c" (result)  /* vystupni operand */
        :                /* zadne vstupni operandy */
        : "%rbx"         /* registry pouzivane uvnitr kodu */
    );

    printf("%Ld\n", result);

    return 0;
}

Tento kód se přeloží následujícím způsobem:

#APP
# 7 "test.c" 1
        mov    $42, %rbx;   
        mov    %rbx, %rcx;    
        
# 0 "" 2
#NO_APP
        movq    %rcx, %rax
        movq    %rax, result(%rip)

Nenechte se zmást použitím RAX, po optimalizaci (-O) tento mezikrok zmizí.

Alternativně můžeme zcela vynechat meziuložení výsledku do registru RBX a prohlásit tento registr za registr obsahující výstupní operand:

#include <stdio.h>

unsigned long long result;

int main()
{
    __asm__ __volatile__(
        "mov    $42, %%rbx;   \n\t"
        : "=b" (result)  /* vystupni operand */
        :                /* zadne vstupni operandy */
        :                /* registry pouzivane uvnitr kodu */
    );

    printf("%Ld\n", result);

    return 0;
}

Překlad bude vypadat takto:

#APP
# 7 "test.c" 1
        mov    $42, %rbx;   
        
# 0 "" 2
#NO_APP
        movq    %rbx, %rax
        movq    %rax, result(%rip)

7. Specifikace vstupních operandů

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, ovšem kromě výstupního operandu musíme specifikovat 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:

#include <stdio.h>

unsigned long long x = 10;
unsigned long long y = 20;
unsigned long long result;

int main()
{
    __asm__ __volatile__(
        "mov    %1, %%rbx;   \n\t"
        "add    %2, %%rbx;   \n\t"
        "mov    %%rbx, %%rcx \n\t"
        : "=c" (result)    /* vystupni operand */
        : "r" (x), "r" (y) /* dva vstupni operandy */
        : "%rbx"           /* registry pouzivane uvnitr kodu */
    );

    printf("%Ld\n", result);

    return 0;
}

Výsledek transformace provedené překladačem céčka vypadá takto:

        movq    x(%rip), %rax
        movq    y(%rip), %rdx
#APP
# 9 "test.c" 1
        mov    %rax, %rbx;   
        add    %rdx, %rbx;   
        mov    %rbx, %rcx 
        
# 0 "" 2
#NO_APP
        movq    %rcx, %rax
        movq    %rax, result(%rip)

Na začátku vidíme automaticky vygenerované instrukce pro umístění vstupních operandů do vybraných registrů (vybral si je sám překladač), dále pak vlastní výpočet a konečně uložení výsledku do proměnné result.

8. Explicitní určení registru pro výstupní operand

Opět můžeme trošku experimentovat a explicitně určit, že výstupní operand je uložen v registru RAX. Tím si ušetříme jednu instrukci MOV:

#include <stdio.h>

unsigned long long x = 10;
unsigned long long y = 20;
unsigned long long result;

int main()
{
    __asm__ __volatile__(
        "mov    %1, %%rax;   \n\t"
        "add    %2, %%rax;   \n\t"
        : "=a" (result)    /* vystupni operand */
        : "r" (x), "r" (y) /* dva vstupni operandy */
        :                  /* registry pouzivane uvnitr kodu */
    );

    printf("%Ld\n", result);

    return 0;
}

Výsledek transformace provedené překladačem céčka (bez zapnutých optimalizací) vypadá následovně:

        movq    x(%rip), %rax
        movq    y(%rip), %rdx
#APP
# 9 "test.c" 1
        mov    %rax, %rbx;   
        add    %rdx, %rbx;   
        mov    %rbx, %rcx 
        
# 0 "" 2
#NO_APP
        movq    %rax, result(%rip)
        movq    result(%rip), %rax

9. Explicitní určení registrů pro vstupní operandy i výstupní operand

Pokračujme v našich úpravách dále. Nyní budeme požadovat, aby výstupní operand (součet) byl umístěn v registru RAX, první vstupní operand v registru RBX a druhý vstupní operand v registru RCX. Zápis bude vypadat následovně (připomeňme si tabulku ze šesté kapitoly):

#include <stdio.h>

unsigned long long x = 10;
unsigned long long y = 20;
unsigned long long result;

int main()
{
    __asm__ __volatile__(
        "mov    %%rbx, %%rax;   \n\t"
        "add    %%rcx, %%rax;   \n\t"
        : "=a" (result)    /* vystupni operand */
        : "b" (x), "c" (y) /* dva vstupni operandy v registrech rbx a rcx */
        :                  /* registry pouzivane uvnitr kodu */
    );

    printf("%Ld\n", result);

    return 0;
}

Výsledek transformace provedené překladačem céčka (bez zapnutých optimalizací) vypadá následovně:

        movq    x(%rip), %rax
        movq    y(%rip), %rdx
        movq    %rax, %rbx
        movq    %rdx, %rcx
#APP
# 9 "test.c" 1
        mov    %rbx, %rbx;   
        add    %rcx, %rbx;   
        mov    %rbx, %rcx 
        
# 0 "" 2
#NO_APP
        movq    %rax, result(%rip)
        movq    result(%rip), %rax

10. Stejný registr použitý pro vstupní i výstupní operand

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:

#include <stdio.h>

unsigned long long x = 10;
unsigned long long y = 20;
unsigned long long result;

int main()
{
    __asm__ __volatile__(
        "add    %%rbx, %%rax;   \n\t"
        : "=a" (result)    /* vystupni operand */
        : "a" (x), "b" (y) /* dva vstupni operandy v registrech rbx a rcx */
        :                  /* registry pouzivane uvnitr kodu */
    );

    printf("%Ld\n", result);

    return 0;
}

11. Vliv optimalizací na generovaný kód

Velký vliv na výslednou podobu kódu mají optimalizace prováděné céčkovým překladačem. Ten sice nezasahuje do našeho kódu psaného v assembleru (což je samozřejmě dobře), ovšem přípravu operandů a uložení výsledků již může být optimalizováno. Předchozí příklad přeložený bez optimalizací vypadá takto:

        movq    x(%rip), %rax
        movq    y(%rip), %rdx
        movq    %rdx, %rbx
#APP
# 9 "asm_in_c_9.c" 1
        add    %rbx, %rax;   

# 0 "" 2
#NO_APP
        movq    %rax, result(%rip)
        movq    result(%rip), %rax

Výsledek překladu s volbou -O:

        movq    x(%rip), %rax
        movq    y(%rip), %rbx
#APP
# 9 "asm_in_c_9.c" 1
        add    %rbx, %rax;   

# 0 "" 2
#NO_APP
        movq    %rax, result(%rip)

Tento kód je již přímočarý a přesně odpovídá tomu, co jsme požadovali: vstupní operandy ulož do registrů RAX a RBX, proveď součet těchto registrů a následně ulož RAX do proměnné result.

12. Použití syntaxe používané firmou Intel

Poslední zajímavostí, o které se dnes zmíníme, je použití syntaxe firmy Intel. Tato syntaxe je podle mého názoru čitelnější, protože zmizí nepěkná procenta atd., navíc se prohodí operandy všech instrukcí. Tuto syntaxi můžeme použít:

#include <stdio.h>

unsigned long long x = 10;
unsigned long long y = 20;
unsigned long long result;

int main()
{
    __asm__ __volatile__(
        "add    rax, rbx;   \n\t"
        : "=a" (result)    /* vystupni operand */
        : "a" (x), "b" (y) /* dva vstupni operandy v registrech rbx a rcx */
        :                  /* registry pouzivane uvnitr kodu */
    );

    printf("%Ld\n", result);

    return 0;
}

Nesmíme však zapomenout při překladu použít volbu -masm=intel. Podoba výsledného transformovaného kódu se v tomto případě radikálně pozmění:

        mov     rax, QWORD PTR x[rip]
        mov     rdx, QWORD PTR y[rip]
        mov     rbx, rdx
#APP
# 9 "test.c" 1
        add    rax, rbx;   
        
# 0 "" 2
#NO_APP
        mov     QWORD PTR result[rip], rax

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

Všechny demonstrační příklady byly, podobně jako v předchozích částech tohoto seriálu, společně s podpůrným skriptem připraveným pro jejich překlad, uloženy do GIT repositáře dostupného na adrese https://github.com/tisnik/presentations/. Všechny zmíněné příklady jsou určeny pro překladač GNU C:

# Soubor Odkaz do repositáře
1 asm_in_c_0.c https://github.com/tisnik/presentations/blob/master/assembler/asm_in_c/asm_in_c_0.c
2 asm_in_c_1.c https://github.com/tisnik/presentations/blob/master/assembler/asm_in_c/asm_in_c_1.c
3 asm_in_c_2.c https://github.com/tisnik/presentations/blob/master/assembler/asm_in_c/asm_in_c_2.c
4 asm_in_c_3.c https://github.com/tisnik/presentations/blob/master/assembler/asm_in_c/asm_in_c_3.c
5 asm_in_c_4.c https://github.com/tisnik/presentations/blob/master/assembler/asm_in_c/asm_in_c_4.c
6 asm_in_c_5.c https://github.com/tisnik/presentations/blob/master/assembler/asm_in_c/asm_in_c_5.c
7 asm_in_c_6.c https://github.com/tisnik/presentations/blob/master/assembler/asm_in_c/asm_in_c_6.c
8 asm_in_c_7.c https://github.com/tisnik/presentations/blob/master/assembler/asm_in_c/asm_in_c_7.c
9 asm_in_c_8.c https://github.com/tisnik/presentations/blob/master/assembler/asm_in_c/asm_in_c_8.c
10 asm_in_c_9.c https://github.com/tisnik/presentations/blob/master/assembler/asm_in_c/asm_in_c_9.c
11 asm_in_c_10.c https://github.com/tisnik/presentations/blob/master/assembler/asm_in_c/asm_in_c_10.c
12 Makefile https://github.com/tisnik/presentations/blob/master/assembler/asm_in_c/Makefile

14. Odkazy na Internetu

  1. 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
  2. GCC-Inline-Assembly-HOWTO
    http://ibiblio.org/gferg/ldp/GCC-Inline-Assembly-HOWTO.html
  3. A Brief Tutorial on GCC inline asm (x86 biased)
    http://www.osdever.net/tutorials/view/a-brief-tutorial-on-gcc-inline-asm
  4. GCC Inline ASM
    http://locklessinc.com/articles/gcc_asm/
  5. Cortex-A35
    https://www.arm.com/products/processors/cortex-a/cortex-a35-processor.php
  6. Cortex-A53
    https://www.arm.com/products/processors/cortex-a/cortex-a53-processor.php
  7. Cortex-A57
    https://www.arm.com/products/processors/cortex-a/cortex-a57-processor.php
  8. Cortex-A72
    https://www.arm.com/products/processors/cortex-a/cortex-a72-processor.php
  9. Cortex-A73
    https://www.arm.com/products/processors/cortex-a/cortex-a73-processor.php
  10. System cally pro AArch64 na Linuxu
    https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/unistd.h
  11. Architectures/AArch64 (FedoraProject.org)
    https://fedoraproject.org/wiki/Architectures/AArch64
  12. SIG pro AArch64 (CentOS)
    https://wiki.centos.org/SpecialInterestGroup/AltArch/AArch64
  13. The ARMv8 instruction sets
    http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.den0024a/ch05s01.html
  14. A64 Instruction Set
    https://developer.arm.com/products/architecture/instruction-sets/a64-instruction-set
  15. Switching between the instruction sets
    http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.den0024a/ch05s01.html
  16. The A64 instruction set
    http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.den0024a/ch05s01.html
  17. Introduction to ARMv8 64-bit Architecture
    https://quequero.org/2014/04/introduction-to-arm-architecture/
  18. MCU market turns to 32-bits and ARM
    http://www.eetimes.com/document.asp?doc_id=1280803
  19. Cortex-M0 Processor (ARM Holdings)
    http://www.arm.com/products/processors/cortex-m/cortex-m0.php
  20. Cortex-M0+ Processor (ARM Holdings)
    http://www.arm.com/products/processors/cortex-m/cortex-m0plus.php
  21. ARM Processors in a Mixed Signal World
    http://www.eeweb.com/blog/arm/arm-processors-in-a-mixed-signal-world
  22. ARM Architecture (Wikipedia)
    https://en.wikipedia.org/wiki/ARM_architecture
  23. ARM Documentation: B, BL, BX, BLX, and BXJ
    http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0204j/Cihfddaf.html
  24. Branch and Call Sequences Explained
    https://community.arm.com/groups/processors/blog/2013/09/25/branch-and-call-sequences-explained
  25. Improving ARM Code Density and Performance
    New Thumb Extensions to the ARM Architecture Richard Phelan
  26. Aarch64 Register and Instruction Quick Start
    https://wiki.cdot.senecacollege.ca/wiki/Aarch64_Register_and_Instruction_Quick_Start
  27. Exploring AArch64 assembler – Chapter 1
    http://thinkingeek.com/2016/10/08/exploring-aarch64-assembler-chapter1/
  28. Exploring AArch64 assembler – Chapter 2
    http://thinkingeek.com/2016/10/08/exploring-aarch64-assembler-chapter-2/
  29. The ARM Processor Architecture
    http://www.arm.com/products/processors/technologies/instruction-set-architectures.php
  30. Thumb-2 instruction set
    http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0344c/Beiiegaf.html
  31. Introduction to ARM thumb
    http://www.eetimes.com/discussion/other/4024632/Introduction-to-ARM-thumb
  32. ARM, Thumb, and ThumbEE instruction sets
    http://www.keil.com/support/man/docs/armasm/armasm_CEGBEIJB.htm
  33. An Introduction to ARM Assembly Language
    http://dev.emcelettronica.com/introduction-to-arm-assembly-language
  34. Processors – ARM
    http://www.arm.com/products/processors/index.php
  35. The ARM Instruction Set
    http://simplemachines.it/doc/arm_inst.pdf
  36. ARM Architecture (Wikipedia)
    http://en.wikipedia.org/wiki/ARM_architecture
  37. C Functions Without Arguments
    https://eklitzke.org/c-functions-without-arguments
  38. GNU Assembler Examples
    http://cs.lmu.edu/~ray/notes/gasexamples/
  39. Simply FPU
    http://www.website.masmforum.com/tutorials/fptute/
  40. Art of Assembly language programming: The 80×87 Floating Point Coprocessors
    https://courses.engr.illinois.edu/ece390/books/artofasm/CH14/CH14-3.html
  41. Art of Assembly language programming: The FPU Instruction Set
    https://courses.engr.illinois.edu/ece390/books/artofasm/CH14/CH14-4.html
  42. INTEL 80387 PROGRAMMER’S REFERENCE MANUAL
    http://www.ragestorm.net/downloads/387intel.txt
  43. x86 Instruction Set Reference: FLD
    http://x86.renejeschke.de/html/file_module_x86_id_100.html
  44. x86 Instruction Set Reference: FLD1/FLDL2T/FLDL2E/FLDPI/FLDLG2/FLDLN2/FLDZ
    http://x86.renejeschke.de/html/file_module_x86_id_101.html
  45. x86 Instruction Set Reference: FLD
    http://x86.renejeschke.de/html/file_module_x86_id_100.html
  46. x86 Instruction Set Reference: FST/FSTP
    http://x86.renejeschke.de/html/file_module_x86_id_117.html
  47. x86 Instruction Set Reference: BTC
    http://x86.renejeschke.de/html/file_module_x86_id_23.html
  48. x86 Instruction Set Reference: BTR
    http://x86.renejeschke.de/html/file_module_x86_id_24.html
  49. x86 Instruction Set Reference: BTS
    http://x86.renejeschke.de/html/file_module_x86_id_25.html
  50. x86 Instruction Set Reference: BSF
    http://x86.renejeschke.de/html/file_module_x86_id_19.html
  51. x86 Instruction Set Reference: BSR
    http://x86.renejeschke.de/html/file_module_x86_id_20.html
  52. x86 Instruction Set Reference: BSWAP
    http://x86.renejeschke.de/html/file_module_x86_id_21.html
  53. x86 Instruction Set Reference: XCHG
    http://x86.renejeschke.de/html/file_module_x86_id_328.html
  54. x86 Instruction Set Reference: SETcc
    http://x86.renejeschke.de/html/file_module_x86_id_288.html
  55. X86 Assembly/Arithmetic
    https://en.wikibooks.org/wiki/X86_Assembly/Arithmetic
  56. Art of Assembly – Arithmetic Instructions
    http://oopweb.com/Assembly/Documents/ArtOfAssembly/Volume/Chapter_6/CH06-2.html
  57. The GNU Assembler Tutorial
    http://tigcc.ticalc.org/doc/gnuasm.html
  58. The GNU Assembler – macros
    http://tigcc.ticalc.org/doc/gnuasm.html#SEC109
  59. ARM subroutines & program stack
    http://www.toves.org/books/armsub/
  60. Generating Mixed Source and Assembly List using GCC
    http://www.systutorials.com/240/generate-a-mixed-source-and-assembly-listing-using-gcc/
  61. Calling subroutines
    http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.kui0100a/armasm_cihcfigg.htm
  62. ARM Assembly Language Programming
    http://peter-cockerell.net/aalp/html/frames.html
  63. ASM Flags
    http://www.cavestory.org/guides/csasm/guide/asm_flags.html
  64. Status Register
    https://en.wikipedia.org/wiki/Status_register
  65. Intel x86 JUMP quick reference
    http://unixwiz.net/techtips/x86-jumps.html
  66. Linux assemblers: A comparison of GAS and NASM
    http://www.ibm.com/developerworks/library/l-gas-nasm/index.html
  67. Programovani v assembleru na OS Linux
    http://www.cs.vsb.cz/grygarek/asm/asmlinux.html
  68. Is it worthwhile to learn x86 assembly language today?
    https://www.quora.com/Is-it-worthwhile-to-learn-x86-assembly-language-today?share=1
  69. Why Learn Assembly Language?
    http://www.codeproject.com/Articles/89460/Why-Learn-Assembly-Language
  70. Is Assembly still relevant?
    http://programmers.stackexchange.com/questions/95836/is-assembly-still-relevant
  71. Why Learning Assembly Language Is Still a Good Idea
    http://www.onlamp.com/pub/a/onlamp/2004/05/06/writegreatcode.html
  72. Assembly language today
    http://beust.com/weblog/2004/06/23/assembly-language-today/
  73. Assembler: Význam assembleru dnes
    http://www.builder.cz/rubriky/assembler/vyznam-assembleru-dnes-155960cz
  74. Assembler pod Linuxem
    http://phoenix.inf.upol.cz/linux/prog/asm.html
  75. AT&T Syntax versus Intel Syntax
    https://www.sourceware.org/binutils/docs-2.12/as.info/i386-Syntax.html
  76. Linux Assembly website
    http://asm.sourceforge.net/
  77. Using Assembly Language in Linux
    http://asm.sourceforge.net/articles/linasm.html
  78. vasm
    http://sun.hasenbraten.de/vasm/
  79. vasm – dokumentace
    http://sun.hasenbraten.de/vasm/release/vasm.html
  80. The Yasm Modular Assembler Project
    http://yasm.tortall.net/
  81. 680×0:AsmOne
    http://www.amigacoding.com/index.php/680×0:AsmOne
  82. ASM-One Macro Assembler
    http://en.wikipedia.org/wiki/ASM-One_Macro_Assembler
  83. ASM-One pages
    http://www.theflamearrows.info/documents/asmone.html
  84. Základní informace o ASM-One
    http://www.theflamearrows.info/documents/asminfo.html
  85. Linux Syscall Reference
    http://syscalls.kernelgrok.com/
  86. Programming from the Ground Up Book – Summary
    http://savannah.nongnu.org/projects/pgubook/
  87. IBM System 360/370 Compiler and Historical Documentation
    http://www.edelweb.fr/Simula/
  88. IBM 700/7000 series
    http://en.wikipedia.org/wiki/IBM_700/7000_series
  89. IBM System/360
    http://en.wikipedia.org/wiki/IBM_System/360
  90. IBM System/370
    http://en.wikipedia.org/wiki/IBM_System/370
  91. Mainframe family tree and chronology
    http://www-03.ibm.com/ibm/history/exhibits/mainframe/mainframe_FT1.html
  92. 704 Data Processing System
    http://www-03.ibm.com/ibm/history/exhibits/mainframe/mainframe_PP704.html
  93. 705 Data Processing System
    http://www-03.ibm.com/ibm/history/exhibits/mainframe/mainframe_PP705.html
  94. The IBM 704
    http://www.columbia.edu/acis/history/704.html
  95. IBM Mainframe album
    http://www-03.ibm.com/ibm/history/exhibits/mainframe/mainframe_album.html
  96. Osmibitové muzeum
    http://osmi.tarbik.com/
  97. Tesla PMI-80
    http://osmi.tarbik.com/cssr/pmi80.html
  98. PMI-80
    http://en.wikipedia.org/wiki/PMI-80
  99. PMI-80
    http://www.old-computers.com/museum/computer.asp?st=1&c=1016
  100. The 6502 overflow flag explained mathematically
    http://www.righto.com/2012/12/the-6502-overflow-flag-explained.html
  101. X86 Opcode and Instruction Reference
    http://ref.x86asm.net/coder32.html