Hlavní navigace

Specifické vlastnosti procesorů AArch64: základní instrukce

1. 3. 2022
Doba čtení: 34 minut

Sdílet

 Autor: Raspberry Pi Foundation
Instrukční sada procesorů s architekturou AArch64 má některé zajímavé vlastnosti, s nimiž se můžeme postupně seznámit. Dnes si ukážeme několik plně funkčních příkladů založených na několika základních instrukcích.

Obsah

1. Specifické vlastnosti procesorů AArch64: úvod

2. Použitý počítač

3. Instalace základních vývojářských nástrojů

4. Sada pracovních registrů mikroprocesorů s architekturou AArch64

5. Instrukce MOV a SVC

6. Nejjednodušší korektní program zapsaný v assembleru

7. Kódování instrukcí MOV a SVC u architektury AArch64

8. Instrukce typu Load-Store

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)

15. Podmíněné skoky

16. Další varianty podmíněných skoků

17. Ukázka programové smyčky

18. Obsah následujícího článku

19. Předchozí články o architektuře AArch64

20. Odkazy na Internetu

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 ADDSUB
  • 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
Poznámka: v rámci instalace se vytvořily i manuálové stránky pro všechny výše vypsané nástroje. Z nich nás dnes budou zajímat především as, ld, objcopy, strings a 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 R0R14, 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ží).

Poznámka: s „nulovým“ registrem se setkáme i v dalších RISCových architekturách. Jeho existence velmi často vede ke zjednodušení instrukční sady, dekodéru instrukcí i vlastního mikroprocesoru.

Některé registry mají i další funkce:

  1. Registr x30 se používá ve funkci LR (Link Register).
  2. 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
Poznámka: GNU assembler rozeznává instrukci MOV, ovšem ve skutečnosti se jedná o pseudoinstrukci, zde konkrétně o alias instrukce MOVI.

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.

Poznámka: jen pro porovnání se můžeme podívat na variantu určenou pro architekturu x86, v níž jsou použita odlišná čísla služeb jádra, pro volání jádra se používá instrukce int a liší se mj. i značení a pořadí operandů u instrukcí:
# 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
Poznámka: celý instrukční soubor má teoreticky 232 instrukčních kódů (ne všechny jsou však obsazené). Vysvětlením instrukce mov Rd, #konstanta jsme již pokryli minimálně 216+5=221 ze všech možných kombinací.

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
Poznámka: právě tuto variantu instrukce str (konkrétně strb) použijeme v dalším textu.

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
Poznámka: povšimněte si, že systémová funkce sys_write vyžaduje zadání tří parametrů: určení kanálu (například otevřeného souboru), do kterého se budou data zapisovat, dále počáteční adresu bloku dat a konečně počet zapisovaných bajtů. Jedná se tedy o obecnou funkci, u které si musíme dát pozor na to, že nebere v úvahu například způsoby ukončení standardních céčkovských řetězců.

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
Poznámka: objdump se snaží obsah interpretovat jako instrukce, což se mu nedaří a dařit ani nemůže.

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"
Poznámka: s tímto trikem se setkáme poměrně často i u některých dalších RISCových architektur. Jedná se o to, že v 32bitovém instrukčním slovu není (a nemůže být) místo pro uložení plné adresy a ještě navíc instrukčního kódu. Z tohoto důvodu je adresa uložena v blízkém dosahu a je adresována relativně.

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
Poznámka: takto jednoduše lze ovšem pracovat pouze s jedinou cifrou výsledku, nikoli například s pěticiferným výsledkem!

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:

  1. 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.
  2. 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
Poznámka: alternativní zápis je podporován například GNU Assemblerem, většinou však můžete používat obě varianty zápisu.

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:

  1. 64bitové mikroprocesory s architekturou AArch64
    https://www.root.cz/clanky/64bitove-mikroprocesory-s-architekturou-aarch64/
  2. Instrukční sada AArch64
    https://www.root.cz/clanky/instrukcni-sada-aarch64/
  3. Instrukční sada AArch64 (2.část)
    https://www.root.cz/clanky/instrukcni-sada-aarch64–2-cast/
  4. Tvorba a ladění programů v assembleru mikroprocesorů AArch64
    https://www.root.cz/clanky/tvorba-a-ladeni-programu-v-assembleru-mikroprocesoru-aarch64/
  5. Instrukční sada AArch64: technologie NEON
    https://www.root.cz/clanky/instrukcni-sada-aarch64-technologie-neon/

