ARM assembly programozás C-fejlesztőknek: AArch64-alkalmazásszinttől Cortex-M bare-metalig

Tartalomjegyzék

Ugrás az olvasói útmutatóhoz

I. RÉSZ – AArch64: alkalmazásszintű ARM assembly Linux alatt

1 Olvasói útmutató: kinek szól a könyv, és hogyan olvasd

2 Bevezetés: hardver, ISA és a gép nyelve

3 Fejlesztői környezet és labor: eszközlánc, QEMU, build-munkafolyamat

4 Az első program: regiszterek, ELF és a Linux syscall-felszín

5 Adatmozgatás, aritmetika és állapotjelzők

6 Vezérlésátadás: elágazás és ciklusok C-szemmel

7 Memóriacímzés és adatszerkezetek

8 Függvények és az AAPCS64 hívási konvenció

9 Bitmanipuláció és logikai műveletek

10 C-assembly interoperabilitás és fordítóprogram-elemzés

11 Debugging, disassembly és instrumentáció

12 Lebegőpontos aritmetika és NEON/SIMD vektorizáció

13 Teljesítmény-tudatos kód: cache, pipeline és mérés

14 Biztonsági szemlélet: verem, védelem és ROP

15 Záróprojekt I: teljesítménykritikus assembly modul C programban

II. RÉSZ – Cortex-M: bare-metal mikrokontroller-programozás

16 Thumb-2, a Cortex-M futási modell és a minimal runtime

17 Kivételek és megszakítások: NVIC

18 Memory-mapped perifériák és GPIO

19 Megszakítás-vezérelt I/O és driver minta

20 Záróprojekt II: interrupt-vezérelt Cortex-M firmware-modul

1 Olvasói útmutató: kinek szól a könyv, és hogyan olvasd

1.1 A könyv használata: C-előismeret, tanulási kimenetek, belépési pontok

Motiváció

Mielőtt bármelyik fejezetbe belekezdenél, két kérdésre érdemes néhány perc alatt válaszolni: van-e meg a szükséges C-alapom, és hol érdemes belépnem a könyvbe. Ha ezt kihagyod, két tipikus veszteség ér: vagy olyan alfejezeteken rágsz át heteket, amelyeket már tudsz (például a számrendszereket), vagy olyan szerződéseket (hívási konvenció, NVIC-regiszterek) ugrasz át „ismerősnek tűnik” alapon, amelyeket a könyv később mindenhol feltételez. Ez az alfejezet ezért nem technikai tananyag, hanem útvonalterv: önellenőrző listát, konkrét tanulási kimeneteket és belépési pontokat ad.

Definíció: előismeret-küszöb és belépési pont

Előismeret-küszöb: a minimális, cselekvésben mérhető C-tudás (nem „közepes szintű C”, mert az nem mérhető), amellyel a könyv technikai alfejezetei a C nyelv újratanítása nélkül értelmezhetők.

Belépési pont: a könyvtérkép alfejezete, ahonnan az olvasó a meglévő tudásához igazítva kezdheti a tanulást — a küszöb mindenkire érvényes, a belépési pont csak a kezdőpozíciót választja meg.

Önellenőrző lista: a szükséges C-tudás

A következő állításoknak kell igaznak lennie rád. Ha valamelyik nem az, azt előbb pótolni kell; a könyv ezeket nem tanítja újra, csak használja.

Ami nem része a küszöbnek: assembly, regiszterek, ISA, NEON/SIMD, megszakításkezelés, bare-metal fejlesztés, QEMU. Ezeket a könyv nulláról építi fel.

Tanulási kimenetek

Rész A rész végére önállóan…
I. rész – AArch64 A64 assembly függvényt írsz, assemblersz, linkelsz és GDB-vel hibakeresel; a függvényt C-ből hívod az AAPCS64 szerződés szerint; felismered a fordító tipikus optimalizálási mintázatait; egy skaláris ciklust NEON-nal vektorizálsz, és a gyorsulást Amdahl-korláttal értelmezed; felismered a verem-biztonsági mintákat (canary, ASLR, NX, PAC).
II. rész – Cortex-M Bare-metal startup kódot és linker scriptet írsz; vektortáblát és NVIC-vezérelt megszakításkezelőt állítasz be; memory-mapped perifériadrivert (GPIO, UART, timer) implementálsz; a firmware-t qemu-system-arm alatt reprodukálhatóan futtatod és mérőponttal igazolod.

Belépési pontok és átugorható szakaszok

