Egy állapotszó gyakran több, egymástól független adatot hordoz: néhány bit üzemmódot, más bitek előjeles korrekciót, további bitek számlálót jelentenek. C-ben ezeket eltolásokkal és maszkolással vagy struktúrába szervezett bitmezőkkel kezelhetjük. Assemblyben a feladat két alapműveletre egyszerűsödik:
A bitmező (bit field) egy rögzített
szélességű érték összefüggő bittartománya. A legkisebb helyi értékű bit
sorszáma 0. Egy mezőt kezdőpozíciója és bitszélessége
határoz meg.
Az előfeltétel a Shift és rotate: LSL/LSR/ASR/ROR gyakorlati mintázatok ismerete. Itt már nem általános bitmozgatásokat építünk fel, hanem azok gyakori összetett mintáit fejezzük ki közvetlenül.
Architektúra és profil: az alfejezet minden assembly utasítása és regisztere az A64 ISA-hoz, az AArch64 I. részhez tartozik. A cél ARMv8-A, Linux, little-endian. A példák nem Cortex-M-perifériákat érnek el.
Az A64 utasítások szintaxisa:
| A64 utasítás | Jelentés |
|---|---|
UBFX Wd, Wn, #lsb, #width |
Mezőkinyerés, nullakiterjesztéssel 32 bitre |
SBFX Wd, Wn, #lsb, #width |
Mezőkinyerés, előjelkiterjesztéssel 32 bitre |
BFI Wd, Wn, #lsb, #width |
A forrás alsó bitjeinek beillesztése a célmezőbe |
Mindháromnak van 64 bites, Xd és Xn
operandusokat használó változata. Egy utasításon belül az
adatoperandusok szélessége azonos.
Legyen:
32 vagy
64;Az immediate operandusok megengedett tartománya:
A mező tehát nem lóghat túl a regiszter felső szélén. A pozíció és a
szélesség fordításkor ismert konstans, nem regiszterből
vett futásidejű paraméter. Például a 32 bites
UBFX W0, W1, #28, #5 érvénytelen: a mező utolsó bitje a nem
létező 32 sorszámú bit lenne.
Az A64 UBFX a kiválasztott mezőt a cél alsó bitjeire
helyezi, a cél többi bitjét nullázza.
Levezetés, előjel nélküli értelmezéssel: ha a forrásregiszter értéke, akkor
Az eredmény tartománya . Az eltolás eltávolítja a mező alatti biteket, a maszk pedig a mező feletti biteket.
Az A64 UBFX W0, W0, #8, #8 tehát a forrás
15:8 bitjeit emeli ki. Az azonos forrás- és célregiszter
megengedett.
Az A64 SBFX ugyanazt a bittartományt választja ki, de
annak legfelső bitjét előjelbitként kezeli. Az
előjelkiterjesztés (sign extension) ezt a
bitet másolja a cél magasabb bitjeire.
Ha az előjel nélküli mezőérték , akkor a kettes komplemens szerinti előjeles érték:
Az eredmény tartománya . Az előjelet a mező legfelső bitje, nem a teljes forrásregiszter legfelső bitje határozza meg.
Fontos A64-szélességi szabály: az SBFX W0, ... csak 32
bitre terjeszti ki az előjelet. A W0 írása az
X0 felső 32 bitjét nullázza.
| A64 művelet eredménye | W0 bitmintája |
X0 bitmintája |
|---|---|---|
32 bites előjeles -3 |
0xFFFFFFFD |
0x00000000FFFFFFFD |
Ha eleve 64 bites, előjeles eredmény kell, a megfelelő 64 bites
forráson az SBFX X0, X0, #3, #5 változatot használhatjuk.
Ennek -3 eredménye 0xFFFFFFFFFFFFFFFD.
Az A64 BFI a forrás alsó
bitjét másolja a cél p+k-1:p tartományába. A cél
eredeti értéke tehát bemenet is.
Legyen a célmező maszkja:
Ekkor, bites műveletként értelmezve:
Itt pontosan biten képzett komplementert jelent. Az első tag megőrzi a célmezőn kívüli biteket, a második a helyükre teszi az új mezőértéket.
Az A64 BFI W0, W1, #8, #8:
W1 alsó 8 bitjét használja;W0 15:8 bitjeit felülírja;W0 többi bitjét megőrzi.A túl nagy bemenetet nem utasítja el: csonkolja. A
0x123 forrásból 8 bites mezőbe 0x23 kerül.
A megőrzési ígéret itt csak a 32 bites célra vonatkozik: a
W0 írása továbbra is nullázza az X0 felső
felét.
Az A64 UBFX, SBFX és BFI
rendre az UBFM, SBFM és BFM
utasítások alternatív nevei, azaz aliasai. Nem több utasításra
kibontott makrók.
A kódolt immediate mezők:
| A64 alias | immr |
imms |
|---|---|---|
UBFX, SBFX |
||
BFI |
A kódolás mindkét immediate mezője 6 bites; 32 bites alaknál csak a megfelelő 5 bites értéktartomány érvényes. Az assembler az olvasható pozíció–szélesség párból állítja elő ezeket.
E három művelet nem módosítja az A64 NZCV
állapotjelzőket. A disassembler más aliast is megjeleníthet, különösen
szélső paramétereknél; ez önmagában nem jelent eltérő működést.
Munkakönyvtár: ~/arm-labor/a64/a034/ —
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/a034
cd ~/arm-labor/a64/a034Egy programon belüli, 32 bites állapotszó formátuma legyen:
| Bittartomány | Szélesség | Mező | Értelmezés |
|---|---|---|---|
2:0 |
3 bit | mode |
Előjel nélküli üzemmód |
7:3 |
5 bit | delta |
Előjeles korrekció |
15:8 |
8 bit | count |
Előjel nélküli számláló |
31:16 |
16 bit | tag |
Előjel nélküli azonosító |
A bemenet 0xA55A3CED.
Levezetett mezőértékek:
mode: 0b101, előjel nélkül
5;delta: 0b11101, előjel nélkül
29, előjelesen
;count: 0x3C, előjel nélkül
60;tag: 0xA55A, előjel nélkül értelmezett
azonosító.A count mezőbe 0x123 értéket illesztünk. A
várt eredmény 0xA55A23ED: csak a mező alsó 8 bitnyi új
értéke kerül be.
A lab-bitfield.S fájl teljes tartalma:
// A64 ISA, AArch64, ARMv8-A.
// A direktívák GNU as / ELF környezethez tartoznak.
.arch armv8-a
.text
.p2align 2
.global lab_count
.type lab_count, %function
lab_count:
UBFX W0, W0, #8, #8
RET X30
.size lab_count, .-lab_count
.global lab_delta
.type lab_delta, %function
lab_delta:
SBFX W0, W0, #3, #5
RET X30
.size lab_delta, .-lab_delta
.global lab_put_count
.type lab_put_count, %function
lab_put_count:
BFI W0, W1, #8, #8
RET X30
.size lab_put_count, .-lab_put_count
.section .note.GNU-stack,"",%progbitsAz AAPCS64 szerint a 32 bites argumentumok itt a W0,
illetve W1 regiszterben érkeznek, az eredmény a
W0 regiszterben távozik. A függvények nem hívnak más
függvényt, és nem használnak megőrzendő regisztert, ezért nincs szükség
veremkeretre.
A C bitmezős struktúra nem hordozható fájl- vagy perifériaformátum-leírás. A mezők tárolóegységen belüli elhelyezése, a kitöltés és bizonyos típusválasztási részletek implementációfüggők; a konkrét ABI további szabályokat ad.
Különösen:
signed int
típust;int bitmező előjelességére ne építsünk
hordozható szerződést;packed attribútum önmagában nem teszi hordozhatóvá a
bitkiosztást.A labor az AArch64 GNU/Linux little-endian profil várható kiosztását
ellenőrzi, nem általános C-szabályként feltételezi. A
reprezentációt memcpy segítségével vizsgáljuk, nem
inkompatibilis mutatótípuson keresztül.
A lab-bitfield.c fájl:
/* Cel: AArch64 GNU/Linux, ARMv8-A, little-endian. */
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
uint32_t lab_count(uint32_t word);
int32_t lab_delta(uint32_t word);
uint32_t lab_put_count(uint32_t word, uint32_t value);
struct lab_fields {
unsigned int mode : 3;
signed int delta : 5;
unsigned int count : 8;
unsigned int tag : 16;
};
_Static_assert(sizeof(struct lab_fields) == 4,
"A labor 4 bajtos strukturakiosztast var");
uint32_t lab_c_count(const struct lab_fields *p)
{
return p->count;
}
int32_t lab_c_delta(const struct lab_fields *p)
{
return p->delta;
}
void lab_c_set_count(struct lab_fields *p, uint32_t value)
{
p->count = value;
}
uint32_t lab_c_put_count_word(uint32_t word, uint32_t value)
{
return (word & UINT32_C(0xFFFF00FF))
| ((value & UINT32_C(0xFF)) << 8);
}
int main(void)
{
const uint32_t word = UINT32_C(0xA55A3CED);
const uint32_t value = UINT32_C(0x123);
const uint32_t updated = lab_put_count(word, value);
if (lab_count(word) != 60 || lab_delta(word) != -3)
return 1;
if (updated != UINT32_C(0xA55A23ED) ||
((updated ^ word) & UINT32_C(0xFFFF00FF)) != 0)
return 2;
struct lab_fields fields = {
.mode = 5, .delta = -3, .count = 0x3C, .tag = 0xA55A
};
uint32_t raw;
memcpy(&raw, &fields, sizeof raw);
if (raw != word ||
lab_c_count(&fields) != 60 || lab_c_delta(&fields) != -3)
return 3;
lab_c_set_count(&fields, value);
memcpy(&raw, &fields, sizeof raw);
if (raw != updated ||
lab_c_put_count_word(word, value) != updated)
return 4;
printf("count=%" PRIu32 " delta=%" PRId32
" updated=0x%08" PRIx32 "\n",
lab_count(word), lab_delta(word), updated);
return 0;
}A C-szintű maszkolás itt biztonságos: előjel nélküli, 32 bites
értékeken dolgozik, és az eltolásszám 8. Általános
segédfüggvényben az (1u << width) - 1 kifejezés
width == 32 esetén már hibás lenne; a teljes szélességű
mezőt külön kell kezelni.
Az alábbi parancsok célja qemu-user, AArch64. A statikus linkeléshez a cross környezet céloldali C fejlesztői könyvtárai is szükségesek.
# Target: qemu-user / AArch64, ARMv8-A, little-endian.
aarch64-linux-gnu-as -EL -g \
-o lab-bitfield.o lab-bitfield.S
aarch64-linux-gnu-gcc -std=c11 -O2 -g -Wall -Wextra \
-march=armv8-a -mlittle-endian \
-c lab-bitfield.c -o lab-bitfield-c.o
aarch64-linux-gnu-gcc -static -no-pie \
lab-bitfield-c.o lab-bitfield.o -o lab-bitfield
qemu-aarch64 ./lab-bitfield
printf 'exit=%d\n' "$?"
# Az A64 kodolas es a C-bol generalt kod vizsgalata.
aarch64-linux-gnu-objdump -d -M no-aliases lab-bitfield.o
aarch64-linux-gnu-objdump -d lab-bitfield-c.oVárt, nem ténylegesen mért kimenet:
count=60 delta=-3 updated=0xa55a23ed, majd
exit=0.
A nem nulla kilépési állapot jelentése:
| Állapot | Elsődleges vizsgálati irány |
|---|---|
1 |
Kinyerési pozíció, szélesség vagy előjelkezelés |
2 |
Beillesztés és a többi bit megőrzése |
3 |
C-struktúra kezdeti kiosztása vagy kiolvasása |
4 |
C-mezőírás és assembly eredményének eltérése |
Little-endian memóriában a bemeneti szó bájtjai, növekvő címek
sorrendjében: 0xED, 0x3C, 0x5A,
0xA5. Ez nem változtatja meg a regiszteren belüli
bitsorszámokat.
A forráskód szerkezete nem ír elő egyetlen kötelező utasítássorozatot.
Az A64 célra fordított lab_c_delta esetében természetes
minta egy betöltést követő SBFX. A count
azonban bájthatárra esik, ezért a fordító közvetlen LDRB
betöltést vagy STRB tárolást is választhat: ilyenkor nincs
szükség külön regiszteres mezőműveletre.
A lab_c_put_count_word ezzel szemben már regiszterben
érkező egész értéket módosít; kézenfekvő A64 megvalósítása a
BFI. A konkrét GCC-verzió választását az objektumkód
mutatja meg.
A helyes ellenőrzési kérdés tehát nem az, hogy „megjelent-e a kívánt mnemonic”, hanem hogy azonos-e a kinyert érték, az előjel és a megőrzött bittartomány. Az egyutasításos alakból önmagában nem következik konkrét ciklusszám vagy gyorsulás; QEMU-val itt funkcionális helyességet vizsgálunk.
A példánk perifériastílusú állapotszó, de kizárólag normál programadat. A valódi perifériaregiszter kezelése további szerződést igényel, amelyet a Memory-mapped I/O alapelve tárgyal.
Az A64 BFI csak CPU-regisztert módosít. A memóriából
betöltés, módosítás és visszaírás együtt
olvasás–módosítás–írás (read-modify-write)
műveletsor; nem válik atomivá attól, hogy középen egyetlen utasítás
áll.
Perifériánál egy visszaírt 1 akár állapotbitet törölhet,
az olvasásnak pedig mellékhatása lehet. A fenntartott biteket és a
megengedett hozzáférési szélességet is a periféria dokumentációja
szabályozza. A C volatile minősítő önmagában sem az
atomiságot, sem a megfelelő bitmező-hozzáférést nem garantálja.
Tünet: -3 helyett
29.
Hipotézis: az előjeles mezőt A64 UBFX emeli ki. Az
objdump kimenetében ellenőrizzük az utasítást és a
#3, #5 operanduspárt. Egyetlen változtatásként cseréljük
SBFX alakra, majd futtassuk újra a tesztet.
Tünet: a számláló helyes, az azonosító mégis
megváltozik.
Hipotézis: túl széles a célmező. A mező visszaolvasása önmagában
elégtelen ellenpróba: a mezőn kívüli biteket is össze kell
hasonlítani.
Tünet: egy korábbi 64 bites érték felső fele
eltűnik.
Hipotézis: A64 W célregisztert használó művelet történt. A
debuggerben az X nézetet is vizsgáljuk; a
szélességválasztást javítsuk, ne a bitmaszkot.
Tünet: néhány negatív érték jó, mások hibásak.
Hipotézis: rossz a mezőszélesség. A -3 véletlenül 4 és 5
bites előjeles mezőként is helyesen kijöhet. Ellenpróbának az 5 bites
tartomány széleit, -16 és 15 értéket
használjuk.
(magyarázó, alapszint — cél:
megértés-ellenőrzés) Miért ad a 0xA55A3CED szó
7:3 mezőjére az A64 UBFX 29, az
SBFX pedig -3 értéket? Mi lesz az
X0 teljes bitmintája, ha az eredményt
SBFX W0, ... állítja elő?
(implementációs, középszint — cél: rutinépítés)
Készíts lab_put_delta(uint32_t word, int32_t value)
függvényt, amely a 7:3 mezőt írja át. Teszteld az összes
-16 és 15 közötti értékkel; ellenőrizd a
visszaolvasást és a többi bit megőrzését is.
(hibakeresési, középszint — cél: rutinépítés) A
lab_put_count A64 BFI utasításában változtasd
a szélességet #8 helyett #9 értékre. Jósolj
eredményt, majd debuggerrel vizsgáld meg a beillesztés előtti és utáni
regiszterállapotot. Miért fogadja el ezt az assembler?
1. megoldás — Segítség. Nézd meg a mező legfelső
bitjét (a 7:3 mező 5 bites, tehát a 4. relatív bit) — ha ez
1, az SBFX kettes komplemens előjelbitként
kezeli.
1. megoldás — Teljes megoldás. A mező bitmintája
0b11101, előjel nélkül 29. Öt biten az
előjelbit értéke 1, ezért az előjeles érték
.
Az A64 SBFX W0, ... eredménye W0 = 0xFFFFFFFD,
de X0 = 0x00000000FFFFFFFD, mert a 32 bites célírás
nullázza a felső 32 bitet.
2. megoldás — Segítség. A lab_put_count
mintáját egyszerűen másolhatod: csak a pozíció és a szélesség immediate
paramétere változik #8,#8-ról #3,#5-re.
2. megoldás — Teljes megoldás. Az assembly fájlhoz hozzáadható:
// A64 ISA; GNU as / ELF direktivak.
.text
.global lab_put_delta
.type lab_put_delta, %function
lab_put_delta:
BFI W0, W1, #3, #5
RET X30
.size lab_put_delta, .-lab_put_deltaA C-fájl deklarációihoz:
/* Cel: AArch64 GNU/Linux. */
uint32_t lab_put_delta(uint32_t word, int32_t value);A main függvénybe, a kiírás elé:
/* Cel: AArch64 GNU/Linux; a main tovabbi ellenorzese. */
for (int32_t v = -16; v <= 15; ++v) {
uint32_t changed = lab_put_delta(word, v);
if (lab_delta(changed) != v)
return 5;
if (((changed ^ word) & UINT32_C(0xFFFFFF07)) != 0)
return 6;
}Az új mező maszkja 0x000000F8, ennek 32 bites
komplementere 0xFFFFFF07. A negatív bemenet alsó öt bitje a
kívánt kettes komplemens mezőkód. Az eredeti buildlépések
megismétlésével a várt kilépési állapot továbbra is 0. A
tartományon kívüli bemeneteket ez az interfész csonkolná; az
elutasításhoz külön ellenőrzés kellene.
3. megoldás — Segítség. Számold ki, melyik bitre
esik a #8, #9 mező teteje (8+9-1), és nézd
meg, hogy a 0x123 forrás kilencedik bitje (index 8) nulla
vagy egy.
3. megoldás — Teljes megoldás. A hibás A64 művelet a
16:8 biteket írja. A 0x123 kilencedik bitje
1, ezért az eredmény
0xA55B23ED lesz: az azonosító egyik bitje
is megváltozik. A program várt kilépési állapota 2. A mező
kódolható, mert 8 + 9 <= 32; a hiba szemantikai, nem
szintaktikai.
Újraépítés után az első terminálban:
# Target: qemu-user / AArch64; varakozas GDB-kapcsolatra.
qemu-aarch64 -g 1234 ./lab-bitfieldA második terminálban:
# Target: a QEMU altal futtatott AArch64 program.
gdb-multiarch -q ./lab-bitfield \
-ex 'target remote :1234' \
-ex 'break lab_put_count' \
-ex 'continue' \
-ex 'x/2i $pc' \
-ex 'info registers x0 x1' \
-ex 'si' \
-ex 'info registers x0 x1'A léptetés után az A64 X0 várt értéke
0x00000000A55B23ED. Állítsuk vissza kizárólag a szélességet
#8 értékre, építsünk újra, és ellenőrizzük a 0
kilépési állapotot.
Az A64 UBFX előjel nélküli, az SBFX
előjeles mezőt emel ki, a BFI pedig a forrás alsó bitjeivel
helyettesíti a cél kijelölt tartományát. A pozíció és a szélesség
immediate operandus, és a mezőnek bele kell férnie a választott
regiszterszélességbe. A 32 bites célírás a kapcsolódó 64 bites regiszter
felső felét nullázza. A C bitmezős struktúrák kiosztását az
implementáció és az ABI határozza meg; rögzített külső formátumhoz
explicit szerződés szükséges. Teszteléskor a kinyert érték mellett a
megőrzendő biteket és a szélső értékeket is ellenőrizzük.
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.
Ellenőrzési hiányok, amelyek a fenti ellenőrzés után is ténylegesen fennmaradnak: a programokat és parancsokat itt nem futtattuk. A helyi Ubuntu 24.04 csomagverziók, a tényleges GCC-utasításválasztás és a struktúrakiosztási teszt eredménye a hallgatói laborban ellenőrizendő. Minden megadott kimenet és regiszterállapot levezetett, várt eredmény.