Obsah
1. Specifické vlastnosti procesorů AArch64: úvod
3. Instalace základních vývojářských nástrojů
4. Sada pracovních registrů mikroprocesorů s architekturou AArch64
6. Nejjednodušší korektní program zapsaný v assembleru
7. Kódování instrukcí MOV a SVC u architektury AArch64
9. Program typu „Hello, world!“ v assembleru
10. Uložení adresy načítané instrukcí LDR
11. Zápis do datového segmentu
12. Základní aritmetické instrukce
13. Použití aritmetických instrukcí
14. Nepodmíněné skoky a skoky do subrutiny (branch with link)
16. Další varianty podmíněných skoků
18. Obsah následujícího článku
19. Předchozí články o architektuře AArch64
1. Specifické vlastnosti procesorů AArch64: úvod
Na předchozí (poněkud starší) pětici článků o architektuře AArch64 dnes navážeme. Ukážeme si, jakým způsobem se používají základní instrukce z instrukční sady této velmi zajímavé architektury. Prozatím si většinou vystačíme s assemblerem (a linkerem), protože pro studijní účely je vhodné používat co nejjednodušší (resp. nejvíce transparentní) nástroje. Ovšem od assembleru je jen relativně malý krok k programům, které jsou zapsány v jazyku C, což je pochopitelně téma, na které v navazujících článcích nezapomeneme.
Dnes se budeme zabývat jen těmi nejzákladnějšími instrukcemi, které ovšem postačují pro tvorbu jednodušších prográmků:
- Přenosy dat instrukcí MOV
- Načtení dat instrukcí LDR (v mnoha obměnách)
- Uložení dat instrukcí STR (taktéž v mnoha obměnách)
- Součet a rozdíl realizovaný instrukcemi ADD a SUB
- Porovnání dvou hodnot pseudoinstrukcí CMP
- Nepodmíněný skok B
- Podmíněný skok B.podmínka
- Volání služby jádra instrukcí SVC
2. Použitý počítač
Otestování všech dále popsaných demonstračních příkladů bylo provedeno na počítači osazeném poměrně výkonným mikroprocesorem s architekturou AArch64. Bližší informace o použitém procesoru lze zjistit přímo z příkazové řádky:
$ lscpu
S výsledkem:
Architecture: aarch64 CPU op-mode(s): 64-bit Byte Order: Little Endian CPU(s): 4 On-line CPU(s) list: 0-3 Thread(s) per core: 1 Core(s) per socket: 4 Socket(s): 1 NUMA node(s): 1 Vendor ID: Cavium Model: 1 Model name: ThunderX2 99xx Stepping: 0x1 BogoMIPS: 400.00 NUMA node0 CPU(s): 0-3 Vulnerability Itlb multihit: Not affected Vulnerability L1tf: Not affected Vulnerability Mds: Not affected Vulnerability Meltdown: Not affected Vulnerability Spec store bypass: Mitigation; Speculative Store Bypass disabled via prctl Vulnerability Spectre v1: Mitigation; __user pointer sanitization Vulnerability Spectre v2: Mitigation; Branch predictor hardening Vulnerability Srbds: Not affected Vulnerability Tsx async abort: Not affected Flags: fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics cpuid asimdrdm
Informace o dostupných jádrech:
# lscpu -a -e=socket,cpu,core,online SOCKET CPU CORE ONLINE 0 0 0 yes 0 1 1 yes 0 2 2 yes 0 3 3 yes
A podrobnější informace o jednotlivých jádrech tak, jak je vidí operační systém:
$ cat /proc/cpuinfo
processor : 0 BogoMIPS : 400.00 Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics cpuid asimdrdm CPU implementer : 0x43 CPU architecture: 8 CPU variant : 0x1 CPU part : 0x0af CPU revision : 1 processor : 1 BogoMIPS : 400.00 Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics cpuid asimdrdm CPU implementer : 0x43 CPU architecture: 8 CPU variant : 0x1 CPU part : 0x0af CPU revision : 1 processor : 2 BogoMIPS : 400.00 Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics cpuid asimdrdm CPU implementer : 0x43 CPU architecture: 8 CPU variant : 0x1 CPU part : 0x0af CPU revision : 1 processor : 3 BogoMIPS : 400.00 Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics cpuid asimdrdm CPU implementer : 0x43 CPU architecture: 8 CPU variant : 0x1 CPU part : 0x0af CPU revision : 1
3. Instalace základních vývojářských nástrojů
V navazujících kapitolách si ukážeme několik demonstračních příkladů naprogramovaných v assembleru, konkrétně s využitím syntaxe podporované GNU Assemblerem. Budeme tedy potřebovat nainstalovat všechny potřebné nástroje, což je minimálně překladač assembleru, disassembler a linker. Všechny tyto nástroje (a mnohé další) jsou nabízeny v balíčku binutils (který není – tedy s ohledem na dnešní poměry – nijak velký). Na Fedoře/RHELu se instalace tohoto balíčku provede standardním způsobem:
# dnf install binutils
Průběh instalace na Fedoře 33 Server:
Last metadata expiration check: 0:13:15 ago on Sat 19 Feb 2022 07:05:31 AM EST. Dependencies resolved. =============================================================================================================================================================== Package Architecture Version Repository Size =============================================================================================================================================================== Installing: binutils aarch64 2.35-18.fc33 updates 5.8 M Installing dependencies: binutils-gold aarch64 2.35-18.fc33 updates 937 k Transaction Summary =============================================================================================================================================================== Install 2 Packages Total download size: 6.7 M Installed size: 30 M Is this ok [y/N]: y
Seznam nástrojů, které se skutečně z tohoto balíčku nainstalovaly, můžeme získat například takto:
$ dnf repoquery -l binutils | grep "/usr/bin" | sort |uniq /usr/bin/addr2line /usr/bin/ar /usr/bin/as /usr/bin/c++filt /usr/bin/dwp /usr/bin/elfedit /usr/bin/gprof /usr/bin/ld /usr/bin/ld.bfd /usr/bin/nm /usr/bin/objcopy /usr/bin/objdump /usr/bin/ranlib /usr/bin/readelf /usr/bin/size /usr/bin/strings /usr/bin/strip
4. Sada pracovních registrů mikroprocesorů s architekturou AArch64
V navazujících kapitolách si ukážeme několik (prozatím) velmi jednoduchých programů vytvořených v assembleru GAS pro mikroprocesory s architekturou AArch64. Ovšem aby bylo zřejmé, jak jsou tyto programy strukturovány, je nutné znát alespoň minimální informace o pracovních registrech, speciálních registrech i o instrukční sadě AArch64. Začněme tedy zmínkou o pracovních registrech a speciálních registrech, protože mj. i v tomto ohledu se AArch64 odlišuje od původní 32bitové architektury mikroprocesorů ARM.
Původních patnáct pracovních registrů pojmenovaných R0 až R14, které známe z dnes již klasických 32bitových procesorů ARM, bylo rozšířeno na celkem 31 registrů, z nichž každý má nyní šířku 64 bitů. Z tohoto důvodu muselo dojít k úpravě pojmenování registrů způsobem, který je naznačený v následující tabulce a který ostatně uvidíme i v dále uvedených demonstračních příkladech:
Jméno | Význam |
---|---|
r0..r30 | použito například v dokumentaci, popisu ABI atd. |
x0..x30 | celý 64bitový registr použitý jako zdroj či cíl pro prováděné operace |
w0..w30 | spodních 32 bitů registru (horních 32 bitů výsledku je buď vynulováno nebo znaménkově rozšířeno) |
Všechny tyto pracovní registry přitom mají v instrukční sadě stejné postavení, na rozdíl od instrukční sady Thumb (přesněji dnes T32), v níž se pro některé instrukce může použít jen spodních osm registrů. Ostatně nemělo by se jednat o žádné překvapení, protože v „pravých“ RISCových procesorech je zvykem používat všechny pracovní registry shodným způsobem a nerozlišovat například akumulátory, indexové registry, ukazatele na bázovou adresu na zásobníku, čítače programových smyček atd.
Další sada pracovních registrů je používána při operacích s datovými typy single/float a double (tedy s operandy reprezentovanými v systému plovoucí řádové čárky), u SIMD operací a taktéž kryptografickým modulem, o němž se blíže zmíníme v samostatném článku:
Jméno | Význam |
---|---|
v0..v31 | 128bitové registry |
d0..d31 | spodních 64 bitů registrů v0..v31, použito pro hodnoty typu double |
s0..s31 | spodních 32 bitů registrů v0..v31, použito pro hodnoty typu single/float |
Pro SIMD operace, tj. operace pracující s vektory, se registry vn rozdělují takto:
Tvar (shape) | Celkem | Pojmenování v assembleru |
---|---|---|
8b×8 | 64b | Vn.8B |
8b×16 | 128b | Vn.16B |
16b×4 | 64b | Vn.4H |
16b×8 | 128b | Vn.8H |
32b×2 | 64b | Vn.2S |
32b×4 | 128b | Vn.4S |
64b×1 | 64b | Vn.1D |
64b×2 | 128b | Vn.2D |
Povšimněte si, že – na rozdíl od mnoha jiných mikroprocesorových architektur – nedochází k tomu, že by se například dva single registry mapovaly do jednoho double registru atd.
Mezi speciální registry patří především:
Jméno | Význam zkratky |
---|---|
SCTLR_ELn | System Control Register |
ACTLR_ELn | Auxiliary Control Register |
SCR_EL3 | Secure Configuration Register |
HCR_EL2 | Hypervisor Configuration Register |
MIDR_EL1 | Main ID Register |
MPIDR_EL1 | Multiprocessor Affinity Register |
Význam těchto registrů bude popsán v navazujících článcích.
V pořadí třicátý druhý pracovní registr, tj. registr se jménem x31 či w31, není ve skutečnosti běžným pracovním registrem, protože má dva speciální významy. V případě použití tohoto registru v aritmetických či logických instrukcích se při použití ve funkci vstupního operandu tento registr chová jako konstantní nula a při použití ve funkci operandu výstupního jako /dev/null (výsledek se zahodí a neovlivní skutečnou hodnotu uloženou do registru). Proto se v assembleru může pro pojmenování tohoto registru použít jméno xzr či wzr (extended zero register, working zero register). U instrukcí pracujících se zásobníkem se tento registr chová jako ukazatel na vrchol zásobníku a proto se pro něj v assembleru používá jméno rsp či jen SP (na velikosti písmen u jmen registrů samozřejmě nezáleží).
Některé registry mají i další funkce:
- Registr x30 se používá ve funkci LR (Link Register).
- Registr PC není přímo dostupný. Toto je jeden z největších viditelných rozdílů mezi ARM 32 a AArch64.
5. Instrukce MOV a SVC
V demonstračním příkladu, který bude ukázán v další kapitole, si vystačíme s pouhými dvěma typy instrukcí. Jedná se o instrukci nazvanou MOV a o instrukci pojmenovanou SVC. Instrukce MOV slouží k přesunu dat mezi pracovními registry i pro načtení konstanty do registru. Ve skutečnosti existuje větší množství variant instrukce MOV, přičemž nás bude konkrétně zajímat varianta, která dokáže do zvoleného pracovního registru uložit konstantu. V assembleru je registr, do něhož se konstanta ukládá, použit jako první operand, za nímž následuje vlastní konstanta zapisovaná s křížkem na začátku (to aby se odlišila konstanta od adresy):
# Linux kernel system call table sys_exit=93 mov x8, #sys_exit // cislo sycallu pro funkci "exit" mov x0, #0 // exit code = 0
Název instrukce SVC vznikl ze sousloví SuperVisor Call a (minimálně na Linuxu) slouží k volání služeb jádra. Tato instrukce má jeden operand určující typ volání. Opět se podívejme na způsob zápisu této funkce v assembleru::
svc 0 // volani Linuxoveho kernelu
6. Nejjednodušší korektní program zapsaný v assembleru
Podívejme se nyní, jak by mohl vypadat program (pravděpodobně dokonce nejjednodušší funkční program vůbec), který po svém spuštění pouze zavolá službu jádra nazvanou exit, která program ukončí a předá volajícímu (rodičovskému) procesu návratový kód. Celý program obsahuje pouze tři instrukce, přičemž první instrukce slouží pro naplnění registru obsahujícího číslo služby jádra (liší se podle použité architektury!), druhá instrukce naplní registr s návratovým kódem a třetí instrukce skutečně zavolá jádro, které vykoná zvolenou službu.
V GNU assembleru lze takový program zapsat následovně (jedná se o šablonu, takže obsahuje i nepotřebné bloky):
# asmsyntax=as # Sablona pro zdrojovy kod Linuxoveho programu naprogramovaneho # v assembleru GNU AS pro architekturu AArch64. # # Autor: Pavel Tisnovsky # Linux kernel system call table sys_exit=93 # List of syscalls for AArch64: # https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/unistd.h #----------------------------------------------------------------------------- .section .data #----------------------------------------------------------------------------- .section .bss #----------------------------------------------------------------------------- .section .text .global _start // tento symbol ma byt dostupny i z linkeru _start: mov x8, #sys_exit // cislo sycallu pro funkci "exit" mov x0, #0 // exit code = 0 svc 0 // volani Linuxoveho kernelu
Překlad tohoto demonstračního příkladu a jeho slinkování se provede standardním způsobem, tedy s použitím nástrojů as (GNU Assembler – GAS) a ld (GNU Linker), které byly nainstalovány v rámci úvodních kapitol:
$ as aarch64.s -o aarch64.o $ ld -s aarch64.o
Výsledkem bude soubor nazvaný a.out, který je možné spustit:
$ ./a.out
Program by měl okamžitě skončit, a to bez chyby nebo pádu.
Zastavme se na chvíli u významu obsahu registru x0. Při volání funkce jádra exit obsahuje tento registr hodnotu, kterou proces vrátí do volajícího procesu, typicky do shellu. Můžeme si to snadno otestovat nepatrnou úpravou našeho programu:
.section .text .global _start // tento symbol ma byt dostupny i z linkeru _start: mov x8, #sys_exit // cislo sycallu pro funkci "exit" mov x0, #42 svc 0 // volani Linuxoveho kernelu
Překlad a otestování nové varianty:
$ as aarch64.s -o aarch64.o $ ld -s aarch64.o $ ./a.out $ echo $? 42
Vidíme, že se skutečně vrátila hodnota 42.
# Linux kernel system call table sys_exit=1 #----------------------------------------------------------------------------- .section .data #----------------------------------------------------------------------------- .section .bss #----------------------------------------------------------------------------- .section .text .global _start # tento symbol ma byt dostupny i linkeru _start: movl $sys_exit,%eax # cislo sycallu pro funkci "exit" movl $0,%ebx # exit code = 0 int $0x80 # volani Linuxoveho kernelu
7. Kódování instrukcí MOV a SVC u architektury AArch64
U architektury AArch64 se používají instrukce o pevné délce čtyři bajty, což má mj. i dalekosáhlé praktické důsledky – načítání konstant, rozsah cílů skoků atd. (na druhou stranu se zjednodušil a urychlil dekodér instrukcí). Bude tedy zajímavé zjistit, jakým způsobem se vlastně přeložila naše trojice instrukcí. Pro tento účel použijeme nástroj objdump, jenž byl nainstalován v rámci úvodních kapitol v balíčku binutils:
$ objdump -f -d -t -h a.out
Takto volaný příkaz nejprve zobrazí základní informace o obsahu souboru, následně jednotlivé sekce (segmenty), dále tabulku symbolů (ta je v našem případě prázdná) a konečně i disassemblovaný kód nalezený v sekci/sektoru nazvaném .text (taktéž se používá označení code):
a.out: file format elf64-littleaarch64 architecture: aarch64, flags 0x00000102: EXEC_P, D_PAGED start address 0x0000000000400078 Sections: Idx Name Size VMA LMA File off Algn 0 .text 0000000c 0000000000400078 0000000000400078 00000078 2**2 CONTENTS, ALLOC, LOAD, READONLY, CODE SYMBOL TABLE: no symbols Disassembly of section .text: 0000000000400078 <.text>: 400078: d2800ba8 mov x8, #0x5d // #93 40007c: d2800000 mov x0, #0x0 // #0 400080: d4000001 svc #0x0
Vidíme, že počáteční (startovní) adresa je rovna 0×0000000000400078, což je posléze ve výpisu adres instrukcí zkracováno na 4000×x. Dále je ve druhém sloupci u každé instrukce uvedena sekvence bajtů se zakódovanou instrukcí i se zakódovanými operandy této instrukce. Z výpisu je patrné, že všechny instrukce mají skutečně šířku čtyři bajty.
Podívejme se nyní na způsob zakódování instrukcí mov registr, #konstanta. Tato instrukce je zakódována ve čtyřech bajtech, a to včetně šestnáctibitové konstanty a čísla pracovního registru. To mj. znamená, že instrukce:
mov x8, #0x5d
Je zakódována do této sekvence bajtů:
d2 80 0b a8
Binárně pak:
1 1 0 1 | 0 0 1 0 | 1 0 0 0 | 0 0 0 0 | 0 0 0 0 | 1 0 1 1 | 1 0 1 0 | 1 0 0 0
Instrukce MOV registr, #konstanta používá toto kódování:
| sf | 1 0 | 1 0 0 1 0 1 | hw | imm16 | Rd |
kde:
- sf = 0 pro 32bitovou operaci
- sf = 1 pro 64bitovou operaci
- hw = 00 (dva bity)
- imm16 = 16bitová konstanta
- Rd = číslo pracovního registru, do kterého se má konstanta uložit
Zkusme tedy dosadit hodnoty pro instrukci:
mov x8, #0x5d
- sf = 1
- imm16 = 0×5d (01011101)
- Rd = 8 (01000)
Binárně a hexadecimálně:
1 1 0 1 |0 0 1 0 | 1 0 0 0| 0 0 0 0 | 0 0 0 0 | 1 0 1 1 | 1 0 1 0 | 1 0 0 0 d 2 8 0 0 b a 8
Což plně odpovídá:
d2 80 0b a8
Instrukce SVC má tento formát:
| 1 1 0 1 0 1 0 0 0 0 0 | imm16 | 0 0 0 0 1 |
V našem konkrétním případě je 16bitová konstanta imm16 nastavena na nulu, takže:
| 1 1 0 1 | 0 1 0 0 | 0 0 0 0 | 0 0 0 0 | 0 0 0 0 | 0 0 0 0 | 0 0 0 0 | 0 0 0 1 | d 4 0 0 0 0 0 1
8. Instrukce typu Load-Store
Jak je u RISCové architektury obvyklé, nalezneme v instrukční sadě i několik instrukcí určených pro načítání dat z paměti do registrů a naopak pro ukládání obsahu registrů do paměti. Navíc některé instrukce umožňují práci nejenom s 32bitovými a 64bitovými slovy, ale i s menšími bloky dat – s bajty a šestnáctibitovými „půlslovy“ (což je důležité například při zpracování řetězců, obrázků, zvukových samplů atd.):
# | Instrukce | Stručný popis |
---|---|---|
1 | LDR | načtení 32bitového či 64bitového registru z paměti |
2 | LDRB | načtení bajtu a s rozšířením na slovo (doplňují se nuly) |
3 | LDRSB | načtení bajtu se znaménkovým rozšířením |
4 | LDRH | načtení šestnáctibitového „půlslova“ s rozšířením o nuly |
5 | LDRSH | načtení šestnáctibitového „půlslova“ se znaménkovým rozšířením |
6 | LDRSW | načtení 32bitového slova se znaménkovým rozšířením do 64bitového registru |
7 | STR | uložení 32bitového či 64bitového registru do paměti |
8 | STRB | uložení bajtu z vybraného 32bitového registru |
9 | STRH | uložení šestnáctibitového „půlslova“ z vybraného 32bitového registru |
Poznámka: zdánlivě chybějící instrukce LDRW a STRW jsou již obsaženy v základních instrukcích LDR a STR pokud použijeme 32bitový registr (u LDR se horních 32 bitů vynuluje).
Mezi podporované adresovací režimy patří i použití offsetu vůči vybranému registru či SP. Navíc je možné zajistit takzvaný pre-index a post-index, tj. změnu obsahu registru obsahujícího adresu před či po provedení operace LOAD/STORE. Toho lze využít například při práci s poli nebo s řetězci:
ldr X1, [X2, #4]! // X1 ← *(X2 + 4) // X2 ← X2 + 4
ldr X1, [X2], #4 // X1 ← *X2 // X2 ← X2 + 4
9. Program typu „Hello, world!“ v assembleru
Druhý demonstrační příklad, který si dnes ukážeme, je nepatrně složitější, protože se jedná o aplikaci typu „Hello, world!“, v níž musíme volat dvě funkce jádra – sys_write určenou pro zápis na standardní výstup (to v našem případě – obecně se jedná o zápis binárních dat do otevřeného kanálu) a sys_exit pro ukončení aplikace. Způsob volání funkcí jádra s předáváním hodnot v pracovních registrech by nás již neměl ničím překvapit. Nová je však instrukce ldr, přesněji řečeno způsob určení adresy, která se má do zvoleného pracovního registru načíst. Ke způsobu překladu této instrukce se dostaneme v navazující kapitole. Další novinkou je definice dat v sekci/segmentu data. Jedná se o řetězec, který se má vypsat na terminál:
# asmsyntax=as # Jednoducha aplikace typu "Hello world!" naprogramovana # v assembleru GNU as. # # Autor: Pavel Tisnovsky # Linux kernel system call table sys_exit=93 sys_write=64 # List of syscalls for AArch64: # https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/unistd.h #----------------------------------------------------------------------------- .section .data hello_lbl: .string "Hello World!\n" #----------------------------------------------------------------------------- .section .bss #----------------------------------------------------------------------------- .section .text .global _start // tento symbol ma byt dostupny i linkeru _start: mov x8, #sys_write // cislo sycallu pro funkci "write" mov x0, #1 // standardni vystup ldr x1, =hello_lbl // adresa retezce, ktery se ma vytisknout mov x2, #13 // pocet znaku, ktere se maji vytisknout svc 0 // volani Linuxoveho kernelu mov x8, #sys_exit // cislo sycallu pro funkci "exit" mov x0, #0 // exit code = 0 svc 0 // volani Linuxoveho kernelu
10. Uložení adresy načítané instrukcí LDR
Opět se podívejme na způsob překladu druhého demonstračního příkladu do strojového kódu. Poznámky budou uvedeny pod výpisem zdrojového kódu:
$ objdump -f -d -t -h a.out
a.out: file format elf64-littleaarch64 architecture: aarch64, flags 0x00000102: EXEC_P, D_PAGED start address 0x00000000004000b0 Sections: Idx Name Size VMA LMA File off Algn 0 .text 00000028 00000000004000b0 00000000004000b0 000000b0 2**3 CONTENTS, ALLOC, LOAD, READONLY, CODE 1 .data 0000000e 00000000004100d8 00000000004100d8 000000d8 2**0 CONTENTS, ALLOC, LOAD, DATA SYMBOL TABLE: no symbols Disassembly of section .text: 00000000004000b0 &ld;.text>: 4000b0: d2800808 mov x8, #0x40 // #64 4000b4: d2800020 mov x0, #0x1 // #1 4000b8: 580000c1 ldr x1, 0x4000d0 4000bc: d28001a2 mov x2, #0xd // #13 4000c0: d4000001 svc #0x0 4000c4: d2800ba8 mov x8, #0x5d // #93 4000c8: d2800000 mov x0, #0x0 // #0 4000cc: d4000001 svc #0x0 4000d0: 004100d8 .inst 0x004100d8 ; undefined 4000d4: 00000000 udf #0
Nově se ve spustitelném kódu objevuje segment/sekce data, ve které je uložen řetězec, který se má vypsat na obrazovku. Ovšem zajímavější je instrukce LDR, která je zakódována následovně:
4000b8: 580000c1 ldr x1, 0x4000d0
Povšimněte si, že tato instrukce říká, že se obsah pracovního registru x1 má naplnit z paměťových buněk uložených na adresách 0×4000d0 až 0×4000d7 (jedná se totiž o 64bitový registr). Tyto adresy jsou ve skutečnosti součástí segmentu/sekce text a jsou tedy jakoby součástí programového kódu. A skutečně – jedná se o poslední dva řádky ve výpisu:
4000d0: 004100d8 .inst 0x004100d8 ; undefined 4000d4: 00000000 udf #0
Obsah výše zmíněných paměťových buněk je tedy 0×00000000 a 0×004100d8. Podívejme se ještě jednou na sekce nalezené v binárním souboru:
Sections: Idx Name Size VMA LMA File off Algn 0 .text 00000028 00000000004000b0 00000000004000b0 000000b0 2**3 CONTENTS, ALLOC, LOAD, READONLY, CODE 1 .data 0000000e 00000000004100d8 00000000004100d8 000000d8 2**0 CONTENTS, ALLOC, LOAD, DATA
Vidíme, že obsah těchto buněk skutečně obsahuje počáteční adresu sekce data, v níž je uložen řetězec. A jedná se o jediná data zde uložená a proto má sekce délku 0×0000000e bajtů neboli 14 bajtů. To opět přesně odpovídá kódu v assembleru:
hello_lbl: .string "Hello World!\n"
11. Zápis do datového segmentu
Potřetí se podívejme na informace o segmentech, z nichž se skládá binární spustitelný soubor:
Sections: Idx Name Size VMA LMA File off Algn 0 .text 00000028 00000000004000b0 00000000004000b0 000000b0 2**3 CONTENTS, ALLOC, LOAD, READONLY, CODE 1 .data 0000000e 00000000004100d8 00000000004100d8 000000d8 2**0 CONTENTS, ALLOC, LOAD, DATA
Zajímavé je, že segment text je určen pouze pro čtení, takže proces nemůže provádět žádné modifikace (tedy nelze vytvořit automodifikující se kód, což je asi jen dobře). Naproti tomu segment data je určen i pro zápis, o což se můžeme pokusit. V programovém kódu nejprve změníme obsah řetězce, který se má vytisknout a teprve poté řetězec vytiskneme. Abychom si neukazovali pouze „obyčejnou“ instrukci STRB (zápis bajtu), použijeme adresování s offsetem. Jinými slovy – zapíšeme nový bajt nikoli na začátek řetězce, ale na offsetu 5, a to bez výpočtu této adresy. Samotný výpočet offsetu, tj. adresy složené z obsahu pracovního registru x1 s offsetem 5 je proveden za běhu:
ldr x1, =hello_lbl // adresa retezce, ktery se ma modifikovat mov w2, #'*' // znak ktery zapiseme do retezce strb w2, [x1, 5] // zapis znaku s vyuzitim offsetu (prepiseme mezeru v retezci)
Upravený zdrojový kód tohoto příkladu vypadá následovně:
# Linux kernel system call table sys_exit=93 sys_write=64 # List of syscalls for AArch64: # https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/unistd.h #----------------------------------------------------------------------------- .section .data hello_lbl: .string "Hello World!\n" #----------------------------------------------------------------------------- .section .bss #----------------------------------------------------------------------------- .section .text .global _start // tento symbol ma byt dostupny i linkeru _start: ldr x1, =hello_lbl // adresa retezce, ktery se ma vytisknout mov w2, #'*' // znak ktery zapiseme do retezce strb w2, [x1, 5] // zapis znaku s vyuzitim offsetu mov x8, #sys_write // cislo sycallu pro funkci "write" mov x0, #1 // standardni vystup ldr x1, =hello_lbl // adresa retezce, ktery se ma vytisknout mov x2, #13 // pocet znaku, ktere se maji vytisknout svc 0 // volani Linuxoveho kernelu mov x8, #sys_exit // cislo sycallu pro funkci "exit" mov x0, #0 // exit code = 0 svc 0 // volani Linuxoveho kernelu
Po překladu a spuštění by se mělo vypsat:
Hello*World!
(s odřádkováním na konci)
12. Základní aritmetické instrukce
V následujícím demonstračním příkladu budou využity některé aritmetické instrukce, s nimiž se ve stručnosti seznámíme v této kapitole. Mezi základní aritmetické instrukce samozřejmě patří součet a rozdíl. Existují vždy čtyři varianty každé z těchto operací, které se od sebe liší podle toho, zda se při výpočtu nastavují příznaky (všechny čtyři – přetečení, přenos, nulovost a zápornost) a zda se po součtu či rozdílu ještě k výsledku přičte předchozí hodnota příznaku carry:
# | Instrukce | Stručný popis |
---|---|---|
1 | ADD | 32bitový či 64bitový součet |
2 | ADC | 32bitový či 64bitový součet s carry |
3 | ADDS | jako ADD, ovšem navíc nastaví příznakové bity |
4 | ADCS | jako ADC, ovšem navíc nastaví příznakové bity |
5 | SUB | 32bitový či 64bitový rozdíl |
6 | SBC | rozdíl s přičtením carry a odečtením jedničky |
7 | SUBS | jako SUB, ovšem navíc nastaví příznakové bity |
8 | SBCS | jako SBC, ovšem navíc nastaví příznakové bity |
Kromě toho existuje hned několik instrukčních aliasů, které je možné používat v assemblerech a které jsou rozpoznány i v disassemblerech a debuggerech. Tyto aliasy využívají registr WZR (32 bitů) či XZR (64 bitů). Připomeňme si, že tento registr je při čtení vždy nulový, což je v tomto případě výhodné, protože vlastně zadarmo získáme velké množství užitečných instrukcí:
# | Instrukce | Stručný popis |
---|---|---|
1 | NEG | alias pro SUB op1, xZR, op2 (op1 = 0 – op2) |
2 | NEGS | alias pro SUBS op1, xZR, op2 (op1 = 0 – op2 + nastavení příznaků) |
3 | NGC | alias pro SBC op1, xZR, op2 (op1 = 0 – op2 + carry – 1) |
4 | NGCS | kombinace předchozích dvou instrukcí |
5 | CMP | alias pro SUBS xZR, op1, op2 (tj.výsledek se zahodí, protože do registrů WZR/XZR se nezapisuje) |
6 | CMN | alias pro ADDS xZR, op1, op2 (dtto jako předchozí instrukce) |
7 | MOV | alias pro ADD reg1, reg2, #0 (platí je při přesunu dat mezi registry) |
Poznámka: v praxi se setkáte především s aliasy CMP, CMN a hlavně pak MOV (resp. přesněji řečeno je toto jedna z mnoha variant pseudoinstrukce MOV).
13. Použití aritmetických instrukcí
Ukažme si nyní příklad použití základních aritmetických instrukcí součtu a rozdílu. Nejdříve provedeme součet dvou hodnot. V RISCové instrukční sadě je nutné nejdříve operandy načíst do pracovních registrů a teprve poté provést příslušnou operaci. Aritmetické operace přitom používají takzvaný tříadresový kód, protože se v instrukci uvádí jak oba vstupní operandy, tak i registr, do kterého se provádí zápis (tj. takto je obsazeno 3×5=15 bitů instrukčního slova):
mov x0, #1 // prvni operand mov x1, #2 // druhy operand add x0, x0, x1 // operace souctu
Podobně je realizován rozdíl:
mov x0, #1000 // prvni operand mov x1, #995 // druhy operand sub x0, x0, x1 // operace souctu
V obou případech byl výsledek operace uložen zpět do registru x0. Nejnižší cifru výsledku zobrazíme relativně snadno – přičteme k ní hodnotu ASCII znaku „0“ a výsledek (jediný bajt) uložíme do bloku paměti s předpřipraveným řetězcem – v podstatě nahradíme znaky „?“ za vypočtené výsledky:
.section .data result_lbl: .string "x=?\ny=?\n" ldr x3, =result_lbl // adresa retezce, ktery se ma vytisknout # výsledek součtu mov x2, #'0' // prvni cifra v ASCII kodu add x0, x0, x2 // převod na ASCII strb w0, [x3, 2] // zapis znaku s vyuzitim offsetu # výsledek rozdílu mov x2, #'0' // prvni cifra v ASCII kodu add x0, x0, x2 // převod na ASCII strb w0, [x3, 6] // zapis znaku s vyuzitim offsetu
Ukažme si nyní úplný zdrojový kód upraveného demonstračního příkladu:
# asmsyntax=as # Zakladni aritmeticke instrukce # v assembleru GNU as. # # Autor: Pavel Tisnovsky # Linux kernel system call table sys_exit=93 sys_write=64 # List of syscalls for AArch64: # https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/unistd.h #----------------------------------------------------------------------------- .section .data result_lbl: .string "x=?\ny=?\n" #----------------------------------------------------------------------------- .section .bss #----------------------------------------------------------------------------- .section .text .global _start // tento symbol ma byt dostupny i linkeru _start: ldr x3, =result_lbl // adresa retezce, ktery se ma vytisknout mov x0, #1 // prvni operand mov x1, #2 // druhy operand add x0, x0, x1 // operace souctu mov x2, #'0' // prvni cifra v ASCII kodu add x0, x0, x2 // převod na ASCII strb w0, [x3, 2] // zapis znaku s vyuzitim offsetu mov x0, #1000 // prvni operand mov x1, #995 // druhy operand sub x0, x0, x1 // operace souctu mov x2, #'0' // prvni cifra v ASCII kodu add x0, x0, x2 // převod na ASCII strb w0, [x3, 6] // zapis znaku s vyuzitim offsetu mov x8, #sys_write // cislo sycallu pro funkci "write" mov x0, #1 // standardni vystup ldr x1, =result_lbl // adresa retezce, ktery se ma vytisknout mov x2, #8 // pocet znaku, ktere se maji vytisknout svc 0 // volani Linuxoveho kernelu mov x8, #sys_exit // cislo sycallu pro funkci "exit" mov x0, #0 // exit code = 0 svc 0 // volani Linuxoveho kernelu
Po překladu a spuštění by se měly zobrazit vypočtené výsledky:
x=3 y=5
14. Nepodmíněné skoky a skoky do subrutiny (branch with link)
U instrukční sady AArch64 je zajímavé (a současně i velmi užitečné), že základní skoková instrukce B neprovádí skok na zadanou absolutní adresu, ale na adresu relativní vůči aktuální hodnotě PC. Rozsah skoku je v takovém případě ±128 MB; větší konstantu není možné v dané konfiguraci instrukčního slova uložit. Dále pak instrukční soubor obsahuje instrukci pro skok do podprogramu s uložením návratové adresy do registru X30 (nikoli na zásobník, používáme RISCový čip) a skok na absolutní adresu uloženou ve vybraném registru. Tato instrukce existuje ve dvou variantách, které se od sebe odlišují pouze hintem pro CPU, zda se právě realizuje návrat z podprogramu (subrutiny) či obyčejný skok:
# | Instrukce | Stručný popis |
---|---|---|
1 | B | skok na adresu vypočtenou z offsetu vůči PC v rozsahu ±128 MB |
2 | BL | branch and link, stejné jako předchozí instrukce, ovšem původní hodnota PC se uloží do X30 |
3 | BR | skok na adresu uloženou v registru s hintem, že se nejedná o výskok z podprogramu |
4 | RET | jako BR, ovšem s hintem, že se jedná o výskok z podprogramu |
5 | BRL | kombinace BR + BL, tj. skok na adresu uloženou v registru + původní PC do X30 |
Nekonečnou smyčku lze realizovat příkazem B (program se potom musí ukončit stiskem klávesy Ctrl+C nebo příkazem kill):
# asmsyntax=as # Nekonečná smyčka # v assembleru GNU as. # # Autor: Pavel Tisnovsky # Linux kernel system call table sys_exit=93 sys_write=64 # List of syscalls for AArch64: # https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/unistd.h #----------------------------------------------------------------------------- .section .data message: .string "Diamons are forever!\n" end_string: #----------------------------------------------------------------------------- .section .bss #----------------------------------------------------------------------------- .section .text .global _start // tento symbol ma byt dostupny i linkeru _start: loop: mov x8, #sys_write // cislo sycallu pro funkci "write" mov x0, #1 // standardni vystup ldr x1, =message // adresa retezce, ktery se ma vytisknout mov x2, #(end_string-message) // pocet znaku, ktere se maji vytisknout svc 0 // volani Linuxoveho kernelu b loop // dokolecka dokola
Dvě poznámky k ukázanému kódu:
- Zcela chybí volání služby jádra pro ukončení aplikace. To je logické, protože do této části by se program nikdy nedostal.
- Povšimněte si malého triku, jak lze vypočítat délku řetězce už v čase překladu s využitím dvou návěští (label).
15. Podmíněné skoky
Instrukční sada AArch64 je hned v několika ohledech odlišná od původní 32bitové RISCové instrukční sady ARM32 (či zkráceně pouze A32). Zásadní a na první pohled viditelná odlišnost spočívá v tom, že se zredukoval počet těch strojových instrukcí, u nichž je možné použít podmínkové bity. Jen ve stručnosti si připomeňme, že v instrukční sadě ARM32 jsou v každém instrukčním slovu (každé má bez výjimky konstantní šířku třiceti dvou bitů) rezervovány čtyři nejvyšší bity, v nichž je zapsán kód podmínky, při jejímž splnění se instrukce provede. Díky této vlastnosti bylo možné v mnoha algoritmech zredukovat počet podmíněných skoků, což je v případě (dnes už nejenom) RISCových procesorů poměrně důležité.
Obrázek 1: Formáty instrukčních slov u původní 32bitové architektury ARM. Povšimněte si, že v každém instrukčním slovu jsou nejvyšší čtyři bity vyhrazeny pro uložení kódu podmínky.
Všechny podmínky jsou vyhodnoceny na základě hodnoty jednoho či (častěji) kombinací většího množství příznaků (flags). První sada podmínkových kódů se používá pro provedení či naopak neprovedení instrukce na základě hodnoty jednoho z příznakových bitů Z (zero), V (overflow) či N (negative). Poslední podmínkový kód z této skupiny má název AL (Any/Always) a značí, že se instrukce provede v každém případě. Tento podmínkový kód se tudíž většinou v assembleru ani nezapisuje, protože je považován za implicitní:
Kód | Přípona | Význam | Testovaná podmínka |
---|---|---|---|
0000 | EQ | Z == 1 | rovnost při porovnání či nulový výsledek poslední ALU operace |
0001 | NE | Z == 0 | nerovnost při porovnání či nenulový výsledek poslední ALU operace |
0100 | MI | N == 1 | výsledek je záporný |
0101 | PL | N == 0 | výsledek je kladný či 0 |
0110 | VS | V == 1 | nastalo přetečení |
0111 | VC | V == 0 | nenastalo přetečení |
1110 | AL | Any/Always | většinou se nezapisuje, implicitní podmínka |
Další čtyři podmínkové kódy se většinou používají při porovnávání dvou celých hodnot bez znaménka (unsigned integer). V těchto případech se testují stavy příznakových bitů C (carry) a Z (zero), přesněji řečeno kombinace těchto bitů:
Kód | Přípona | Význam | Testovaná podmínka |
---|---|---|---|
0010 | CS/HS | C == 1 | ≥ |
0011 | CC/LO | C == 0 | < |
1000 | HI | C == 1 & Z == 0 | > |
1001 | LS | C == 0 | Z == 1 | ≤ |
Poslední čtyři podmínkové kódy se používají pro porovnávání hodnot se znaménkem (signed). V těchto případech se namísto příznakových bitů © carry a (Z) zero testují kombinace bitů (N) negative, (V) overflow a (Z) zero:
Kód | Přípona | Význam | Testovaná podmínka |
---|---|---|---|
1010 | GE | N == V | ≥ |
1011 | LT | N ≠ V | < |
1100 | GT | Z = 0, N = V | > |
1101 | LE | Z = 1, N ≠ V | ≤ |
Procesory s architekturou AArch64 sice používají shodné podmínkové bity, ty jsou ovšem použity jen v několika pečlivě vybraných instrukcích. Příznak přetečení je, podobně jako u mnoha dalších typů procesorů, používán při aritmetických operacích (ovšem ne implicitně – jen u instrukcí končících na S – set) a testy podmínkových bitů lze provádět především u podmíněných skoků, tj. u instrukcí, jejichž mnemotechnická zkratka začíná znakem B od slova „Branch“. Rozeznáváme následující typy nepodmíněných podmíněných skoků:
# | Instrukce | Alternativní zápis |
---|---|---|
1 | B | BAL |
2 | B.EQ | BEQ |
3 | B.NE | BNE |
4 | B.MI | BMI |
5 | B.PL | BPL |
6 | B.VS | BVS |
7 | B.VC | BVC |
8 | B.CS | BCS |
9 | B.CC | BCC |
10 | B.HI | BHI |
11 | B.LS | BLS |
12 | B.GE | BGE |
13 | B.LT | BLT |
14 | B.GT | BGT |
15 | B.LE | BLE |
16. Další varianty podmíněných skoků
Další dva typy skoků jsou odvozeny od instrukcí, které známe z instrukční sady Thumb. Dochází zde k porovnání vybraného pracovního registru s nulou:
# | Instrukce | Stručný popis |
---|---|---|
1 | CBZ | Compare and Branch if Zero |
2 | CBNZ | Compare and Branch if Not Zero |
Existují ještě další dvě instrukce pro podmíněné skoky, které se jmenují TBZ (Test and Branch if Zero) a TBNZ (Test and Branch if Not Zero). Ty provádí test hodnoty vybraného bitu registru na nulu:
# | Instrukce | Stručný popis |
---|---|---|
1 | TBZ | Test and Branch if Zero |
2 | TBNZ | Test and Branch if Not Zero |
Způsob zápisu těchto instrukcí je následující:
TBZ Xn, #konstanta, návěští TBZ Wn, #konstanta, návěští TBNZ Xn, #konstanta, návěští TBNZ Wn, #konstanta, návěští
Konstanta má šířku pouze šest bitů, protože je v ní uložen index bitu pracovního registru, který se testuje na nulu či jedničku (u registrů Wn by stačilo jen pět bitů). V případě instrukce TBZ – pokud je n-tý bit registru Xn/Wn nastavený na nulu, provede se skok, v opačném případě se řízení přenese na další instrukci. V případě instrukce TBNZ je bit testován na jedničku. Vzhledem k tomu, že v instrukčním slovu je nutné kromě adresy cíle (návěští) specifikovat i číslo pracovního registru a index bitu, je tento typ skoku omezen na rozsah ±32kB, což by ovšem v praxi mělo být více než dostačující (v opačném případě lze TBZ/TBNZ zkombinovat s absolutním skokem B).
17. Ukázka programové smyčky
V dalším demonstračním příkladu jsou ukázány dvě nové vlastnosti. Zejména se jedná a použití sekce/sektoru bss, v němž jsou neinicializovaná data. Tato sekce není ve výsledném binárním programu plně alokována, což znamená, že i když budete chtít v assembleru pracovat s obrovskými poli, nebude se velikost binárního programu zvětšovat (tedy například 1MB pole v bss nezpůsobí zvětšení binárního souboru o 1MB):
# pocet opakovani znaku rep_count = 40 .section .bss .lcomm buffer, rep_count // rezervace bufferu pro vystup
A druhou vlastností, kterou si zde ukážeme, je programová smyčka založená na podmíněném skoku. Nejprve je porovnána hodnota pracovního registru x2 s nulou (což je řešeno pseudoinstrukcí CMP). Tím se nastaví (či nenastaví) příznak Z (zero). A posléze následuje podmíněný skok na začátek smyčky ve chvíli, kdy příznak ještě není nastaven:
mov w3, #'*' // zapisovany znak loop: strb w3, [x1] // zapis znaku do bufferu add x1, x1, #1 // uprava ukazatele do bufferu sub x2, x2, #1 // zmenseni pocitadla cmp x2, #0 // otestovani, zda jsme jiz nedosahli nuly bne loop // pokud jsme se nedostali k nule, skok na zacatek smycky
Tato smyčka vyplní oblast paměti alokované v bss hvězdičkami, které jsou následně vypsány na standardní výstup:
mov x8, #sys_write // cislo syscallu pro funkci "write" mov x0, #std_output // standardni vystup ldr x1, =buffer // adresa retezce, ktery se ma vytisknout mov x2, #rep_count // pocet znaku, ktere se maji vytisknout svc 0 // volani Linuxoveho kernelu
Úplný tvar tohoto demonstračního příkladu vypadá následovně:
# asmsyntax=as # Testovaci program naprogramovany v assembleru GNU as # - pocitana programova smycka # - uprava pro mikroprocesory s architekturou AArch64 # # Autor: Pavel Tisnovsky # Linux kernel system call table sys_exit = 93 sys_write = 64 # List of syscalls for AArch64: # https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/unistd.h # Dalsi konstanty pouzite v programu - standardni streamy std_input = 0 std_output = 1 # pocet opakovani znaku rep_count = 40 #----------------------------------------------------------------------------- .section .data #----------------------------------------------------------------------------- .section .bss .lcomm buffer, rep_count // rezervace bufferu pro vystup #----------------------------------------------------------------------------- .section .text .global _start // tento symbol ma byt dostupny i linkeru _start: ldr x1, =buffer // zapis se bude provadet do tohoto bufferu mov x2, #rep_count // pocet opakovani znaku mov w3, #'*' // zapisovany znak loop: strb w3, [x1] // zapis znaku do bufferu add x1, x1, #1 // uprava ukazatele do bufferu sub x2, x2, #1 // zmenseni pocitadla cmp x2, #0 // otestovani, zda jsme jiz nedosahli nuly bne loop // pokud jsme se nedostali k nule, skok na zacatek smycky mov x8, #sys_write // cislo syscallu pro funkci "write" mov x0, #std_output // standardni vystup ldr x1, =buffer // adresa retezce, ktery se ma vytisknout mov x2, #rep_count // pocet znaku, ktere se maji vytisknout svc 0 // volani Linuxoveho kernelu mov x8, #sys_exit // cislo sycallu pro funkci "exit" mov x0, #0 // exit code = 0 svc 0 // volani Linuxoveho kernelu
18. Obsah následujícího článku
Programová smyčka v posledním demonstračním příkladu není ve skutečnosti napsána příliš optimálně. V navazující části článku si tedy ukážeme, jak tuto smyčku zapsat s využitím menšího počtu instrukcí a taktéž s využitím dalších adresovacích režimů, které umožňují automaticky zvýšit adresu (ukazatel) před či po provedení operace čtení/zápisu. Právě takové optimalizace musí provádět i překladače, aby plně využily možností nabízených architekturou AArch64.
19. Předchozí články o architektuře AArch64
S architekturou AArch64 jsme se již na stránkách Roota setkali, a to konkrétně v následující pětici článků, z nichž dnes vycházíme:
- 64bitové mikroprocesory s architekturou AArch64
https://www.root.cz/clanky/64bitove-mikroprocesory-s-architekturou-aarch64/ - Instrukční sada AArch64
https://www.root.cz/clanky/instrukcni-sada-aarch64/ - Instrukční sada AArch64 (2.část)
https://www.root.cz/clanky/instrukcni-sada-aarch64–2-cast/ - Tvorba a ladění programů v assembleru mikroprocesorů AArch64
https://www.root.cz/clanky/tvorba-a-ladeni-programu-v-assembleru-mikroprocesoru-aarch64/ - Instrukční sada AArch64: technologie NEON
https://www.root.cz/clanky/instrukcni-sada-aarch64-technologie-neon/
20. Odkazy na Internetu
- Arm Architecture Reference Manual for A-profile architecture
https://developer.arm.com/documentation/ddi0487/latest - GNU Binutils
https://sourceware.org/binutils/ - Documentation for binutils 2.38
https://sourceware.org/binutils/docs-2.38/ - AArch64 Instruction Set Architecture
https://developer.arm.com/architectures/learn-the-architecture/aarch64-instruction-set-architecture/instruction-sets-in-the-arm-architecture - Arm Armv8-A A32/T32 Instruction Set Architecture
https://developer.arm.com/documentation/ddi0597/2021–12/?lang=en - Comparison of ARMv8-A cores
https://en.wikipedia.org/wiki/Comparison_of_ARMv8-A_cores - Cortex-A32 Processor
https://www.arm.com/products/processors/cortex-a/cortex-a32-processor.php - Cortex-A35 Processor
https://www.arm.com/products/processors/cortex-a/cortex-a35-processor.php - Cortex-A53 Processor
https://www.arm.com/products/processors/cortex-a/cortex-a53-processor.php - Cortex-A57 Processor
https://www.arm.com/products/processors/cortex-a/cortex-a57-processor.php - Cortex-A72 Processor
https://www.arm.com/products/processors/cortex-a/cortex-a72-processor.php - Cortex-A73 Processor
https://www.arm.com/products/processors/cortex-a/cortex-a73-processor.php - Apple A7 (SoC založen na CPU Cyclone)
https://en.wikipedia.org/wiki/Apple_A7 - System cally pro AArch64 na Linuxu
https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/unistd.h - Architectures/AArch64 (FedoraProject.org)
https://fedoraproject.org/wiki/Architectures/AArch64 - SIG pro AArch64 (CentOS)
https://wiki.centos.org/SpecialInterestGroup/AltArch/AArch64 - The ARMv8 instruction sets
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.den0024a/ch05s01.html - A64 Instruction Set
https://developer.arm.com/products/architecture/instruction-sets/a64-instruction-set - Switching between the instruction sets
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.den0024a/ch05s01.html - The A64 instruction set
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.den0024a/ch05s01.html - Introduction to ARMv8 64-bit Architecture
https://quequero.org/2014/04/introduction-to-arm-architecture/ - MCU market turns to 32-bits and ARM
http://www.eetimes.com/document.asp?doc_id=1280803 - Cortex-M0 Processor (ARM Holdings)
http://www.arm.com/products/processors/cortex-m/cortex-m0.php - Cortex-M0+ Processor (ARM Holdings)
http://www.arm.com/products/processors/cortex-m/cortex-m0plus.php - ARM Processors in a Mixed Signal World
http://www.eeweb.com/blog/arm/arm-processors-in-a-mixed-signal-world - ARM Architecture (Wikipedia)
https://en.wikipedia.org/wiki/ARM_architecture - DSP for Cortex-M
https://developer.arm.com/technologies/dsp/dsp-for-cortex-m - Cortex-M processors in DSP applications? Why not?!
https://community.arm.com/processors/b/blog/posts/cortex-m-processors-in-dsp-applications-why-not - White Paper – DSP capabilities of Cortex-M4 and Cortex-M7
https://community.arm.com/processors/b/blog/posts/white-paper-dsp-capabilities-of-cortex-m4-and-cortex-m7 - Q (number format)
https://en.wikipedia.org/wiki/Q_%28number_format%29 - TriCore Architecture & Core
http://www.infineon.com/cms/en/product/microcontroller/32-bit-tricore-tm-microcontroller/tricore-tm-architecture-and-core/channel.html?channel=ff80808112ab681d0112ab6b73d40837 - TriCoreTM V1.6 Instruction Set: 32-bit Unified Processor Core
http://www.infineon.com/dgdl/tc_v131_instructionset_v138.pdf?fileId=db3a304412b407950112b409b6dd0352 - TriCore v2.2 C Compiler, Assembler, Linker Reference Manual
http://tasking.com/support/tricore/tc_reference_guide_v2.2.pdf - Infineon TriCore (Wikipedia)
https://en.wikipedia.org/wiki/Infineon_TriCore - C166®S V2 Architecture & Core
http://www.infineon.com/cms/en/product/microcontroller/16-bit-c166-microcontroller/c166-s-v2-architecture-and-core/channel.html?channel=db3a304312bef5660112c3011c7d01ae - Comparing four 32-bit soft processor cores
http://www.eetimes.com/author.asp?section_id=14&doc_id=1286116 - RISC-V Instruction Set
http://riscv.org/download.html#spec_compressed_isa - RISC-V Spike (ISA Simulator)
http://riscv.org/download.html#isa-sim - RISC-V (Wikipedia)
https://en.wikipedia.org/wiki/RISC-V - David Patterson (Wikipedia)
https://en.wikipedia.org/wiki/David_Patterson_(computer_scientist) - OpenRISC (oficiální stránky projektu)
http://openrisc.io/ - OpenRISC architecture
http://openrisc.io/architecture.html - Emulátor OpenRISC CPU v JavaScriptu
http://s-macke.github.io/jor1k/demos/main.html - OpenRISC (Wikipedia)
https://en.wikipedia.org/wiki/OpenRISC - OpenRISC – instrukce
http://sourceware.org/cgen/gen-doc/openrisc-insn.html - OpenRISC – slajdy z přednášky o projektu
https://iis.ee.ethz.ch/~gmichi/asocd/lecturenotes/Lecture6.pdf - Berkeley RISC
http://en.wikipedia.org/wiki/Berkeley_RISC - Great moments in microprocessor history
http://www.ibm.com/developerworks/library/pa-microhist.html - Microprogram-Based Processors
http://research.microsoft.com/en-us/um/people/gbell/Computer_Structures_Principles_and_Examples/csp0167.htm - Great Microprocessors of the Past and Present
http://www.cpushack.com/CPU/cpu1.html - A Brief History of Microprogramming
http://www.cs.clemson.edu/~mark/uprog.html - What is RISC?
http://www-cs-faculty.stanford.edu/~eroberts/courses/soco/projects/2000–01/risc/whatis/ - RISC vs. CISC
http://www-cs-faculty.stanford.edu/~eroberts/courses/soco/projects/2000–01/risc/risccisc/ - RISC and CISC definitions:
http://www.cpushack.com/CPU/cpuAppendA.html - FPGA
https://cs.wikipedia.org/wiki/Programovateln%C3%A9_hradlov%C3%A9_pole - The Evolution of RISC
http://www.ibm.com/developerworks/library/pa-microhist.html#sidebar1 - disasm.pro
https://disasm.pro/ - Exploring AArch64 assembler – Chapter 5
https://thinkingeek.com/2016/11/13/exploring-aarch64-assembler-chapter-5/