Olvasóprofil Belépési pont Átugorható Nem átugorható
Assembly-kezdő, C-tudás megvan Miért assembly: amit a C elrejt — semmi
ISA/architektúra-alapokat ismer, assemblyben kezdő Az A64 regiszterkészlet áttekintése Számrendszerek és bitszintű reprezentáció (az elején lévő belépőteszt alapján), részben Számítógép-architektúra fogalmai I. rész: Az AAPCS64 regiszterszerződés; II. rész: az AAPCS32 hívási szabályai (A-profile és M-profile különbségei) és NVIC konfiguráció: engedélyezés és prioritás
AArch64-et ismer, csak Cortex-M kell Thumb-2, a Cortex-M futási modell és a minimal runtime, az A-profile és M-profile különbségei áthidaló példájával I. RÉSZ – AArch64 nagy része; Az AAPCS64 regiszterszerződés csak összehasonlításként ajánlott az AAPCS32 hívási szabályai (alkalmazásuk: A-profile és M-profile különbségei, ISR írása: C és assembly vegyesen), Memóriatérkép és a bare-metal futási modell, NVIC konfiguráció: engedélyezés és prioritás

Az átugorható szakaszok is alap-ISA-ismeretek: ha a kettes komplemens, a bináris jelölés vagy az ISA/mikroarchitektúra megkülönböztetése már biztos, a Számrendszerek és bitszintű reprezentáció és a Számítógép-architektúra fogalmai valóban kihagyható.

Az át nem ugorható szerződések más okból kötelezők, és útvonalanként különböznek:

Útvonal Át nem ugorható szerződés Nem előfeltétel
AArch64 (I. rész) Az AAPCS64 regiszterszerződés az NVIC és az AAPCS32
Cortex-M (II. rész) az AAPCS32 hívási szabályai (A-profile és M-profile különbségei, ISR írása: C és assembly vegyesen), Memóriatérkép és a bare-metal futási modell, NVIC konfiguráció: engedélyezés és prioritás az AAPCS64 (csak összehasonlításként ajánlott)

Egyik sem a könyv saját konvenciója:

Ezeket el kell választani a választott labor saját szerződésétől — például a QEMU-célgéptől (mps2-an385) és az azon érvényes perifériacímektől —, amely csak a könyv példáira érvényes. A választott útvonal szerződését azért kell tapasztalt olvasóként is végigvenni, mert az útvonal későbbi példái rá épülnek, nem azért, mert a könyv találta ki: az I. rész C-ből hívható assembly függvényei az AAPCS64-re, a II. rész megszakításos laborjai az AAPCS32-re és az NVIC-re.

A kizárólag Cortex-M-et tanuló olvasó kötelező PCS-belépője az AAPCS32 (Procedure Call Standard for the Arm Architecture), a 32 bites Arm architektúra, köztük az M-profile eljáráshívási szabványa. Az Az AAPCS64 regiszterszerződés ilyenkor hasznos összehasonlítás, de nem helyettesíti az AAPCS32-t: eltér a regiszterszélesség (32 vs. 64 bit), az argumentumregiszterek köre (R0–R3 vs. X0–X7), a hívott által megőrzendő regiszterek halmaza (R4–R11 vs. X19–X28) és a verem igazítási követelménye (nyilvános interfészen 8 vs. 16 bájt).

Hogyan használd a feladatokat és a megoldásokat

Ez az útmutató a könyv minden feladatsorára és megoldására érvényes; az alfejezetek nem ismétlik meg.

Kidolgozott példa: előismeret-önellenőrző build

Munkakönyvtár: ~/arm-labor/a64/a000/ — 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/a000
cd ~/arm-labor/a64/a000

Az alábbi háromfájlos program a küszöb elemeit (mutató, struktúra, többfájlos build) gyakorolja: két külön fordítási egységből (lab-main.c, lab-sum.c) és egy közös fejlécből (lab-sum.h) áll. A fejléc a struct pair típust és a sum_values deklarációját adja, a függvény definíciója egyetlen fordítási egységben, a lab-sum.c-ben van. Célja nem a funkcionalitás, hanem hogy te tudd összeilleszteni és lefordítani.

/* lab-sum.h */
#ifndef LAB_SUM_H
#define LAB_SUM_H

struct pair { int key; int value; };

int sum_values(const struct pair *p, int n);   /* deklaráció */

#endif
/* lab-sum.c */
#include "lab-sum.h"

int sum_values(const struct pair *p, int n) {  /* definíció */
    int s = 0;
    for (const struct pair *q = p; q < p + n; ++q) s += q->value;
    return s;
}
/* lab-main.c */
#include <stdio.h>
#include "lab-sum.h"

int main(void) {
    struct pair v[3] = {{1,10},{2,20},{3,30}};
    printf("%d %zu\n", sum_values(v, 3), sizeof(struct pair));
    return 0;
}

A Makefile mindkét objektumot linkeli, és mindkét objektum függőségei között szerepel a fejléc:

