Képzeljünk el egy Cortex-M3 alapú motorvezérlőt, ahol a megszakításkezelő rutin (ISR) minden egyes ciklusa számít. A rendszer egy 72 MHz-es órajelen fut, és a szabályozási hurok 10 µs-onként lefut. Ha az ISR kódja néhány tíz bájttal hosszabb a szükségesnél, az flash-memória megtakarítás mellett a végrehajtási idő is nő. A Cortex-M3 utasításkészlete, a Thumb-2, változó hosszúságú kódolást használ: a gyakori műveletek 16 biten, a bonyolultabbak 32 biten érhetők el. A kódméret és a sebesség közötti egyensúly megértéséhez elengedhetetlen, hogy tisztában legyünk azzal, melyik utasítás melyik kódolási formában érhető el, milyen regisztereket ér el, és hogyan viselkedik az állapotregiszter. Ez az alfejezet az I. részben megismert A64 ISA után áttér a Thumb-2-re, és megalapozza a Cortex-M3 assembly-programozást.
A Thumb-2 egy olyan utasításkészlet-architektúra (ISA), amely 16 bites és 32 bites utasításkódolást egyaránt tartalmaz. Ez az Armv7-M architektúra kötelező utasításkészlete; a Cortex-M3 processzor kizárólag Thumb-2 módban működik, ARM (A32) mód nem érhető el rajta. A 16 bites utasítások az eredetileg ARMv4T-ben bevezetett Thumb utasításkészlet továbbfejlesztett változatai, amelyek korlátozottabbak: jellemzően csak az R0–R7 regisztereket érik el, és szűkebb immediate-tartományt kódolnak. A 32 bites utasítások – amelyeket gyakran „Thumb-2” néven emlegetünk szűkebb értelemben – már a teljes R0–R15 regiszterkészletet elérik, nagyobb immediate-eket és összetettebb címzési módokat támogatnak.
A Cortex-M3 regiszterkészlete 16 darab 32 bites regiszterből áll. Ezek szerepe a következő:
| Regiszter | Név | Szerep |
|---|---|---|
| R0–R12 | általános célú | szabadon használható adattárolásra; R0–R7 „low”, R8–R12 „high” regiszter |
| R13 | SP (Stack Pointer) | veremmutató; a verem tetejére mutat |
| R14 | LR (Link Register) | visszatérési cím tárolása BL utasítás esetén |
| R15 | PC (Program Counter) | a következő végrehajtandó utasítás címe |
Három különböző „szélességet” érdemes szétválasztani, mert a mindennapi szóhasználat összemossa őket:
| Fogalom | Cortex-M3 (Armv7-M) | Megjegyzés |
|---|---|---|
| regiszterszélesség | minden általános célú regiszter 32 bites | nincs Xn/Wn-szerű nézetpár |
| eredmény- és adatszélesség | jellemzően 32 bit, de van 64 bites eredmény is: az
UMULL/SMULL (32 × 32 → 64 bit) és az
UMLAL/SMLAL két regiszterbe írja az eredményt,
az LDRD/STRD egy duplaszót (8 bájt) mozgat két
regiszter között |
64 bites összeadás ADDS + ADC lánccal
készül |
| utasításhossz | 16 vagy 32 bit (2 vagy 4 bájt) | független a regiszter- és az adatszélességtől |
A regiszterek tehát egységesen 32 bitesek, és ez alapvető eltérés az AArch64-hez képest, ahol az Xn regiszter 64 bites, a Wn pedig ugyanazon regiszter 32 bites nézete. A 32 bites regiszterekből viszont nem következik, hogy a hardver semmilyen 64 bites műveletet nem ismer: a hosszú szorzás és a duplaszavas memóriaelérés két regiszter együttes használatával, de egyetlen utasítással történik.
A PC (R15) olvasásakor Thumb állapotban a kapott
érték az aktuális utasítás címe + 4, függetlenül attól,
hogy az aktuális utasítás 16 vagy 32 bites. (Ez tehát nem általánosan a
„következő utasítás + 4”: 16 bites utasításnál a következő utasítás címe
+2, 32 bitesnél +4.) Néhány használatnál további igazítás is számít. A
PC-relatív betöltés (LDR Rt, [PC, #imm], illetve a
LDR Rt, =érték literáljai) és az ADR az
Align(PC, 4) értékből indul, vagyis az aktuális utasítás
címe + 4 lefelé 4 többszörösére kerekítve. Ezt a részletet a későbbi,
elágazásokat és literálokat tárgyaló alfejezetekben hasznos lesz szem
előtt tartani. A Thumb bit (az EPSR egyik bitje) mindig
1, és a BX/BLX utasítások kezelik a váltást,
de mivel ARM mód nincs, ez csak elméleti jelentőségű.
A Thumb-2 utasítások változó hosszúságúak. A processzor a memóriából
mindig 16 bites félszavakat (halfword) olvas. Az első félszó felső 5
bitje dönti el, hogy az utasítás 16 vagy 32 bites-e. Ha a felső 5 bit
értéke 11101, 11110 vagy 11111,
akkor az utasítás 32 bites, és a következő félszó is az utasításhoz
tartozik. Minden más esetben az utasítás 16 bites. Ez a séma teszi
lehetővé a tömör kódolást: a gyakori, egyszerű műveletek 16 biten, a
ritkább, összetettebbek 32 biten érhetők el. Az alábbi táblázat néhány
tipikus utasítást mutat be mindkét csoportból. Két írásmódot
következetesen megkülönböztetünk:
objdump -d kiírja. Minden 16 bites félszó egy hexa szám (a
két bájtja little-endian olvasatban), 32 bites utasításnál az
első félszó áll elöl. A f245 6378 tehát a
45 f2 78 63 bájtsort jelenti.A táblázat minden sora tényleges assembler-kimenetből származik (a forrás és az ellenőrzés módja a Források szakaszban):
| Utasítás | Hossz | Bájtsor (memória) | Félszavak (objdump) | Megjegyzés |
|---|---|---|---|---|
MOVS r0, #1 |
16 bit | 01 20 |
2001 |
R0–R7, 8 bites immediate |
ADDS r2, r0, r1 |
16 bit | 42 18 |
1842 |
R0–R7, flagfrissítés |
CMP r2, #30 |
16 bit | 1E 2A |
2a1e |
R0–R7, 8 bites immediate |
ADD.W r0, r0, r1, LSL #2 |
32 bit | 00 EB 81 00 |
eb00 0081 |
eltolt regiszteroperandus |
MOVW r3, #0x5678 |
32 bit | 45 F2 78 63 |
f245 6378 |
16 bites immediate az alsó félbe, a felső fél 0 |
MOVT r3, #0x1234 |
32 bit | C1 F2 34 23 |
f2c1 2334 |
16 bites immediate a felső félbe, az alsó marad |
A 0x12345678 konstans egyetlen
utasításba nem kódolható. A MOV.W csak „módosított
immediate”-et fogad el: egy 8 bites értéket eltolva, vagy bizonyos
ismétlődő bájtmintákat (0x00XY00XY, 0xXYXYXYXY
stb.). Az assembler a MOV.W r3, #0x12345678 sort ezért
elutasítja. A teljes 32 bites konstanst a MOVW +
MOVT pár tölti be. A korábbi kiadás itt egy
4F F0 78 56 bájtsort közölt; ez valójában a
MOV.W r6, #0x3e000000 utasítás.
A 16 bites utasítások előnye a kisebb kódméret, hátrányuk a
korlátozott regiszter- és immediate-elérés. A 32 bites utasítások
nagyobb rugalmasságot adnak, de négy bájtot foglalnak. A fordítóprogram
és az assembly-programozó feladata eldönteni, mikor melyiket használja.
A MOVS r0, #1 16 biten elfér, de a
MOV r0, #0x1234 már 32 bites kódolást igényel (az assembler
MOVW-t választ).
Az APSR (Application Program Status Register) egy 32
bites regiszter, amelynek néhány bitje van definiálva. A legfontosabbak
az NZCV-jelzők: N (negative), Z (zero), C (carry) és V
(overflow). Ezek megegyeznek az AArch64-ben megismert szemantikával, de
elérésük módja eltér. Az APSR-t az MRS és MSR
utasításokkal lehet olvasni, illetve írni, közvetlenül nem
címezhető.
| Bit | Név | Jelentés |
|---|---|---|
| 31 | N | negatív eredmény |
| 30 | Z | nulla eredmény |
| 29 | C | átvitel (carry) vagy kölcsön (borrow) |
| 28 | V | előjeles túlcsordulás |
A Thumb-2 utasítások többsége csak akkor frissíti a jelzőket, ha az
utasítás neve S utótagot kap. Például az
ADDS frissíti az NZCV-t, az ADD nem. A
CMP utasítás mindig frissíti a jelzőket, mert az
összehasonlítás lényege a flagbeállítás. A 16 bites Thumb utasításoknál
gyakran az S utótag implicit: az ADDS r2, r0, r1 16 bites
formája mindig frissít, míg a 32 bites ADD.W
alapértelmezésben nem frissít, csak ADDS.W esetén. Ez a
látszólagos következetlenség a történelmi fejlődés eredménye, és a
programozónak tudatosan kell kezelnie.
Munkakönyvtár: ~/arm-labor/cm/a062/ —
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/cm/a062
cd ~/arm-labor/cm/a062Az alábbi assembly rutin bemutatja a Thumb-2 kódolás kevert
használatát. A példa összeszerelhető az arm-none-eabi-as
assemblerrel, és az objdump segítségével ellenőrizhető a
kódolás hossza.
.syntax unified
.thumb
.global lab_thumb2_mix
.text
lab_thumb2_mix:
MOVS r0, #10 @ 16 bites: R0 = 10
MOVS r1, #20 @ 16 bites: R1 = 20
ADDS r2, r0, r1 @ 16 bites: R2 = 30, NZCV frissül
CMP r2, #30 @ 16 bites: Z = 1
BEQ lab_ok @ 16 bites: elágazás, ha Z = 1
MOVW r3, #0x5678 @ 32 bites: az alsó fél, a felső fél nulla
MOVT r3, #0x1234 @ 32 bites: a felső fél beállítása
lab_ok:
BX lr @ 16 bites: visszatérésAssemble és disassemble:
arm-none-eabi-as -mcpu=cortex-m3 -mthumb -o lab-thumb2-mix.o lab-thumb2-mix.s
arm-none-eabi-objdump -d lab-thumb2-mix.oKimenet a helyettesítő eszközlánccal (a Clang
integrált assemblere,
-target thumb-freestanding-eabi -mcpu=cortex-m3, és a könyv
tools/thumb-dump.py capstone-alapú disassemblere). A
második oszlop az objdump-féle félszóalak, a szögletes zárójelben a
memóriabeli bájtsor áll. A GNU objdump ugyanezeket a
kódokat adja, csak az immediate-eket tízes számrendszerben írja
(#10, #22136 @ 0x5678), a szűk elágazást pedig
beq.n alakban:
00000000 <lab_thumb2_mix>:
0: 200a [0a 20 ] movs r0, #0xa
2: 2114 [14 21 ] movs r1, #0x14
4: 1842 [42 18 ] adds r2, r0, r1
6: 2a1e [1e 2a ] cmp r2, #0x1e
8: d003 [03 d0 ] beq #0x12
a: f245 6378 [45 f2 78 63] movw r3, #0x5678
e: f2c1 2334 [c1 f2 34 23] movt r3, #0x1234
00000012 <lab_ok>:
12: 4770 [70 47 ] bx lr
A disassembly lista jól mutatja a 2 bájtos (16 bites) és 4 bájtos (32
bites) utasításokat. A 0x12345678 konstans egyetlen
MOV.W utasításban nem kódolható; a MOVW az
alsó, a MOVT a felső 16 bitet állítja be. Mindkettő 4
bájtos. A beq.n utasítás 16 bites, a .n utótag
a szűk kódolásra utal. A BX lr szintén 16 bites.
Ellenőrzési feltétel: Az objdump
kimenetében a movw és movt 4–4 bájtot
foglaljon, a többi utasítás 2 bájtot. A beq.n célja a
0x12 eltolású lab_ok. A megadott bemeneti
konstansokkal a branch átugorja a két konstansbetöltést: visszatéréskor
R0=10, R1=20, R2=30, Z=1; R3 értékét ez az útvonal nem változtatja meg.
(A rutint emulátorban lefuttatva, R3 = 0xdead
kezdőértékkel, pontosan ez az állapot adódott, C = 1-gyel
együtt, mert a CMP 30, 30 kivonás nem kér kölcsönt.)
Hiba 1: High regiszter használata 16 bites
utasításban.
Ha a programozó MOVS r8, #1 utasítást ír, az assembler
hibát jelez, mert a 16 bites MOVS csak R0–R7-et ér el.
Ellenpróba: használjunk MOV.W r8, #1 utasítást, vagy
töltsük be egy low regiszterbe és mozgassuk át.
Tünet → hipotézis → adatgyűjtés: „Assembler error:
invalid register” → valószínűleg high regiszter 16 bites utasításban →
nézzük meg az utasításkészlet-kézikönyvben az elérhető
regisztereket.
Hiba 2: Immediate túlcsordulás.
A MOVS r0, #0x100 utasítás 16 biten nem kódolható, mert a
16 bites MOVS immediate csak 8 bites. Az assembler 32 bites
MOV.W-t javasol, vagy hibát jelez. Ellenpróba:
MOV.W r0, #0x100 használata.
Tünet → hipotézis → adatgyűjtés: „Immediate out of
range” → a 16 bites kódolás korlátja → ellenőrizzük az utasítás 32 bites
változatát.
Hiba 3: PC-relatív számítás figyelmen kívül hagyása.
Az ADR r0, cimke és a PC-relatív LDR az
Align(PC, 4) értékből számol, ahol a PC az ADR
saját címe + 4. Ha a ADR egy 4-gyel nem osztható (csak
2-vel osztható) címen áll, a kiinduló érték az utasítás címe + 2. A kézi
eltolásszámításnál mindkét lépést figyelembe kell venni. Ellenpróba:
disassemble, és ellenőrizzük a betöltött címet.
(magyarázó, alapszint — cél: megértés-ellenőrzés) Ismertesse a Cortex-M3 regisztereit (R0–R15), és térjen ki az SP, LR, PC konvencionális szerepére! Miért nincs a Cortex-M3-nak 64 bites regiszterpárja, mint az AArch64 Xn/Wn nézete?
(implementációs, középszint — cél: rutinépítés)
Írjon Thumb-2 assembly rutint, amely két 32 bites számot összead, és az
eredményt az R0 regiszterbe teszi! A carry flaget (C) egy külön
regiszterbe (R1) mentse el 0 vagy 1 értékkel! Használjon
ADDS és ADC utasításokat! A rutin végén
BX lr-rel térjen vissza.
(hibakeresési/mérési, középszint — cél: rutinépítés) Adott az alábbi objdump kimenet. Azonosítsa, mely utasítások 16 és melyek 32 bitesek! Számítsa ki a teljes kódméretet bájtban! Melyik utasítás töltene be nagyobb immediate-et, és miért?
0: 2305 movs r3, #5
2: 240a movs r4, #10
4: 191b adds r3, r3, r4
6: f04f 5568 mov.w r5, #0x3a000000
a: 429d cmp r5, r3
c: d101 bne.n 12
e: 2000 movs r0, #0
10: e000 b.n 14
12: 2001 movs r0, #1
14: 4770 bx lr
1. megoldás — Segítség. Nézd meg a fejezet eleji regisztertáblázatot, és gondold át: egy nézetpárhoz (mint Xn/Wn) két ELTÉRŐ szélességű hardveres tárolóra lenne szükség ugyanahhoz a fizikai regiszterhez — van-e ilyen a Cortex-M3-ban?
1. megoldás — Teljes megoldás. - R0–R12: általános
célú, szabadon használható. R0–R7 „low”, R8–R12 „high”.
- R13 (SP): veremmutató, a verem tetejére mutat.
- R14 (LR): visszatérési cím tárolása BL esetén.
- R15 (PC): programszámláló. Thumb utasításból olvasva az aktuális
utasítás címe + 4; a PC-relatív betöltés és az ADR ennek
4-re lefelé igazított értékét használja. - Nincs 64 bites regiszter,
mert az Armv7-M csak 32 bites általános célú regisztereket definiál. Az
AArch64-ben az Xn a 64 bites, Wn a 32 bites nézet ugyanazon
regiszternek, de itt minden regiszter fixen 32 bites. - Indoklás:
nézetpárra azért nincs szükség, mert nincs 64 bites regiszter, amelynek
egy része lehetne. Ez nem jelenti, hogy a 64 bites műveletek teljesen
hiányoznának: az UMULL/SMULL 64 bites
szorzatot ír két regiszterbe, az LDRD/STRD
duplaszót mozgat, csak az eredmény itt mindig regiszterpárban
keletkezik.
2. megoldás — Segítség. Az ADDS
beállítja a C jelzőt, az ADC pedig egy harmadik
operandusként hozzáadja azt egy másik összeadáshoz — hogyan használható
ez arra, hogy a C értékét (0 vagy 1) egy sima regiszterbe “csapold
ki”?
2. megoldás — Teljes megoldás.
.syntax unified
.thumb
.global lab_add_carry
.text
lab_add_carry:
ADDS r0, r0, r1 @ összeadás, flagfrissítés
MOVS r1, #0 @ R1 = 0
ADC r1, r1, #0 @ R1 = R1 + 0 + C
BX lrADDS végzi az összeadást, beállítja a C flaget, ha
átvitel keletkezett.MOVS r1, #0 az N és Z jelzőt frissíti, a C-t
nem módosítja (immediate forrású MOVS
esetén nincs eltolás, amely C-t adhatna), ezért a carry megmarad a
következő utasításig.ADC a carry figyelembevételével ad hozzá 0-t, így R1
a C flag értékét veszi fel.ADC r1, r1, #0 32 bites utasítás:
az immediate-es ADC-nek nincs 16 bites kódolása, a
disassembly f141 0100-t mutat (bájtsor:
41 f1 00 01). A rutin így 2 + 2 + 4 + 2 = 10 bájt. Ha 16
bites alakot szeretnénk, a regiszteres ADCS r1, r1, r1
megfelel (R1 = 0 + 0 + C), de az a jelzőket is írja.3. megoldás — Segítség. Az objdump bal oszlopában a bájt-cím ugrása (pl. 0→2, 2→4 vs. 4→a) közvetlenül elárulja, melyik utasítás hány bájtos — nem kell a mnemonicokat fejből tudni hozzá.
3. megoldás — Teljes megoldás.
- 16 bites utasítások: movs r3, #5,
movs r4, #10, adds r3, r3, r4,
cmp r5, r3, bne.n, movs r0, #0,
b.n, movs r0, #1, bx lr. - 32
bites utasítás: mov.w r5, #0x3a000000, kódja
f04f 5568 (bájtsor: 4f f0 68 55). - Teljes
kódméret: 9 db 16 bites (18 bájt) + 1 db 32 bites (4 bájt) = 22 bájt;
ezt a címoszlop is mutatja (0x14 + 2 = 0x16 = 22). - A
mov.w azért 32 bites, mert a 0x3a000000 immediate nem fér
el 16 biten; a 16 bites MOVS csak 8 bites immediate-et
kódol. A 32 bites alak a 0x3a000000-t módosított
immediate-ként ábrázolja. A második félszó 5568 mezői:
imm3 = 101, Rd = 0101 (R5),
imm8 = 0x68; az első félszó i bitje 0. Az
imm12 = 0:101:01101000 = 0x568 felső bitjei nem nullák,
ezért elforgatott alakról van szó: az alapérték
1:1101000 = 0xE8, a forgatás
imm12[11:7] = 01010 = 10 bit, és
0xE8 ROR 10 = 0x3A000000. Ugyanez a dekódolás a korábbi
kiadás f04f 4561 sorára
0xE1 ROR 8 = 0xE1000000-t adna, ezért az a sor nem a
0x3a000000-t töltötte volna be.
- A bne.n és b.n szűk elágazások, 16
bitesek.
A Thumb-2 ISA 16 és 32 bites utasításokat kever, lehetővé téve a
tömör kódméretet és a rugalmas regiszterelérést. A Cortex-M3 16 darab 32
bites regiszterrel rendelkezik: R0–R12 általános, R13 SP, R14 LR, R15
PC. Nincs Xn/Wn nézetpár, mert nincsenek 64 bites regiszterek; 64 bites
eredményt adó utasítások (UMULL, SMULL,
LDRD/STRD) viszont vannak, regiszterpárral. A
regiszterszélesség, az eredményszélesség és az utasításhossz három külön
fogalom. A PC olvasáskor az aktuális utasítás címe + 4. Az APSR
tartalmazza az NZCV-jelzőket, amelyeket az S utótagú utasítások és a
CMP frissítenek. A 16 bites utasítások jellemzően R0–R7-et
érnek el, a 32 bitesek a teljes regiszterkészletet. A kódolás hosszát az
első félszó felső 5 bitje dönti el.
Továbbhasznált definíciók: - Thumb-2: 16 és 32 bites utasításokat egyaránt tartalmazó ISA, az Armv7-M kötelező utasításkészlete. - APSR: alkalmazásprogram állapotregisztere, amely az NZCV-jelzőket tárolja. - low/high regiszter: R0–R7 (low) és R8–R12 (high); a 16 bites utasítások jellemzően csak a low regisztereket érik el.
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.
developer.arm.com → support.arm.com
átirányítás megerősített); ugyanaz a JS-renderelési korlát érvényes,
mint fent.qemu-system-arm -machine mps2-an385 használata.
Tartalmilag ellenőrizve, de verzió-megjegyzéssel: a
master ág dokumentációja (közös verziókorlát).Ellenőrzési hiányok, amelyek a fenti ellenőrzés után is
ténylegesen fennmaradnak: a QEMU mps2-an385
machine pontos Thumb-2 utasítástámogatása és az
arm-none-eabi-as verziófüggő viselkedése ebben a
folyamatban nem került ellenőrzésre. A kódolási táblázat, a kidolgozott
példa listája, a 3. feladat listája és a 2. megoldás kódja a
helyettesítő eszközlánccal készült: a Clang integrált assemblere
(zig cc -target thumb-freestanding-eabi -mcpu=cortex_m3), a
disassembly pedig a capstone-alapú tools/thumb-dump.py. A
16 és 32 bites kódolás azonos ISA-specifikációt követ, ezért a bájtoknak
GNU as-sel is egyezniük kell; a GNU objdump
kiírási formája (decimális immediate-ek, .n/.w
utótagok) eltér. A lektor GNU eszközökkel végzett ellenőrzése a
42 18, 00 EB 81 00 és a
f04f 4561 = mov.w r5, #0xe1000000 értéket ugyanígy adta. A
lab_thumb2_mix és a lab_add_carry rutint
Unicorn-emulátorban is lefuttattuk; a közölt regiszter- és jelzőállapot
ebből származik.