Egy feldolgozófüggvény gyakran több összetartozó eredményt számít: például összeget és különbséget, minimumot és maximumot, vagy feldolgozott elemszámot és hibakódot. C-ben ezt struktúra-visszaadással vagy kimenő mutatóparaméterekkel (output parameters) fejezhetjük ki. Assemblyben azonban pontosan meg kell állapodnunk arról, melyik eredmény hol jelenik meg.
Az AAPCS64 ezt a visszaadott típus alapján, a fordításkor rögzíti. A hívó és a hívott nem egyeztet futás közben a struktúra méretéről.
Ebben az alfejezetben kizárólag A64 ISA, AArch64 Linux, little-endian, LP64 környezetben dolgozunk. A példák normál elrendezésű, egész mezőkből álló C-struktúrákat használnak. Minden assemblyutasítás és regiszter ebbe az A64-környezetbe tartozik; az assemblydirektívák a GNU assembler AArch64 céltárgyára vonatkoznak. Előfeltétel Az AAPCS64 regiszterszerződés ismerete.
A közvetlen eredményátadás (direct result return) során a függvény eredménye regiszterekben érkezik. A közvetett eredményátadás (indirect result return) esetén a hívó eredménytárolót biztosít, a hívott pedig ebbe ír.
A most vizsgált, egész mezőkből álló struktúrákra:
A visszaadott struktúra sizeof értéke |
Eredmény helye, A64/AAPCS64 |
|---|---|
| 1–8 bájt | X0 megfelelő bájtjai |
| 9–16 bájt | X0, majd X1 megfelelő bájtjai |
| 16 bájtnál több | Memória; az eredménytároló címét a hívó X8-ban adja
át |
A határ tehát 16 bájt, beleértve az igazítás miatti kitöltést (padding). Nem a mezők száma és nem pusztán a mezőméretek összege dönt.
Fontos hatókörkorlát: a „16 bájt felett memória” nem
minden AAPCS64-típusra érvényes általános szabály. A homogén
lebegőpontos és vektoraggregátumok (HFA, HVA) külön besorolást
kapnak; például egy négy double mezőből álló homogén
struktúra 32 bájtos, mégis lebegőpontos regiszterekben térhet vissza.
Ezeket később tárgyaljuk. Itt egész típusú aggregátumokra alkalmazzuk a
táblázatot.
A kis struktúra memóriaképe kerül az A64 eredményregiszterekbe, nem általánosan „egy mező egy regiszterbe”.
Little-endian profilban egy
/* AArch64 Linux, LP64, little-endian. */
struct lab_words {
uint32_t first;
uint32_t second;
};típusú, 8 bájtos eredmény mindkét mezője az A64 X0
regiszterben fér el: first az alsó, second a
felső 32 biten. A definíció használatához természetesen
<stdint.h> szükséges.
Ezzel szemben két uint64_t mező esetén a struktúra 16
bájtos: az első mező A64 X0-ba, a második A64
X1-be kerül.
A kitöltőbájtokból és a regiszter kihasználatlan részeiből ne
következtessünk értékre. Az ellenőrzés a mezőket hasonlítsa össze, ne a
teljes struktúrát memcmp segítségével.
Nagy struktúránál a hívó:
X8 regiszterbe teszi;Az A64 X8 különleges szerepe miatt egy nagy struktúrát
visszaadó, két egész paraméteres függvény első két argumentuma továbbra
is X0 és X1. A rejtett eredménymutató
nem tolja el őket.
A hívott nem köteles az eredménytároló címét A64 X0-ban
visszaadni. Az A64 X8 pedig nem megőrzött regiszter: ha a
hívott további függvényt hív, és később még szüksége lesz a címre, saját
magának kell megőriznie.
Munkakönyvtár: ~/arm-labor/a64/a031/ —
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/a031
cd ~/arm-labor/a64/a031Három függvényt készítünk:
lab_pair: két eredmény visszaadása 16 bájtos
struktúrában;lab_triple: három eredmény visszaadása 24 bájtos
struktúrában;lab_pair_out: két eredmény kiírása explicit
mutatóparaméteren keresztül.Minden számítás előjel nélküli, 64 bites; az összeadás, kivonás és a későbbi szorzás eredménye modulo értendő.
Az interfészek szerződése:
| Függvény | Bemenet, A64 | Eredmény, A64 |
|---|---|---|
lab_pair(a, b) |
X0 = a, X1 = b |
X0 = a+b, X1 = a-b |
lab_triple(a, b) |
X0 = a, X1 = b,
X8 = eredménytároló |
24 bájt az eredménytárolóban |
lab_pair_out(a, b, out) |
X0 = a, X1 = b, X2 = out |
16 bájt az out által jelölt tárolóban |
Az explicit kimenő mutató közönséges harmadik argumentum, ezért A64
X2-ben érkezik, nem X8-ban. Mindkét memóriába
író változat érvényes, kellően igazított tárolót igényel; ezt a C-teszt
helyi objektumai biztosítják.
A lab-return.S fájl teljes tartalma:
// A64 ISA, ARMv8-A; GNU as, AArch64 Linux, little-endian.
.arch armv8-a
.text
.p2align 2
.global lab_pair
.type lab_pair, %function
lab_pair:
add X2, X0, X1
sub X1, X0, X1
mov X0, X2
ret
.size lab_pair, .-lab_pair
.global lab_triple
.type lab_triple, %function
lab_triple:
add X2, X0, X1
sub X3, X0, X1
eor X4, X0, X1
stp X2, X3, [X8]
str X4, [X8, #16]
ret
.size lab_triple, .-lab_triple
.global lab_pair_out
.type lab_pair_out, %function
lab_pair_out:
add X3, X0, X1
sub X4, X0, X1
stp X3, X4, [X2]
ret
.size lab_pair_out, .-lab_pair_out
.section .note.GNU-stack,"",@progbitsMindhárom levélfüggvény: nem hív további függvényt, nem módosít megőrzendő regisztert, ezért nincs szüksége veremkeretre.
Az első függvényben az A64 X2 ideiglenesen megőrzi az
összeget. Ez szükséges, mert a különbséghez még kell az eredeti
X0 bemenet. Ha előbb közvetlenül X0-ba írnánk
az összeget, elveszítenénk ezt a bemenetet.
Az A64 STP két 64 bites regisztert tárol egymást követő
8 bájtos helyekre. A használt eltolásos alak előjeles, 7 bites, 8-cal
skálázott eltolást kódolhat: −512 és 504 bájt között, 8 bájtos
lépésekben. Itt az eltolás nulla.
Az A64 STR X4, [X8, #16] előjel nélküli, 12 bites, 8-cal
skálázott eltolást használ: 0–32760 bájt között, 8 bájtos lépésekben. A
16 bájtos eltolás tehát kódolható.
A GNU AArch64 .p2align 2 direktíva
bájtos igazítást kér. A .size a függvény gépi kódjának
méretét rögzíti, nem a C-visszatérési típus
méretét.
A lab-return-main.c fájl:
/* AArch64 Linux, LP64, little-endian. */
#include <stddef.h>
#include <stdint.h>
#include <inttypes.h>
#include <stdio.h>
struct lab_pair_value {
uint64_t sum;
uint64_t difference;
};
struct lab_triple_value {
uint64_t sum;
uint64_t difference;
uint64_t xor_value;
};
extern struct lab_pair_value
lab_pair(uint64_t a, uint64_t b);
extern struct lab_triple_value
lab_triple(uint64_t a, uint64_t b);
extern void
lab_pair_out(uint64_t a, uint64_t b, struct lab_pair_value *out);
_Static_assert(sizeof(struct lab_pair_value) == 16, "pair size");
_Static_assert(offsetof(struct lab_pair_value, sum) == 0, "sum offset");
_Static_assert(offsetof(struct lab_pair_value, difference) == 8,
"difference offset");
_Static_assert(sizeof(struct lab_triple_value) == 24, "triple size");
_Static_assert(offsetof(struct lab_triple_value, sum) == 0, "sum offset");
_Static_assert(offsetof(struct lab_triple_value, difference) == 8,
"difference offset");
_Static_assert(offsetof(struct lab_triple_value, xor_value) == 16,
"xor offset");
int main(void)
{
const uint64_t inputs[][2] = {
{20, 7},
{0, 1},
{UINT64_MAX, 1}
};
for (size_t i = 0; i < sizeof inputs / sizeof inputs[0]; ++i) {
uint64_t a = inputs[i][0];
uint64_t b = inputs[i][1];
struct lab_pair_value p = lab_pair(a, b);
struct lab_triple_value t = lab_triple(a, b);
struct lab_pair_value out = {0, 0};
lab_pair_out(a, b, &out);
if (p.sum != a + b || p.difference != a - b ||
t.sum != a + b || t.difference != a - b ||
t.xor_value != (a ^ b) ||
out.sum != a + b || out.difference != a - b) {
fprintf(stderr, "hiba: eset %zu\n", i);
return 1;
}
if (i == 0) {
printf("sum=%" PRIu64 " difference=%" PRIu64
" xor=%" PRIu64 "\n",
t.sum, t.difference, t.xor_value);
}
}
puts("ok: 3 eset, 3 visszaadasi mod");
return 0;
}A statikus állítások az assembly által feltételezett méreteket és mezőeltolásokat ellenőrzik. Nem helyettesítik a közös típusdeklarációt, de megakadályozzák, hogy egy észrevétlen struktúramódosítás hibás bináris szerződést hozzon létre.
Cél: qemu-user, qemu-aarch64; host: Ubuntu 24.04
x86_64. A statikus linkeléshez az AArch64 cross libc fejlesztői
állományai is szükségesek, az alapkörnyezet
részeként.
# Assemble: A64 objektum.
aarch64-linux-gnu-gcc -march=armv8-a -g \
-c lab-return.S -o lab-return.o
# Compile: AArch64 Linux C-objektum.
aarch64-linux-gnu-gcc -march=armv8-a -std=c11 -O0 -g \
-Wall -Wextra -Werror -fno-pie \
-c lab-return-main.c -o lab-return-main.o
# Link: statikus AArch64 Linux ELF.
aarch64-linux-gnu-gcc -static -no-pie \
lab-return-main.o lab-return.o -o lab-return
# Run: qemu-user.
qemu-aarch64 ./lab-return
printf 'kilepesi allapot=%d\n' "$?"Levezetett, várt kimenet; itt nem végrehajtott mérés:
sum=27 difference=13 xor=19ok: 3 eset, 3 visszaadasi modkilepesi allapot=0Az első hívássorozat után:
| Ellenőrzési pont | Várt állapot, A64 |
|---|---|
lab_pair, közvetlenül visszatéréskor |
X0 = 27, X1 = 13 |
lab_triple, az eredménytároló 0 bájtos eltolásán |
64 bites előjel nélküli 27 |
| Ugyanott, 8 bájtos eltoláson | 64 bites előjel nélküli 13 |
| Ugyanott, 16 bájtos eltoláson | 64 bites előjel nélküli 19 |
A másik két teszt az előjel nélküli alulcsordulást és körbefordulást is ellenőrzi. Ezek C-ben definiált műveletek; nem szabad előjeles túlcsordulási példaként értelmezni őket.
Ha a C-deklaráció 24 bájtos eredményt ír elő, de az assembly csak A64
X0 és X1 értékét állítja be, a hívó által
kijelölt tároló nem kapja meg az eredményt.
Fordított esetben a hívott A64 X8-on keresztül próbálhat
írni, miközben a hívó nem adott át érvényes eredménycímet. Ennek
következménye lehet memóriahiba vagy memóriarongálás.
A linker rendszerint csak a szimbólumnevet egyezteti; ettől a C-típusok még nem lesznek kompatibilisek. A sikeres linkelés nem ABI-ellenőrzés.
Két uint32_t mező 8 bájtos struktúrája nem az A64
W0 és W1 regiszterben tér vissza. Mindkét mező
az A64 X0 regiszter része. Ez tipikus hiba, amikor a
fejlesztő mezőszámból következtet regiszterszámra.
A példában lab_pair_out explicit harmadik paramétere A64
X2, míg lab_triple rejtett eredménycíme A64
X8. Ugyanaz a memóriaírási művelet eltérő interfészből
származhat.
Cél: a qemu-userhez épített AArch64 ELF vizsgálata. A következő parancsok nem futtatják a célprogramot:
aarch64-linux-gnu-objdump -d --disassemble=lab_pair ./lab-return
aarch64-linux-gnu-objdump -d --disassemble=lab_triple ./lab-returnA64 lab_pair bemeneteinek és eredményeinek követéséhez
az első terminálban:
# Cél: qemu-user; várakozás a debugger kapcsolódására.
qemu-aarch64 -g 1234 ./lab-returnMásodik terminálban:
# Cél: az előző qemu-user folyamat A64 állapotának vizsgálata.
gdb-multiarch -q -batch ./lab-return \
-ex 'target remote :1234' \
-ex 'break lab_pair' \
-ex 'continue' \
-ex 'info registers x0 x1' \
-ex 'si 3' \
-ex 'info registers x0 x1 x2' \
-ex 'detach'A belépéskor A64 X0 = 20, X1 = 7 várható.
Három utasítás után, még az A64 RET előtt:
X0 = 27, X1 = 13, X2 = 27. A
leválasztás után a program várhatóan befejeződik, és 0 állapottal lép
ki.
A hibakeresési ciklus: tünet → feltételezett szerződés- vagy adatfüggőségi hiba → regiszteradatok → egyetlen javítás → teljes teszt újrafuttatása. Az emuláció itt szemantikai ellenőrzést szolgál; hardveres ciklusszámot nem állapítunk meg.
(magyarázó, alapszint — cél:
megértés-ellenőrzés) Hol tér vissza a két uint32_t
mezős, a két uint64_t mezős és a három
uint64_t mezős struktúra? Az első esetben milyen
hexadecimális érték lesz az A64 eredményregiszterben, ha a mezők értéke
rendre 1 és 2?
(implementációs, középszint — cél: rutinépítés)
Módosítsd lab_triple harmadik eredményét bitenkénti kizáró
vagy helyett az a * b előjel nélküli szorzat alsó 64
bitjére. Ne változtasd meg a struktúra méretét vagy a visszaadási
mechanizmust. Igazítsd hozzá a C-tesztet is.
(hibakeresési, középszint — cél: rutinépítés) A
lab_pair számítását valaki az alábbi A64-részletre
cserélte. Milyen eredményt kapsz az első tesztben? Ellenőrizd a
hipotézist debuggerrel, majd javítsd az adatfüggőséget.
// Szándékosan hibás A64 függvénytörzs.
add X0, X0, X1
sub X1, X0, X1
ret1. megoldás — Segítség. Nézd meg a fejezet első táblázatát (1–8 bájt / 9–16 bájt / 16 bájt felett), majd gondold át little-endian elrendezésben, hogy egy 8 bájtos regiszteren belül az ALACSONYABB című mező kerül-e az alsó vagy a felső félre.
1. megoldás — Teljes megoldás. A 8 bájtos eredmény
A64 X0-ban, a 16 bájtos eredmény A64
X0/X1-ben, a 24 bájtos eredmény pedig az A64
X8-ban átadott címen tér vissza. Little-endian profilban az
első struktúra eredménye X0 = 0x0000000200000001: az első
mező foglalja el az alsó 32 bitet.
2. megoldás — Segítség. Az A64 MUL-nak
ugyanaz a célregiszter/forrás-mintája van, mint az
ADD/SUB/EOR-nak — csak a
mnemonicot és a szemantikát kell cserélni, a struktúra-elrendezést
nem.
2. megoldás — Teljes megoldás. A meglévő A64
EOR helyére ez kerül:
// A64: a 64 bites szorzat alsó 64 bitje.
mul X4, X0, X1A C-forrásban a xor_value mezőnevet következetesen
product névre cseréljük, az ellenőrzés pedig
t.product != a * b legyen. A kiírás feliratát is
módosítjuk. Az első eset harmadik eredménye 140. A struktúra továbbra is
24 bájtos, ezért az A64 X8-alapú szerződés változatlan. A
korábbi buildlépéseket újra végre kell hajtani.
3. megoldás — Segítség. A hibás verzió az összeget
ÚJRA X0-ba írja, mielőtt a különbséghez szüksége lenne az
EREDETI X0 bemenetre — melyik érték vész el emiatt a
második sorban?
3. megoldás — Teljes megoldás. Az első A64 utasítás
után X0 = 27; a második ezért már 27 - 7
értékét számítja. Az eredmény X0 = 27,
X1 = 20, nem pedig 27 és 13. A C-teszt az első esetnél
hibát jelez, és 1 állapottal lép ki. A debuggerben két utasítást
léptessünk, hogy még a visszatérés előtt lássuk a hibát. A kidolgozott
példa A64 X2-t használó változata megőrzi a szükséges
bemenetet, ezért helyes.
A visszatérési szerződést a függvény típusa határozza meg, nem a
függvénytörzs választja futás közben. A vizsgált egész mezős struktúrák
legfeljebb 16 bájtig A64 X0 és szükség esetén
X1 segítségével térnek vissza; nagyobb eredménynél a hívó
A64 X8-ban adja át a tároló címét. A kitöltés beleszámít a
méretbe, a mezők pedig nem feltétlenül külön regisztereket jelentenek.
Az explicit kimenő mutató közönséges paraméter. A helyes aritmetika
mellett a típusdeklaráció, a mezőeltolás és az eredmény helye is
ellenőrzendő.
X8-ban átadott eredménycím.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.
X8 szerepe és HFA/HVA-kivételek.
Tartalmilag ellenőrizve: a jelenlegi kiadás 2025Q4
(2026-01-23), Release minőségű (az SME-részek Beta).Ellenőrzési hiányok, amelyek a fenti ellenőrzés után is ténylegesen fennmaradnak: a kódot itt nem fordítottuk és nem futtattuk. Az Ubuntu 24.04 környezet konkrét csomagverzióit, a statikus cross libc elérhetőségét és a debugger-munkamenetet a laborban kell validálni. A megadott kimenetek és regiszterértékek levezetett elvárások, nem mért eredmények.