# Makefile
CC = gcc
CFLAGS = -Wall -Wextra -O2 -std=c11
OBJS = lab-main.o lab-sum.o

.PHONY: all clean
all: lab-a000

lab-a000: $(OBJS)
    $(CC) $(CFLAGS) -o $@ $^

lab-main.o: lab-main.c lab-sum.h
    $(CC) $(CFLAGS) -c -o $@ $<

lab-sum.o: lab-sum.c lab-sum.h
    $(CC) $(CFLAGS) -c -o $@ $<

clean:
    rm -f lab-a000 $(OBJS)

Futtatás (target: x86_64 host, natív gcc; a kód hordozható, AArch64-en azonos kimenet várható):

make
./lab-a000
echo $?

Várt kimenet: 60 8, majd a kilépési állapot 0. Az 60 a három value összege; a 8 a struct pair mérete ezen az ABI-n (két 4 bájtos int, igazítás miatt hézag nélkül). A pontos méret ABI-függő; a sizeof kiíratása éppen ezért része az ellenőrzésnek.

Elfogadási próbák. A futó program önmagában még nem bizonyítja, hogy a többfájlos build szerkezete helyes. Két próbának is teljesülnie kell:

  1. A definíció valóban a másik objektumban van. Linkeld a programot a lab-sum.o nélkül. Ez egy negatív próba: itt a nem nulla kilépési állapot az elvárt eredmény.

    make -B OBJS=lab-main.o
    echo "make kilépési állapot: $?"    # elvárt: nem nulla (GNU Make: 2)

    A -B kapcsoló kötelező. Egy sikeres make után a lab-a000 újabb, mint minden függősége, és a Make az időbélyegek alapján dönt: az OBJS lista megváltozása önmagában nem kényszerít új linkelést. A -B nélkül a parancs Nothing to be done for 'all'. üzenettel, 0-s állapottal érne véget, és a próba semmit sem bizonyítana. Ugyanezt a próbát a Make megkerülésével is elvégezheted: gcc -o lab-missing lab-main.o.

    Valódi linkerhibát kell kapnod, nem fordítási hibát: a lab-main.c a fejléc deklarációja miatt hibátlanul lefordul, csak a definíció hiányzik.

    gcc -Wall -Wextra -O2 -std=c11 -c -o lab-main.o lab-main.c
    gcc -Wall -Wextra -O2 -std=c11 -o lab-a000 lab-main.o
    /usr/bin/ld: lab-main.o: in function `main':
    lab-main.c:(.text.startup+0x39): undefined reference to `sum_values'
    collect2: error: ld returned 1 exit status
    make: *** [Makefile:10: lab-a000] Error 1
    make kilépési állapot: 2

    (A +0x39 eltolás fordítóverziótól és kapcsolóktól függ.) Ezután térj vissza a helyes objektumlistához, és ellenőrizd újra a kimenetet:

    make -B && ./lab-a000                # elvárt: 60 8
  2. A fejléc módosítása az érintett objektumokat újrafordítja. Egy sikeres make után:

    touch lab-sum.h
    make

    Mindkét gcc -c sornak (lab-main.o és lab-sum.o) meg kell jelennie, majd a linkelésnek. Ellenpróbaként touch lab-sum.c után csak a lab-sum.o fordul újra.

A két eltéréstípust érdemes külön kezelni. Ha az összeg nem 60, vagy bármelyik elfogadási próba eltér a fentitől, a build szerkezete vagy a küszöb valamelyik eleme bizonytalan; azt pótolni kell a továbblépés előtt. Ha viszont csak a második szám tér el a 8-tól, az nem build-hiba, hanem a cél-ABI tulajdonsága. Ilyenkor azt kell ellenőrizni, hogy a kapott méret megfelel-e az adott ABI int-méretének és igazítási szabályának (két int mező, hézag nélkül: 2 * sizeof(int)).

Tipikus hibák és ellenpróbák

Rövid összefoglaló

Az előismeret-küszöb az a cselekvésben mérhető C-tudáshalmaz (mutató, struktúra, Makefile, fordítási lánc), amely nélkül a könyv technikai fejezetei nem értelmezhetők. A belépési pont a könyvtérkép azon alfejezete, ahonnan a küszöböt teljesítő olvasó a tudásához igazítva indul: assembly-kezdőnek Miért assembly: amit a C elrejt, ISA-alapokat ismerőnek Az A64 regiszterkészlet áttekintése, AArch64-ismerőnek Thumb-2, a Cortex-M futási modell és a minimal runtime. Az át nem ugorható szerződések útvonalanként különböznek. Az AArch64-útvonalon ez Az AAPCS64 regiszterszerződés, az NVIC itt nem előfeltétel. A Cortex-M-útvonalon az AAPCS32, a memóriatérkép és az NVIC konfiguráció: engedélyezés és prioritás; az AAPCS64 itt csak összehasonlításként hasznos, és nem helyettesíti az M-profile hívási szabályait. Egyik sem a könyv saját konvenciója: az AAPCS64 és az AAPCS32 eljáráshívási szabvány (PCS), vagyis az ABI hívási szabályokat rögzítő része; az NVIC architekturális komponens, amelyhez implementáció- és panelspecifikus beállítások társulnak. Ezeket el kell választani a választott labor saját szerződésétől.

