Egy C-programban a uint64_t változó inicializálása egy
nagy konstanssal egyetlen forrássor. A fordító azonban több gépi
utasítást is kibocsáthat hozzá. Ez akkor válik mérnöki kérdéssé, amikor
disassemblyben keresünk egy értéket, kézzel írunk rövid inicializáló
kódot, vagy megmagyarázzuk egy függvény kódméretét.
Az utasításba kódolt közvetlen operandus (immediate operand) olyan érték, amelyet maga az utasításkód tartalmaz. Nem memóriából olvassuk, és nem egy másik regiszter szolgáltatja.
Hatáskör: az alfejezet minden utasítása és regisztere az A64 ISA, AArch64, I. rész kontextusában értendő. Az assemblydirektívák a GNU assembler AArch64 céljához tartoznak; nem CPU-utasítások. A labor little-endian profilt használ. A konstansokat előjel nélküli bitmintaként kezeljük, kivéve, ahol külön előjeles értelmezést adunk.
Az előfeltétel a Linux syscall-konvenció AArch64-en: a labor végén az ott megismert kilépést használjuk.
MOV:
kényelmes név, többféle kódolásAz A64 utasítások hossza rögzítetten 32 bit. Ebből az utasítás fajtáját, az operandusokat és az esetleges közvetlen értéket is ki kell olvasni.
Levezetés: egy tetszőleges 64 bites konstansnak lehetséges értéke van. Egy 32 bites utasításnak összesen csak különböző bitmintája lehet, ráadásul nem mind konstansbetöltés. Ezért egyetlen A64 utasítás nem kódolhatja közvetlenül az összes lehetséges 64 bites konstanst.
Az A64 MOV kényelmi, gyakran pszeudoutasításként
emlegetett írásmód. Pontosabban az itt használt formái más utasítások
aliasnevei (instruction aliases). Az assembler
a megfelelő gépi kódolást választja ki.
| A64 forrásalak | Mögöttes kódolás, illetve jelentés |
|---|---|
mov X4, X3 |
orr X4, XZR, X3: regisztermásolás |
mov W4, W3 |
orr W4, WZR, W3: 32 bites regisztermásolás |
mov X3, #0x1234 |
Például MOVZ: közvetlen konstansbetöltés |
mov X3, #-1 |
MOVN nulla közvetlen mezővel |
mov X3, #0x00ff00ff00ff00ff |
Logikai immediate-et használó ORR |
Az A64 XZR és WZR olvasáskor nullát
szolgáltató nullaregiszter (zero register). A nullával végzett
bitenkénti VAGY változatlanul továbbadja a másik operandust, ezért
alkalmas másolásra.
A veremmutatót tartalmazó A64 MOV alakok külön esetek:
például a mov X4, SP mögött ADD áll nulla
közvetlen operandussal. Ezekre nem szabad automatikusan ráhúzni az
ORR magyarázatát.
Fontos: a GNU assembler AArch64 célján a
mov X3, #konstans nem általános, több utasítást előállító
konstansbetöltő makró. Ha nincs megfelelő egyutasításos kódolás, az
assembler hibát jelez.
A logikai immediate kódolása sem tetszőleges. Röviden: a szó egy 2, 4, 8, 16, 32 vagy 64 bites blokk ismétléséből állhat; a blokk körbeforgatott, összefüggő egyesbitsorozatot és legalább egy nullát tartalmaz. A 32 bites alak legfeljebb 32 bites blokkot használ. Emiatt a fenti ismétlődő maszk kódolható, de egy tetszőleges hexadecimális érték általában nem. Részletesen a Logikai alapműveletek és bitmaszkolás tárgyalja ezt.
MOVZ,
MOVK, MOVN: építkezés 16 bites mezőkbőlA széles közvetlenérték-mozgatások (move wide immediate) 16
bites közvetlen mezőt tartalmaznak. Ennek megengedett tartománya
0x0000…0xffff.
A mező helyét az opcionális lsl #s módosító választja
ki:
| A64 célregiszter | Megengedett s értékek, bitben |
|---|---|
Wn, 32 bites művelet |
0, 16 |
Xn, 64 bites művelet |
0, 16, 32, 48 |
Itt az lsl az operandus módosítója, nem külön
végrehajtott utasítás. Elhagyása nulla eltolást jelent. A
kódolás kétbites mezőválasztót használ; a 32 bites változatban a felső
két választás nem megengedett.
Legyen V a közvetlen 16 bites mező s bittel
balra tolva. A műveleti szélesség 32 vagy 64 bit.
| A64 utasítás | Hatás a műveleti szélességen belül |
|---|---|
MOVZ |
Beírja V értékét; minden más bit nulla lesz. |
MOVK |
Csak a kiválasztott 16 bites mezőt cseréli ki; a többi bit megmarad. |
MOVN |
A teljes szélességen bitenként invertálja V
értékét. |
A nevekben a Z a nullázásra (zero), a
K a megtartásra (keep), az N a
bitenkénti negálásra (NOT) utal.
Két fontos következmény:
MOVK eredménye függ a célregiszter korábbi
tartalmától. Ismeretlen kezdőértékre építve nem kapunk megbízható
konstanst.MOVN nem csak a kiválasztott 16 bitet
invertálja. A többi, előzőleg nulla bitből is egyes lesz.Például:
// A64 ISA, AArch64: önálló szemléltető utasítások.
movn X5, #0
movn W6, #0Az első eredménye az A64 X5 = 0xffffffffffffffff. Ez 64
bites előjeles, kettes komplemens értelmezésben −1.
A második eredménye az A64 W6 = 0xffffffff, de a teljes
X6 = 0x00000000ffffffff. Ennek oka az
általános A64 szabály: egy Wn célregiszter írása nullázza a
hozzá tartozó Xn felső 32 bitjét.
Ez az A64 MOVK Wn, ... esetében is igaz. A „megtartás”
csak a 32 bites műveleti szélesség érintetlen mezőire vonatkozik.
Az itt tárgyalt A64 mozgatások nem módosítják az NZCV állapotjelzőket.
Legyen C egy előjel nélküli, 64 bites, dimenzió nélküli
egész. Bontsuk négy 16 bites mezőre:
A j mezőindex értéke 0–3; az egyes eltolások bitszámot
jelentenek. A mezők kinyerése C-szemlélettel: jobbra tolás, majd
maszkolás 0xffff értékkel.
A 0x0123456789abcdef konstans felépítése:
| Lépés | A64 utasítás | Várt X3 tartalom |
|---|---|---|
| 1 | movz X3, #0xcdef |
0x000000000000cdef |
| 2 | movk X3, #0x89ab, lsl #16 |
0x0000000089abcdef |
| 3 | movk X3, #0x4567, lsl #32 |
0x0000456789abcdef |
| 4 | movk X3, #0x0123, lsl #48 |
0x0123456789abcdef |
Az első utasítás ismert alapállapotot hoz létre, a továbbiak különálló mezőket írnak át. Így legfeljebb négy utasítás elegendő bármely 64 bites konstanshoz.
Ez nem állítás minden konstans minimális utasításszámáról. A nulla
mezőket nem kell külön beírni a MOVZ után, sok egyesbit
esetén pedig a MOVN adhat jobb kezdőállapotot. Például az
A64 movn X3, #0xedcb önmagában előállítja a
0xffffffffffff1234 bitmintát.
Munkakönyvtár: ~/arm-labor/a64/a014/ —
az alfejezet minden laborfájlja ide kerül, és minden parancs innen
futtatandó (a könyvtárrendet a Labor-alapkörnyezet alfejezet írja le).
mkdir -p ~/arm-labor/a64/a014
cd ~/arm-labor/a64/a014A következő teljes program nem használ C-futtatókörnyezetet. A
konstansok adatok, nem memóriacímek. A lab_check
szimbólumnál debuggerrel ellenőrizhető az eredmény.
A fájl neve legyen lab-mov.s:
// A64 ISA, AArch64, I. rész; GNU as; little-endian cél.
.section .text
.p2align 2
.global _start
.global lab_check
.type _start, %function
_start:
movz X3, #0xcdef
movk X3, #0x89ab, lsl #16
movk X3, #0x4567, lsl #32
movk X3, #0x0123, lsl #48
mov X4, X3
movn X5, #0
movn W6, #0
mov W7, W4
mov X9, #0x00ff00ff00ff00ff
ldr X10, =0x0123456789abcdef
lab_check:
mov X0, #0
mov X8, #93
svc #0
.size _start, .-_start
.ltorg
.section .note.GNU-stack,"",%progbitsA GNU as AArch64 .p2align 2 direktívája
bájtos igazítást kér. A szekció- és szimbólumdirektívák a tárgyfájlt
írják le; nem végrehajtandó műveletek.
Build és futtatás — cél: qemu-user, AArch64 little-endian; host: Ubuntu 24.04 x86_64.
aarch64-linux-gnu-as -EL -march=armv8-a \
-o lab-mov.o lab-mov.s &&
aarch64-linux-gnu-ld -EL -e _start \
-o lab-mov lab-mov.o &&
qemu-aarch64 ./lab-mov
printf 'Kilépési állapot: %d\n' "$?"Levezetett, várt eredmény: a program nem ír a
szabványos kimenetre; a shell Kilépési állapot: 0 sort ír
ki. A nulla kilépés önmagában még nem igazolja a konstansok helyességét:
a program nem végez önellenőrzést.
A tényleges kódolások megtekintése:
# Cél: a qemu-user számára készült AArch64 ELF statikus vizsgálata.
aarch64-linux-gnu-objdump -d -M no-aliases lab-movA no-aliases kapcsolóval a regisztermásolás mögötti A64
ORR, illetve az egyszerű immediate-mozgatások mögötti
kódolás látható. A megjelenítés pontos formája binutils-verziótól
függhet.
A regiszterellenőrzéshez az első terminálban:
# Cél: qemu-user, AArch64; várakozás GDB-kapcsolatra.
qemu-aarch64 -g 1234 ./lab-movA második terminálban:
# Cél: a fenti qemu-user folyamat A64 regisztereinek ellenőrzése.
gdb-multiarch -q ./lab-mov \
-ex 'set architecture aarch64' \
-ex 'target remote :1234' \
-ex 'break lab_check' \
-ex 'continue' \
-ex 'info registers x3 x4 x5 x6 x7 x9 x10' \
-ex 'continue' \
-ex 'quit'Várt állapot a töréspontnál; nem ténylegesen mért laboreredmény:
| A64 regiszter | Várt bitminta | Ellenőrzött tulajdonság |
|---|---|---|
X3 |
0x0123456789abcdef |
Négymezős felépítés |
X4 |
0x0123456789abcdef |
Teljes regisztermásolás |
X5 |
0xffffffffffffffff |
64 bites MOVN |
X6 |
0x00000000ffffffff |
32 bites írás felsőfél-nullázása |
X7 |
0x0000000089abcdef |
W nézetű másolás |
X9 |
0x00ff00ff00ff00ff |
Logikai immediate |
X10 |
0x0123456789abcdef |
Assembler-segített betöltés |
LDR =konstans előzeteseA labor A64 ldr X10, =0x0123456789abcdef sora valódi
assembler-pszeudoutasítás (pseudo-instruction). Az
egyenlőségjeles alak nem gépi operandusformátum.
A GNU assembler AArch64 célján ez a következő elvet használja:
LDR utasítást állít
elő.A GNU as AArch64 .ltorg direktívája az aktuális helyre
bocsátja ki a függőben lévő konstanstárolót. A példában ez a kilépés
után található, így a normál vezérlés nem fut bele az adatba.
A valódi A64 LDR literal kódolás előjeles, 19 bites
mezőt használ, négyszeres skálázással: a bájteltolás −1048576 és 1048572
közötti, négybájtos lépésekben. Ezért a tárolónak elérhető távolságban
kell lennie.
Az egyetlen forrássor tehát itt egy betöltőutasítást és külön
tárolt adatot jelent. Nem következik belőle, hogy gyorsabb a
MOVZ/MOVK sorozatnál. Utóbbi nem végez
adatmemória-olvasást; előbbi igen. Teljesítményt ebből mérés nélkül nem
állítunk, QEMU pedig nem garantál hardverhű ciklusszámot.
Az A64 minden utasításformája saját, rögzített szélességű mezőben tárolja a közvetlen operandust vagy eltolást. A könyv későbbi alfejezetei nem sorolják fel újra ezeket a korlátokat, hanem erre a táblázatra hivatkoznak, és csak a példára jellemző eltérést emelik ki. A részletes levezetést az utolsó oszlopban megadott alfejezet adja.
| Utasításcsalád | Mező | Megengedett érték | Részletesen |
|---|---|---|---|
MOVZ, MOVN, MOVK (és a
MOV #konstans alias) |
16 bit, előjel nélküli + lsl #s |
0…0xffff; s = 0, 16 (Wn),
illetve 0, 16, 32, 48 (Xn) |
ez az alfejezet |
ADD, SUB, ADDS,
SUBS; aliasok: CMP, CMN |
12 bit, előjel nélküli + opcionális lsl #12 |
0…4095, vagy ennek 4096-szorosa
(4096…16773120, 4096-os lépésben) |
Egész aritmetika |
AND, ORR, EOR,
ANDS; alias: TST, bitmaszkos
MOV |
logikai (bitmaszk-) immediate | ismétlődő, forgatott, összefüggő egyesbit-blokk; a csupa 0 és a csupa 1 nem kódolható | Logikai alapműveletek |
LSL, LSR, ASR,
ROR immediate; eltolt regiszteroperandus |
eltolásszám | 0–31 (Wn), 0–63 (Xn) |
Shift és rotate |
UBFX, SBFX, BFI |
pozíció és szélesség, 6–6 bit | a mező a műveleti szélességen belül marad (Wn:
0–31) |
Bitfield-utasítások |
CCMP, CCMN immediate |
5 bit + 4 bites #nzcv |
0…31 |
NZCV-összefoglaló |
MUL, MADD, SDIV,
UDIV, CSEL |
nincs immediate-forma | a konstanst előbb regiszterbe kell tölteni | Egész aritmetika, CSET/CSEL |
LDR/STR unsigned offset |
12 bit, a hozzáférés méretével skálázva | 0…4095 × méret bájt, a méret többszöröse (pl.
Wn: 0–16380, Xn: 0–32760) |
Load/store címzési módok |
LDUR/STUR; pre- és post-index |
9 bit, előjeles, skálázatlan | −256…255 bájt |
Load/store címzési módok |
LDP/STP |
7 bit, előjeles, skálázva | Wn: −256…252, Xn:
−512…504 bájt |
Load/store címzési módok |
LDR literal (az LDR =konstans mögött
is) |
19 bit, előjeles, ×4 | −1048576…1048572 bájt (±1 MiB) |
ez az alfejezet |
ADR |
21 bit, előjeles, bájtos | −1048576…1048575 bájt (±1 MiB) |
Load/store címzési módok |
ADRP |
21 bit, előjeles, 4 KiB-os lapokban | ±4 GiB; az alsó 12 bitet ADD …, :lo12: adja |
Load/store címzési módok |
B, BL |
26 bit, előjeles, ×4 | ±128 MiB | Elágazás |
B.cond, CBZ, CBNZ |
19 bit, előjeles, ×4 | ±1 MiB | Elágazás |
TBZ, TBNZ |
14 bit, előjeles, ×4; bitszám 6 bit | ±32 KiB; bitszám 0–63 | Elágazás |
SVC |
16 bit, előjel nélküli | 0…65535; Linuxon mindig #0, a hívásszám az
X8-ban van |
Linux syscall-konvenció |
Ha egy konstans vagy eltolás nem fér bele a mezőbe, az assembler
hibát jelez (vagy — LDR/STR esetén — más
kódolást, például LDUR-t választhat). A megoldás ilyenkor a
konstans regiszterbe töltése (MOVZ/MOVK
sorozat vagy LDR =konstans), illetve a cím előzetes
kiszámítása.
„A MOV nem fogadja el a számot.”
Először azt vizsgáljuk, belefér-e egyetlen támogatott kódolásba.
Kontrollált negatív próba:
# Cél: AArch64/qemu-user labor; szándékos assemblerhiba, nincs futtatás.
printf '%s\n' '.text' 'mov X3, #0x0123456789abcdef' \
> lab-mov-bad.s
aarch64-linux-gnu-as -EL -march=armv8-a \
-o lab-mov-bad.o lab-mov-bad.s
printf 'Assembler kilépési állapota: %d\n' "$?"A várt eredmény nem nulla állapot és olyan diagnosztika, hogy az immediate nem mozgatható egyetlen utasítással. Pontos szövege verziófüggő. Linkelésre és futtatásra ennél a negatív próbánál nem kerül sor.
Eltűnik egy korábban beírt mező. Hipotézis:
MOVK helyett újabb MOVZ szerepel. GDB-ben
utasításonként léptessünk, és keressük meg az első hibás
regiszterállapotot. Csak ezt az utasítást javítsuk, majd ismételjük meg
az ellenőrzést.
Nulla lesz a felső 32 bit. Keressünk Wn
célregiszteres írást. A MOVK Wn sem őrzi meg az
Xn felső felét.
Érvénytelen eltolás. A 16 bites mező nem helyezhető
tetszőleges bitpozícióra: például az A64 lsl #8 nem
megengedett ezeknél az utasításoknál. A Wn alakhoz a
lsl #32 sem használható.
(magyarázó, alapszint — cél:
megértés-ellenőrzés) Miért nem megfelelő magyarázat, hogy „a
regiszter 64 bites, tehát a MOV bármely 64 bites számot
elfogad”? Add meg az A64 movn X2, #0x1234, lsl #16
eredményét is.
(implementációs, középszint — cél: rutinépítés)
Egészítsd ki a labort a lab_check előtt. Az A64
X11 kapja a 0xfedcba9876543210 értéket
MOVZ/MOVK segítségével, az X12
pedig a 0xffffffffffff1234 értéket egyetlen utasítással.
Ellenőrizd mindkettőt GDB-ben.
(hibakeresési, középszint — cél: rutinépítés) A
labor második A64 utasításában cseréld a MOVK mnemonicot
MOVZ-re, a többi operandust hagyd változatlanul. Jósolj
végső X3 értéket, majd keresd meg az első eltérést
utasításonkénti léptetéssel. Egyetlen módosítással javítsd a
hibát.
1. megoldás — Segítség. A regiszter szélessége és az
UTASÍTÁSKÓDBAN tárolható mező mérete két külön korlát — melyik szabja
meg, mit tud egyetlen MOVN ténylegesen beírni? A megadott
MOVN előbb egy 16 bites mezőt helyez el eltolva, majd a
TELJES 64 bitet invertálja.
1. megoldás — Teljes megoldás. A regiszter
szélessége a tárolható értéket határozza meg, nem az utasítás közvetlen
mezőjének méretét. A megadott A64 MOVN először a
0x0000000012340000 mintát képezi, majd 64 biten invertálja.
Eredménye 0xffffffffedcbffff.
2. megoldás — Segítség. Az X11-hez négy
MOVZ/MOVK lépés kell (ugyanaz a minta, mint a
fejezet 0x0123456789abcdef példájában). Az X12-nél nézd
meg: a 0xffffffffffff1234 mintázat egyetlen
MOVN bitenkénti negáltjaként kódolható-e.
2. megoldás — Teljes megoldás. A laborba illeszthető A64 kód:
// A64 ISA, AArch64: beszúrás a lab_check címke elé.
movz X11, #0x3210
movk X11, #0x7654, lsl #16
movk X11, #0xba98, lsl #32
movk X11, #0xfedc, lsl #48
movn X12, #0xedcbAz első sorozat minden 16 bites mezőt kitölt. A második a
0xedcb bitenkénti komplementereként 0x1234
alsó mezőt és csupa egyes felső biteket állít elő. Ugyanazzal a builddel
futtatható; a GDB regiszterlistájához add hozzá az x11 x12
neveket.
3. megoldás — Segítség. A MOVZ mindig
NULLÁZZA a teljes műveleti szélességet a beírt mezőn kívül — mi történik
emiatt az ELŐZŐ utasítás által már beírt mezővel, ha a másodikat is
MOVZ-re cseréled?
3. megoldás — Teljes megoldás. A végső A64
X3 = 0x0123456789ab0000. A második utasítás lenullázza az
első által beírt 0xcdef mezőt. A GDB-ben
_start törésponttól két stepi után már
0x0000000089ab0000 látható az elvárt
0x0000000089abcdef helyett. A javítás kizárólag a második
mnemonic visszaállítása MOVK-re; utána új build és azonos
ellenőrzés szükséges.
Az A64 konstansbetöltésnél külön kell választani a regiszter
szélességét és az utasításban rendelkezésre álló kódolómezőket. A
MOV kényelmes alias, de nem ígéret tetszőleges konstans
egyutasításos előállítására. A MOVZ ismert, nullázott
alapot ad, a MOVK kiválasztott mezőket cserél, a
MOVN teljes műveleti szélességen invertál. Tetszőleges 64
bites érték legfeljebb négy ilyen utasítással felépíthető. A
Wn célregiszteres írás mindig nullázza az Xn
felső felét. Az LDR =konstans kényelme mögött külön tárolt
adat és memóriaolvasás áll; működését disassemblyvel és
regiszterellenőrzéssel igazoljuk. Az A64 immediate-mezők korlátait az
alfejezet hivatkozható összefoglaló táblázata gyűjti egybe; a könyv
későbbi alfejezetei erre utalnak vissza.
A linkek és a dokumentumverziók közös állapota az Olvasói útmutató forrásállapot-táblájában van; itt csak az alfejezet saját megállapításai szerepelnek.
MOV, MOVZ, MOVK,
MOVN, ORR és LDR leírásaihoz.
Ellenőrizve: a link él (a fejezetszám és a kiadásbetű
az Arm-oldalak közös korlátja miatt nem ellenőrzött)..ltorg. Tartalmilag ellenőrizve: a
jelenlegi kiadás binutils 2.47.Ellenőrzési hiányok, amelyek a fenti ellenőrzés után is ténylegesen fennmaradnak: laborfuttatás nem történt. A regiszterértékek levezetett várakozások. Az Ubuntu 24.04 telepített csomagverzióit, az assembler pontos hibaüzenetét és az objdump megjelenítését a hallgatói laborban kell rögzíteni.