Az Assembly függvény hívása C-ből és C hívása
assemblyből alfejezet egyik fő megállapítása az volt, hogy a sikeres
linkelés semmit nem ellenőriz. A C fordító a
lab_kernel(p, n) híváskor legfeljebb annyit tud, amit a
fejlécbeli prototípusból kiolvas: a pointer és a hossz futásidejű
értékéről, a fogadó fél tényleges kapacitásáról, a pufferek átfedéséről
nem tud semmit. Amint a vezérlés átlép a saját kezűleg írt assembly
modulba, megszűnik a típusrendszer, a Fortify, az automatikus konverzió,
sőt a -Wall is.
A verem-túlcsordulás mechanizmusa assembly
szinten alfejezet pontosan ezt a rést használta ki demonstrációs
célból: egy strb ciklus, amely egy fix méretű keretbeli
puffert írt, miközben a hossz a hívótól, ellenőrizetlenül érkezett. A ROP-lánc felismerése és miértje pedig megmutatta,
mi lesz ebből a következmény: a felülírt mentett visszatérési cím
kontrolláram-eltérítési primitív.
A mérnöki feladat ebben az alfejezetben tehát nem az, hogy felismerjük a hibát (azt már megtettük), hanem hogy megtervezzük a határt, ahol a hibát meg lehet fogni: a hívó és a hívott közötti szerződést rögzítjük, és a hívott fél a saját kapacitásához köti a kapott hosszt, mielőtt egyetlen bájtot is írna.
Definíció. A C–assembly interop-határ szerződése négy dimenzió együttes rögzítése, amelyet a két oldalnak azonos módon kell értelmeznie:
A négy dimenzióból az első kettő az, amit a legtöbbször
elhanyagolunk. Fontos mérnöki belátás: a pointer érvényessége
általánosan nem eldönthető C-ben. A p != NULL csak
a legdurvább esetet fogja meg; egy 0x1000-szerű, nem
leképezett cím éppúgy átcsúszik, mint egy olyan, amely a szomszéd tömbbe
mutat. Ezért a működő stratégia nem az, hogy „megvizsgáljuk,
érvényes-e a pointer”, hanem hogy a kapott hosszt a fogadó fél
saját, fordítási időben ismert kapacitásához kötjük. Ez az az
ellenőrzés, amely olcsó, hordozható és determinisztikus.
| Ellenőrzés | Hol végezhető | Költség | Mit nem fog meg |
|---|---|---|---|
| NULL-ellenőrzés | határon, 1 regiszterteszt | ~1 utasítás | érvénytelen, de nem-NULL pointert |
| hossz felső korlát | határon, 1 összehasonlítás | ~2 utasítás | hibás, de a korláton belüli hosszt |
pointer-tartomány (linker szimbólumok, pl. __bss_start,
_end) |
futásidőben, platformfüggő | több tíz ciklus | nem hordozható, nem teljes |
| ASan / hardveres címcímkézés | teszt- vagy speciális build | 2–10× lassulás | éles buildben nincs jelen |
Munkakönyvtár: ~/arm-labor/a64/a056/ —
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/a056
cd ~/arm-labor/a64/a056A cél egy olyan modul, amely egy 64 bájtos belső
puffert tart karban, és egy (src, n) párt fogad
el. A szerződés teljes alakja:
| Szerződési elem | Kinek a kötelezettsége | Ellenőrzi-e a modul? |
|---|---|---|
src != NULL |
hívó | igen (cbz) |
n ≤ 63 (a lezáró '\0' helye is kell) |
hívó | igen (cmp/b.hi) |
src[0 … n−1] olvasható, azaz legalább n
érvényes forrásbájt |
hívó | nem – általánosan nem is ellenőrizhető |
a forrás nem fed át a belső puffer írt tartományával
([0 … n]); megengedett átfedés nincs, így a
lab_buffer() mutatója sem adható át forrásként |
hívó | nem |
| a belső puffer egyetlen, közös, statikus példány; a modul nem reentráns és nem szálbiztos, párhuzamos (vagy jelkezelőből érkező) hívásokat a hívónak kell sorosítania | hívó | nem |
siker: visszatérés n, a pufferben n bájt
és egy '\0' |
modul | – |
elutasítás (src == NULL vagy n > 63):
visszatérés -1, a puffer változatlan |
modul | – |
A lab_buffer() által visszaadott mutató a modul belső
tárára mutat, nem önálló élettartamú eredményobjektum:
a mutató a program teljes futása alatt érvényes, a tartalom viszont csak
a következő sikeres lab_copy_bounded hívásig. Akinek tartós
eredmény kell, a hívás után átmásolja. A hosszellenőrzés ezen semmit nem
változtat: egy jól ellenőrzött hossz sem teszi a közös puffert használó
függvényt reentránssá.
A „mindent vagy semmit” garancia tehát a szerződés alapján
elutasított argumentumokra vonatkozik (NULL, túl nagy
n). Ha a hívó megszegi a nem ellenőrzött feltételeket
(például n megengedett, de a forrásból csak 10 bájt
olvasható), a másolás közben memóriahiba keletkezhet, és ekkor a puffer
félig felülírt állapotban maradhat. Erre a modul nem ad és nem is adhat
garanciát.
A teszthajtó minden esetnél a hívás előtt és után is lemásolja a
puffert. Elutasításkor azt ellenőrzi, hogy a tartalom bájtra azonos
maradt, sikernél pedig a másolt bájtokat és a lezáró nullát. Az esetek a
határ két oldalát (62, 63, 64) és
a konverziós szélsőértéket (SIZE_MAX) is lefedik:
/* lab_a056.c – biztonságos interop-határ demonstráció (A64 ISA) */
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#define LAB_CAP 64u
/* Szerződés (a teljes szöveget lásd a fejezetben):
* - src != NULL, és src[0..n-1] olvasható (legalább n bájt);
* - n <= LAB_CAP-1 (63);
* - a forrás nem fedhet át a belső pufferrel (src nem lehet lab_buffer()-ből);
* - a belső puffer egyetlen, közös példány: a függvény nem reentráns,
* párhuzamos hívásokat a hívónak kell sorosítania.
* Visszatérés: n siker esetén (a puffer: n bájt + '\0');
* -1, ha src == NULL vagy n > 63 – ekkor a puffer érintetlen. */
long lab_copy_bounded(const uint8_t *src, size_t n);
const uint8_t *lab_buffer(void); /* csak megfigyeléshez; a tartalom a
következő sikeres hívásig érvényes */
static uint8_t src[4096];
static int check(const char *name, const uint8_t *p, size_t n, long want)
{
uint8_t before[LAB_CAP], after[LAB_CAP];
memcpy(before, lab_buffer(), LAB_CAP);
long got = lab_copy_bounded(p, n);
memcpy(after, lab_buffer(), LAB_CAP);
int ok = (got == want);
if (want < 0) /* elutasítás: nincs mellékhatás */
ok = ok && memcmp(before, after, LAB_CAP) == 0;
else /* siker: n bájt + lezáró nulla */
ok = ok && memcmp(after, p, n) == 0 && after[n] == 0;
printf("%-16s -> %3ld (várt %3ld) %s\n",
name, got, want, ok ? "OK" : "HIBA");
return ok;
}
int main(void)
{
int ok = 1;
memset(src, 'A', sizeof(src));
ok &= check("n=5", src, 5, 5);
ok &= check("n=62", src, 62, 62);
ok &= check("n=63", src, 63, 63);
ok &= check("n=64", src, 64, -1);
ok &= check("NULL, n=5", NULL, 5, -1);
ok &= check("n=SIZE_MAX", src, SIZE_MAX, -1);
printf("puffer hossza: %zu\n", strlen((const char *)lab_buffer()));
return ok ? 0 : 1;
}Az assembly oldal (A64 ISA, little-endian):
/* lab_bound.S – határellenőrzött másoló (A64 ISA) */
.arch armv8-a
.equ LAB_CAP, 64
.equ LAB_MAXN, LAB_CAP - 1 /* 63: a lezáró '\0' indexe is belefér */
.bss
.align 4
lab_buf:
.skip LAB_CAP
.size lab_buf, LAB_CAP
.text
.global lab_copy_bounded
.type lab_copy_bounded, %function
lab_copy_bounded:
/* x0 = src, x1 = n (A64 ISA) */
cbz x0, .Lreject /* pointer-érvényesség: nem-NULL */
mov x2, #LAB_MAXN
cmp x1, x2
b.hi .Lreject /* n > 63 -> elutasítás, MÉG NEM ÍRTUNK */
adrp x3, lab_buf /* lapcím + 12 bites eltolás */
add x3, x3, :lo12:lab_buf
mov x4, #0 /* i = 0 */
.Lloop:
cmp x4, x1
b.hs .Ldone
ldrb w5, [x0, x4]
strb w5, [x3, x4]
add x4, x4, #1
b .Lloop
.Ldone:
strb wzr, [x3, x1] /* '\0' az n. indexre (n <= 63) */
mov x0, x1 /* siker: a másolt bájtok száma */
ret
.Lreject:
mov x0, #-1 /* MOVN -> x0 = 0xFFFF_FFFF_FFFF_FFFF */
ret
.size lab_copy_bounded, . - lab_copy_bounded
.global lab_buffer
.type lab_buffer, %function
lab_buffer:
adrp x0, lab_buf
add x0, x0, :lo12:lab_buf
ret
.size lab_buffer, . - lab_buffer
.section .note.GNU-stack,"",%progbits /* nem kér végrehajtható vermet */Fordítás és futtatás (qemu-user target). A teszthajtó
kilépési állapota 0, ha minden eset OK, egyébként 1:
aarch64-linux-gnu-gcc -O2 -Wall -Wextra -fstack-protector-strong -fPIE -pie \
-o lab-a056 lab_a056.c lab_bound.S
qemu-aarch64 -L /usr/aarch64-linux-gnu ./lab-a056; echo "kilépési állapot: $?"Kimenet a helyettesítő eszközlánccal (zig cc/Clang + musl, statikus
build, Unicorn-alapú emulátor) mérve; a GNU-eszközlánccal és
qemu-aarch64-gyel ugyanezt kell kapni, mert a kimenet csak
a programlogikától függ:
n=5 -> 5 (várt 5) OK
n=62 -> 62 (várt 62) OK
n=63 -> 63 (várt 63) OK
n=64 -> -1 (várt -1) OK
NULL, n=5 -> -1 (várt -1) OK
n=SIZE_MAX -> -1 (várt -1) OK
puffer hossza: 63
kilépési állapot: 0
A határ körüli három bemenet a helyes kódra és a két jellegzetes elrontott változatra (mindegyik ugyanabból a forrásból, egyetlen sor módosításával készült és lefutott):
| Változat | n = 62 |
n = 63 |
n = 64 |
A hiba jellege |
|---|---|---|---|---|
helyes: cmp x1, #63 + b.hi |
62 | 63 | −1 | – |
b.hs a b.hi helyett (63-mal
hasonlítva) |
62 | −1 | −1 | túl szigorú: az érvényes 63-at is elutasítja, memóriahibát nem okoz |
cmp x1, #64 + b.hi (a kapacitással, nem a
maximális hosszal hasonlítva) |
62 | 63 | 64 | túl engedékeny: a '\0' a 64. indexre, a puffer mögé
kerül |
Levezetés az indexhatárra. Tegyük fel, hogy az
elutasítás után az n ≤ 63 feltétel fennáll. A
ciklusváltozóra i ∈ {0, …, n−1}, így a cikluson belüli
legnagyobb írási index n−1 ≤ 62. A lezáró bájt indexe
n ≤ 63. A puffer érvényes indexei 0 … 63,
tehát minden írás a pufferen belül marad. A feltétel
tehát elégséges. A cmp x1, #63 után a b.hi a
63-nál nagyobb, előjel nélküli hosszt utasítja el: n = 63
még elfogadott, n = 64 már elutasított.
b.hs-re cserélve a 63-as értéket is elutasítanánk. A lezáró
nulla a belső 64 bájtos puffer n indexű bájtjába kerül.
Elfogadási táblázat: n = 62 → siker (62);
n = 63 → siker (63); n = 64 és
n = SIZE_MAX → -1, a puffer bájtra
változatlan. Mind a négy sort a fenti teszthajtó ellenőrzi.
Miért fontos a sorrend? Az elutasítási ág a másolás előtt fut le, ezért a szerződés alapján elutasított argumentumokra a modul „mindent vagy semmit” módon viselkedik: nem marad félig felülírt puffer. Ez a tulajdonság nem esztétikai kérdés — egy részleges írás a hívó oldalon konzisztensnek látszó, de tartalmilag hibás állapotot hagyhat maga után.
1. mov w0, #-1 a mov x0, #-1
helyett. A W-regiszterbe írás a felső 32 bitet nullázza, így
x0 = 0x0000_0000_FFFF_FFFF: a hívó long
visszatérési értéke 4294967295 lesz, nem -1. A
hívóoldali if (ret < 0) ellenőrzés csendben elmarad.
Ellenpróba: objdump -d lab-a056 | grep -A2 reject, illetve
a C oldalon printf("%ld"), nem %d.
2. A határellenőrzés a konverzió után történik-e. Ha
a C hívó int-et ad át és a fogadó size_t-ként
kapja, a negatív érték előjeles→előjel nélküli konverzióval hatalmas
számmá válik. Ez nem hiba, ha az ellenőrzés az átvétel
utáni értéken fut (nálunk így van: a
cmp x1, x2 a nyers bitmintát vizsgálja). Ellenpróba:
lab_copy_bounded(src, (size_t)-1) → várt
-1.
3. Off-by-one a feltételkódban vagy a konstansban. A
cmp x1, #63 után a b.hi az
n > 63, a b.hs az n ≥ 63
esetet utasítja el. A b.hs tehát nem
engedi át a 64-et, hanem túl szigorú: az érvényes n = 63-at
is visszautasítja (a tesztben az n=63 sor
-1-et ad, és a puffer hossza 62 marad). Ez funkcionális
hiba, memóriát nem ír felül. A veszélyes off-by-one a másik irány: ha a
kódot a kapacitással (cmp x1, #64) hasonlítjuk a maximális
hossz helyett, a b.hi átengedi a 64-et, és a
strb wzr, [x3, x1] a 64. indexre ír, egy bájttal a puffer
mögé. Ez az a022 óta ismert
[Xn, Xm] címzési mód klasszikus csapdája. Ellenpróba: a
fenti táblázat három sora; az n=63 sornak
63-at, az n=64 sornak -1-et kell
adnia.
4. Hiányos clobber-lista inline assemblynél. Ha
ugyanezt a logikát C asm volatile blokkban írnánk meg (lásd
Inline assembly GCC-ben), az x0–x5
regisztereket vagy kimeneti/bemeneti megszorításként kell deklarálni,
vagy szerepelniük kell a clobber-listában. Hiányzó bejegyzés esetén a
fordító egy élő értéket tart nyilván ugyanabban a regiszterben, és a
keletkező hibát semmi nem jelzi — a program „néha” rossz eredményt ad,
optimalizálási szinttől függően.
5. Mentési aszimmetria. A fenti rutin levélfüggvény:
nem hív mást, és csak az x0–x5 caller-saved (hívó által
mentendő) regisztereket használja, veremhasználat nélkül. Amint a rutin
bl-t kap vagy x19–x28-at használ, minden
stp-hez tartoznia kell egy ldp-nek, és a
veremmutatót 16 bájtos határra kell igazítani (AAPCS64 regiszterszerződés, Prológus és epilógus). Ellenpróba: a
ret előtt mov sp, x29-szerű visszaállítás
hiánya GDB-ben „nem oda visszatér” tünettel jelenik meg.
A fenti ellenőrzéseket érdemes kiegészíteni, nem helyettesíteni a fordítói védelmi kapcsolókkal:
-fstack-protector-strong — canary-t tesz a
RET-et tartalmazó, helyi puffert használó függvények
keretébe;-D_FORTIFY_SOURCE=2 -O2 — a
memcpy/strcpy családot futásidejű
méretellenőrzéssel ellátott változatra cseréli, ahol a méret fordítási
időben ismert;-fPIE -pie — a kód pozíciófüggetlen, így az ASLR a
teljes binárist áthelyezi;-Wl,-z,relro,-z,now — a dinamikus szerkezetek
írásvédetté válnak a betöltés után.Fontos: ezek a következményt detektálják vagy nehezítik, nem az okot szüntetik meg. A canary a felülírás után szól, a határellenőrzés viszont az írás előtt. Ez a két mechanizmus nem versenytárs, hanem réteg. A konkrét kapcsolók elérhetősége és viselkedése az adott GCC-verzióhoz kötött, Ubuntu 24.04 LTS csomagolt toolchainjén ellenőrzendő (lásd az utolsó szakaszt).
| # | Ellenőrzés | Kérdés a review-n | Bizonyíték |
|---|---|---|---|
| 1 | Szerződés rögzítve | Hol van írva az argumentumok értékhatománya, az olvasható forráshossz, a megengedett átfedés, a közös állapot (reentrancia) és a hibajelzés? | fejléc-komment, .h prototípus |
| 2 | Határellenőrzés a határon | A hossz a fogadó kapacitásához van kötve? | cmp/b.hi az első írás előtt |
| 3 | Ellenőrzés a konverzió után | Az int→size_t konverzió előtt vagy után
vizsgálunk? |
disassembly, objdump -d |
| 4 | Nulla mellékhatás elutasításkor | Elutasítás után érintetlen-e az állapot? | határérték-teszt kimenete |
| 5 | Mentési szimmetria | Minden stp-nek van ldp párja minden
kimeneti úton? |
objdump, minden ret áttekintése |
| 6 | Veremigazítás | sp 16 bájtos többszöröse minden bl
előtt? |
pro-/epilógus hossza |
| 7 | Callee-saved szabály | Az x19–x28, x29, sp értéke
helyreáll? |
AAPCS64-tábla összevetése |
| 8 | Clobber-lista / megszorítások | Minden érintett regiszter deklarált inline asm-nál? | GCC output, -S |
| 9 | Visszatérési érték konvenciója | A -1 x0-ban, nem w0-ban
képződik? |
disassembly |
| 10 | Fordítói védelmi kapcsolók | Bekapcsoltak a fenti flagek az éles buildben? | makefile, readelf -h (PIE),
readelf -d (BIND_NOW) |
| 11 | Fuzz / határérték-tesztek | Van teszt n = 0, n = max,
n = max+1, NULL esetre? |
CI-kimenet |
Ez a lista egyben a visszacsatolás és auditmódszer előképe: a Záróprojekt I auditlépése ugyanezeket a pontokat kéri számon, kiegészítve a mérési terv és a reprodukálhatóság ellenőrzésével.
1. feladat (magyarázó, alapszint — cél:
megértés-ellenőrzés). Egy kollégád azt állítja: „elég, ha a
határon ellenőrizzük, hogy a pointer nem NULL, mert a
hosszt úgyis a hívó számolja”. Fogalmazz három ellenérvet, és add meg
mindegyikhez azt a konkrét tesztesetet, amely megkülönbözteti a két
álláspontot!
2. feladat (implementációs, középszint — cél:
rutinépítés). Egészítsd ki a modult egy
long lab_copy_n(const uint8_t *src, size_t n, size_t cap)
rutinnal, amely a kapacitást paraméterként kapja, és
ott is explicit határellenőrzést végez! Gondoskodj arról, hogy a C
oldali int átadás esetén is helyes legyen a viselkedés, és
hogy a rutin ne írjon a pufferen kívülre. Írj hozzá legalább négy
határérték-tesztet!
3. feladat (hibakeresési/mérési, középszint — cél:
rutinépítés). Adott egy variáns, amelyben a
cmp x1, x2 utasítás a ciklus után
szerepel, és a cbz a ciklus előtt. GDB-vel
(a qemu-aarch64 -g és gdb-multiarch párossal)
keresd meg, melyik konkrét tároló utasítás ír a puffer mögé az
n = 64 esetben, majd egyszeri módosítással javítsd és
igazold a javítást!
1. megoldás — Segítség. Gondold át külön: mit szűr
ki a NULL-ellenőrzés, és mit NEM — egy pointer lehet
nem-NULL és mégis érvénytelen, a hívó által adott hossz
pedig maga is hibás lehet.
1. megoldás — Teljes megoldás. (a) A
NULL-ellenőrzés csak egyetlen érvénytelen értéket szűr ki;
egy 0x1000-re mutató vagy a szomszéd objektumba mutató
pointer átmegy rajta, és az írás a puffer mögé kerül. Teszt:
lab_copy_bounded((const uint8_t *)0x1000, 8) — a hívás nem
NULL, mégis érvénytelen. (b) A hívó által számolt hossz
maga is lehet hibás (túlcsorduló size_t szorzás,
off-by-one), és a fogadó fél az egyetlen, aki ismeri a saját
kapacitását. Teszt: n = LAB_CAP → várt -1. (c)
A hívó és a hívott közötti szerződés nem kényszeríthető ki a hívó
oldalon, mert a hívó lehet harmadik féltől származó kód. Teszt: az
n=64 sor kimenete -1 kell legyen, függetlenül
attól, ki hívja a rutint.
2. megoldás — Segítség. A sorrend számít: előbb a
cap saját felső korlátját ellenőrizd (nehogy a hívó maga is
túlígérjen), utána a kért n-t a cap-hez
képest, és csak ezután induljon a másoló ciklus.
2. megoldás — Teljes megoldás. A kulcs a sorrend:
először cap felső korlátja (a modul statikus puffere), majd
a hívó által kért n összevetése a cap-pel, és
csak ezután a ciklus. Vázlat:
lab_copy_n: /* x0=src, x1=n, x2=cap (A64 ISA) */
cbz x0, .Lrej
mov x6, #LAB_MAXN
cmp x2, x6 /* cap se lépje túl a statikus puffert */
b.hi .Lrej
cmp x1, x2 /* n <= cap ? -- a konverzió UTÁN */
b.hi .Lrej
/* ... ugyanaz a másoló ciklus, n helyett x1 ... */A tesztek: n = 0, cap = 63 → 0;
n = 63, cap = 63 → 63; n = 64, cap = 63 → −1;
n = 5, cap = 64 → −1 (a cap maga is korlátos);
src = NULL → −1. Az int→size_t
eset azért biztonságos, mert a negatív érték hatalmas előjel nélküli
számmá alakul, és a b.hi úgyis elutasítja.
3. megoldás — Segítség. Számítsd ki pontosan, mely index a ciklus utolsó, még engedélyezett írása, és melyik index az, amelyre a lezáró tárolóutasítás ír — a kettő eltérése mutatja meg, hol csúszik túl a határon.
3. megoldás — Teljes megoldás. A hibás variánsban a
ciklus legfeljebb a strb w5, [x3, x4] utasítással ír az
x4 = 63 indexre (ez még a pufferen belül van), majd a
lezáró strb wzr, [x3, x1] az x1 = 64 indexre
ír — ez már a lab_buf utáni bájtra mutat. A GDB-ben
break *lab_copy_bounded+... töréspontot érdemes a lezáró
tárolóra tenni, majd x/8bx &lab_buf+60 paranccsal
ellenőrizni a puffer környékét. A javítás: a
cmp/b.hi párt a ciklus elé mozgatni, és a
strb wzr indexét a már ellenőrzött n-re
hagyni. Igazolás: az n=64 sor -1-et ad
OK-val (a teszthajtó a puffer változatlanságát is
ellenőrzi), az n=63 sor továbbra is 63, a
kilépési állapot 0.
Mérési megjegyzés: a határellenőrzés költsége itt két utasítás (egy
mov és egy cmp+b), ami
elhanyagolható a másolási ciklus mellett; érdemi változás akkor várható,
ha a rutin néhány bájtos bemenetre hívódik, és az ellenőrzés dominálja a
futásidőt. QEMU-emulációban ez relatív/illusztratív adat, nem
hardverhű abszolút ciklusszám.
A C–assembly határ biztonsági szerződése négy dimenzióból áll: értékhatomány, pointer-érvényesség, tulajdonjog és hibajelzési konvenció. A linkelés egyiket sem ellenőrzi. Mivel a pointer érvényessége általánosan nem eldönthető, a legértékesebb ellenőrzés az, amely a kapott hosszt a fogadó fél saját kapacitásához köti, a konverzió után és az első írás előtt. A fordítói védelmi kapcsolók a következményt detektálják, nem az okot. A review-checklista tizenegy pontja (szerződés, határellenőrzés, mentési szimmetria, clobber-lista, hibajelzés, tesztek) közvetlenül a záróprojekt auditlépésére készít fel.
Továbbhasznált definíciók: - interop-szerződés: az argumentumok értékhatománya, a pointer-érvényesség értelmezése, a tulajdonjog és a hibajelzési konvenció együttese. - kapacitáskötött határellenőrzés: a fogadott hossz összevetése a hívott fél saját, fordítási időben ismert kapacitásával. - mentési szimmetria: minden regisztermentő utasításhoz tartozó, minden kimeneti úton végrehajtott helyreállítás.
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.
MOVN és a feltételkódok elsődleges forrása.
Ellenőrizve: a link él, de a fejezetszám és a kiadásbetű az
Arm-oldalak közös korlátja miatt nem ellenőrzött.-fstack-protector-strong, -fPIE,
_FORTIFY_SOURCE; az inline assembly megszorításai és
clobber-listája. Ellenőrizve: a legújabb listázott kiadás GCC
16.2; a helyben telepített verzió ettől eltérhet..equ,
.size, .bss direktívák. Ellenőrizve:
a jelenlegi kiadás binutils 2.47.qemu-aarch64 -L loader-útvonal és a
-g GDB-stub használata. Ellenőrizve: a
master ághoz tartozó dokumentáció, nem egy konkrét kiadott
verzióhoz kötött; nem állít semmit a cache/sávszélesség-modellezésről
sem.Ellenőrzési hiányok, amelyek a fenti ellenőrzés után is
fennmaradnak: a lab_a056.c +
lab_bound.S párost a helyettesítő eszközlánccal (zig
cc/Clang + musl, statikus build, Unicorn-alapú emulátor) lefordítottuk
és lefuttattuk, a közölt kimenet és a 62/63/64-es táblázat mindhárom
sora ebből származik; a GNU-eszközlánccal és
qemu-aarch64-gyel nem futtattuk. A laborban ellenőrizendő:
(1) az Ubuntu 24.04 LTS-ben csomagolt
gcc-aarch64-linux-gnu,
binutils-aarch64-linux-gnu és qemu-user
konkrét verziója és a /usr/aarch64-linux-gnu
sysroot-útvonal; (2) a -fstack-protector-strong tényleges
viselkedése AArch64-en az adott GCC-verzióval; (3) a
readelf -h által mutatott PIE-típus és a
readelf -d BIND_NOW bejegyzése; (4) a
qemu-aarch64 user-mode GDB-stubjának (-g)
működése gdb-multiarch-gyal.