20. Odkazy na Internetu

  1. Arm Architecture Reference Manual for A-profile architecture
    https://developer.arm.com/do­cumentation/ddi0487/latest
  2. GNU Binutils
    https://sourceware.org/binutils/
  3. Documentation for binutils 2.38
    https://sourceware.org/binutils/docs-2.38/
  4. AArch64 Instruction Set Architecture
    https://developer.arm.com/ar­chitectures/learn-the-architecture/aarch64-instruction-set-architecture/instruction-sets-in-the-arm-architecture
  5. Arm Armv8-A A32/T32 Instruction Set Architecture
    https://developer.arm.com/do­cumentation/ddi0597/2021–12/?lang=en
  6. Comparison of ARMv8-A cores
    https://en.wikipedia.org/wi­ki/Comparison_of_ARMv8-A_cores
  7. Cortex-A32 Processor
    https://www.arm.com/produc­ts/processors/cortex-a/cortex-a32-processor.php
  8. Cortex-A35 Processor
    https://www.arm.com/produc­ts/processors/cortex-a/cortex-a35-processor.php
  9. Cortex-A53 Processor
    https://www.arm.com/produc­ts/processors/cortex-a/cortex-a53-processor.php
  10. Cortex-A57 Processor
    https://www.arm.com/produc­ts/processors/cortex-a/cortex-a57-processor.php
  11. Cortex-A72 Processor
    https://www.arm.com/produc­ts/processors/cortex-a/cortex-a72-processor.php
  12. Cortex-A73 Processor
    https://www.arm.com/produc­ts/processors/cortex-a/cortex-a73-processor.php
  13. Apple A7 (SoC založen na CPU Cyclone)
    https://en.wikipedia.org/wi­ki/Apple_A7
  14. System cally pro AArch64 na Linuxu
    https://github.com/torval­ds/linux/blob/master/inclu­de/uapi/asm-generic/unistd.h
  15. Architectures/AArch64 (FedoraProject.org)
    https://fedoraproject.org/wi­ki/Architectures/AArch64
  16. SIG pro AArch64 (CentOS)
    https://wiki.centos.org/Spe­cialInterestGroup/AltArch/A­Arch64
  17. The ARMv8 instruction sets
    http://infocenter.arm.com/hel­p/index.jsp?topic=/com.ar­m.doc.den0024a/ch05s01.html
  18. A64 Instruction Set
    https://developer.arm.com/pro­ducts/architecture/instruc­tion-sets/a64-instruction-set
  19. Switching between the instruction sets
    http://infocenter.arm.com/hel­p/index.jsp?topic=/com.ar­m.doc.den0024a/ch05s01.html
  20. The A64 instruction set
    http://infocenter.arm.com/hel­p/index.jsp?topic=/com.ar­m.doc.den0024a/ch05s01.html
  21. Introduction to ARMv8 64-bit Architecture
    https://quequero.org/2014/04/in­troduction-to-arm-architecture/
  22. MCU market turns to 32-bits and ARM
    http://www.eetimes.com/do­cument.asp?doc_id=1280803
  23. Cortex-M0 Processor (ARM Holdings)
    http://www.arm.com/produc­ts/processors/cortex-m/cortex-m0.php
  24. Cortex-M0+ Processor (ARM Holdings)
    http://www.arm.com/produc­ts/processors/cortex-m/cortex-m0plus.php
  25. ARM Processors in a Mixed Signal World
    http://www.eeweb.com/blog/arm/arm-processors-in-a-mixed-signal-world
  26. ARM Architecture (Wikipedia)
    https://en.wikipedia.org/wi­ki/ARM_architecture
  27. DSP for Cortex-M
    https://developer.arm.com/techno­logies/dsp/dsp-for-cortex-m
  28. Cortex-M processors in DSP applications? Why not?!
    https://community.arm.com/pro­cessors/b/blog/posts/cortex-m-processors-in-dsp-applications-why-not
  29. White Paper – DSP capabilities of Cortex-M4 and Cortex-M7
    https://community.arm.com/pro­cessors/b/blog/posts/white-paper-dsp-capabilities-of-cortex-m4-and-cortex-m7
  30. Q (number format)
    https://en.wikipedia.org/wi­ki/Q_%28number_format%29
  31. TriCore Architecture & Core
    http://www.infineon.com/cms/en/pro­duct/microcontroller/32-bit-tricore-tm-microcontroller/tricore-tm-architecture-and-core/channel.html?channel=ff80808112ab681d0112­ab6b73d40837
  32. TriCoreTM V1.6 Instruction Set: 32-bit Unified Processor Core
    http://www.infineon.com/dgdl/tc_v131_in­structionset_v138.pdf?file­Id=db3a304412b407950112b409b6dd0352
  33. TriCore v2.2 C Compiler, Assembler, Linker Reference Manual
    http://tasking.com/suppor­t/tricore/tc_reference_gu­ide_v2.2.pdf
  34. Infineon TriCore (Wikipedia)
    https://en.wikipedia.org/wi­ki/Infineon_TriCore
  35. C166®S V2 Architecture & Core
    http://www.infineon.com/cms/en/pro­duct/microcontroller/16-bit-c166-microcontroller/c166-s-v2-architecture-and-core/channel.html?channel=db3a304312bef5660112­c3011c7d01ae
  36. Comparing four 32-bit soft processor cores
    http://www.eetimes.com/au­thor.asp?section_id=14&doc_id=1286116
  37. RISC-V Instruction Set
    http://riscv.org/download­.html#spec_compressed_isa
  38. RISC-V Spike (ISA Simulator)
    http://riscv.org/download.html#isa-sim
  39. RISC-V (Wikipedia)
    https://en.wikipedia.org/wiki/RISC-V
  40. David Patterson (Wikipedia)
    https://en.wikipedia.org/wi­ki/David_Patterson_(compu­ter_scientist)
  41. OpenRISC (oficiální stránky projektu)
    http://openrisc.io/
  42. OpenRISC architecture
    http://openrisc.io/architecture.html
  43. Emulátor OpenRISC CPU v JavaScriptu
    http://s-macke.github.io/jor1k/demos/main.html
  44. OpenRISC (Wikipedia)
    https://en.wikipedia.org/wi­ki/OpenRISC
  45. OpenRISC – instrukce
    http://sourceware.org/cgen/gen-doc/openrisc-insn.html
  46. OpenRISC – slajdy z přednášky o projektu
    https://iis.ee.ethz.ch/~gmichi/a­socd/lecturenotes/Lecture6­.pdf
  47. Berkeley RISC
    http://en.wikipedia.org/wi­ki/Berkeley_RISC
  48. Great moments in microprocessor history
    http://www.ibm.com/develo­perworks/library/pa-microhist.html
  49. Microprogram-Based Processors
    http://research.microsoft.com/en-us/um/people/gbell/Computer_Struc­tures_Principles_and_Exam­ples/csp0167.htm
  50. Great Microprocessors of the Past and Present
    http://www.cpushack.com/CPU/cpu1.html
  51. A Brief History of Microprogramming
    http://www.cs.clemson.edu/~mar­k/uprog.html
  52. What is RISC?
    http://www-cs-faculty.stanford.edu/~ero­berts/courses/soco/projec­ts/2000–01/risc/whatis/
  53. RISC vs. CISC
    http://www-cs-faculty.stanford.edu/~ero­berts/courses/soco/projec­ts/2000–01/risc/risccisc/
  54. RISC and CISC definitions:
    http://www.cpushack.com/CPU/cpu­AppendA.html
  55. FPGA
    https://cs.wikipedia.org/wi­ki/Programovateln%C3%A9_hra­dlov%C3%A9_pole
  56. The Evolution of RISC
    http://www.ibm.com/develo­perworks/library/pa-microhist.html#sidebar1
  57. disasm.pro
    https://disasm.pro/
  58. Exploring AArch64 assembler – Chapter 5
    https://thinkingeek.com/2016/11/13/ex­ploring-aarch64-assembler-chapter-5/

Autor článku

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