Források és ellenőrzési hiányok

A könyv egészében hivatkozott elsődleges források élő linkjét és aktuális verzióját ennek az átdolgozásnak a részeként ellenőriztük:

Ellenőrzési hiányok, amelyek a fenti ellenőrzés után is fennmaradnak: (1) a telepített Ubuntu 24.04 LTS csomagolt GCC/binutils/GDB/QEMU pontos verziószáma ebben a munkafolyamatban nincs lekérdezve; (2) a qemu-system-arm -machine mps2-an385 memóriatérkép mezőnevei a QEMU forráskódjából (hw/arm/mps2.c) ellenőrizendők, ezt a fenti dokumentációs linkek önmagukban nem igazolják; (3) a lab-a000 példa kimenete (60 8), a lab-sum.o elhagyásakor kapott undefined reference hiba és a fejlécmódosítás utáni újrafordítás x86_64 hoston, GCC 13.3.0-val (Ubuntu 24.04) mért; más fordítón és AArch64-en nem mértük, és a sizeof(struct pair) értéke az adott ABI-tól függ, ezért a hallgató laborjában validálandó.

Forrás- és ellenőrzésállapot-jelölések

Az alfejezetek „Források és ellenőrzési hiányok” szakaszai ezekre a jelölésekre hivatkoznak, és csak a saját példájuk feltételeit sorolják fel. A forrásállapot a fenti listára épül (ellenőrzés: 2026-10):

Forrás Állapot Közös korlát
Arm-dokumentáció (developer.arm.com: DDI0487, DDI0403E, architektúra-oldalak) a link él JavaScript-renderelés miatt a fejezetszám és a kiadásbetű nem ellenőrzött; a konkrét fejezetet a laborban kell visszakeresni
AAPCS64 ellenőrizve: 2025Q4 (2026-01-23), Release az SME-részek Beta minőségűek
AAPCS32 a link él az aktuális kiadás nincs ellenőrizve
GNU binutils, GCC, GDB kézikönyv ellenőrizve: binutils 2.47, GCC 16.2; GDB „current” a GDB „current” fejlesztői pillanatkép; a telepített eszközök verziója eltérhet
QEMU dokumentáció (docs/master) ellenőrizve a master ág dokumentációja, nem egy kiadott verzióé; a telepített (Ubuntu 24.04: QEMU 8.2.2) változat eltérhet
„dokumentációs kiindulópont” megnevezett forrás, ebben a kiadásban nem megnyitott tartalmi állítás nem épül rá ellenőrzöttként

A mérésállapot jelölései eszközverzióhoz és bizonyítékhoz kötöttek:

Jelölés a szövegben Eszközök és verzió Bizonyíték
„helyettesítő eszközlánccal mérve” (I. rész) zig cc 0.17.0 (Clang + ld.lld, -target aarch64-linux-musl), lab-run.py 1.1 (unicorn 2.1.4) kísérőcsomag v1.0, tests/run.sh substitute, logs/substitute-1.0-20261020.log
„helyettesítő eszközlánccal mérve” (II. rész) zig cc 0.17.0 (-target thumb-freestanding-eabi -mcpu=cortex_m3), cm-run.py 1.2 (unicorn 2.1.4, capstone 5.0.7, pyelftools 0.33) ugyanott; a II. rész laboronkénti állapota az A-profile és M-profile különbségei közös táblázatában
host-mérés („host gcc”, „GNU ld/readelf 2.42”) x86_64, Ubuntu 24.04, gcc 13.3, GNU binutils 2.42 a helyben közölt kimenet; a csomag hostpróbái ugyanabban a naplóban
fő út (GNU eszközlánc + QEMU) aarch64-linux-gnu-*, arm-none-eabi-*, qemu-aarch64, qemu-system-arm -machine mps2-an385 csak ott állítjuk, ahol a szöveg kifejezetten így jelöli; a kísérőcsomag tests/run.sh gnu-qemu naplója; a v1.0 csomagban ilyen napló még nincs
„levezetett”, „illusztratív”, „hipotetikus” — a levezetés maga; nem mérés