16.4 Reset és boot-folyamat: vektortábla

16.4.1 Motiváció: miért nem indul el a firmware?

Egy új Cortex-M3 alapú panel bring-upja során a fejlesztő lefordítja a main()-t, letölti a lapra, és semmi nem történik: nincs soros kimenet, a debugger nem áll meg a main első során, néha a mag HardFault-ba kerül. A leggyakoribb gyökér nem a C-kódban, hanem a címtér legelső nyolc bájtjában van. A Cortex-M mag reset után nem egy fix, architektúrába égetett címről kezd utasítást végrehajtani, hanem a 0x00000000-tól induló vektortáblából olvassa ki a kezdeti veremmutatót és a reset-handler címét. Ha ez a nyolc bájt hibás, a processzor vagy rossz veremmel indul, vagy a rossz címre ugrik, és minden további kód irreleváns.

Ez az alfejezet a boot-folyamat első, hardveresen definiált szerződését építi fel: hogyan néz ki a minimális vektortábla, hogyan kerül a flash elejére linker scripttel, mit tesz a reset-handler a .data/.bss kezelése előtt, és hogyan ellenőrizhető mindez objdump-pal. A .data másolása és a .bss törlése a startup kód témája; itt csak a belépési pontig jutunk el.

16.4.2 Definíció: vektortábla és reset-szekvencia

Vektortábla (vector table): a címtér elején elhelyezkedő, 4 bájtos szavak rendezett listája, amely a processzor kivétel- és megszakításkezelőinek belépési címeit tartalmazza. Armv7-M esetén az első szó nem ugrási cím, hanem a kezdeti veremmutató (initial stack pointer) értéke. A tábla első 16 bejegyzése fix sorrendű rendszerkivétel, ezután következnek az NVIC által kezelt külső megszakítások (IRQ0, IRQ1, …).

A reset-szekvencia hardveresen, utasítás végrehajtása nélkül történik:

Lépés Művelet Forrás
1 SP ← mem[0x00000000] vektortábla 0. szava
2 PC ← mem[0x00000004] (bit0 = Thumb jelző) vektortábla 1. szava
3 LR ← 0xFFFFFFFF visszatérési cím hiányát jelzi

A 0xFFFFFFFF azért fontos, mert a reset-handlerből nincs értelmes visszatérés: nincs hívó, aki visszavárna. Ha a handler BX LR-rel próbál visszatérni, a mag 0xFFFFFFFE-re ugrik, és HardFault lesz belőle. Ezért a reset-handler vagy egy startup rutinra adja tovább a vezérlést, vagy végtelen ciklusba kerül.

A 16 rendszerkivétel-bejegyzés sorrendje Armv7-M-en kötelező:

Offset Bejegyzés Megjegyzés
0x00 Initial SP nem kód, hanem adat
0x04 Reset ide ugrik a mag reset után
0x08 NMI nem maszkolható megszakítás
0x0C HardFault minden nem kezelt fault ide fut
0x10 MemManage MPU-sértés
0x14 BusFault buszhiba
0x18 UsageFault érvénytelen utasítás / állapot
0x1C–0x28 Reserved négy szó, 0val kitöltve
0x2C SVCall supervisor call
0x30 DebugMonitor debug kivétel
0x34 Reserved egy szó
0x38 PendSV függőben lévő SVCall
0x3C SysTick rendszer-tick

Egy nem használt bejegyzést 0-val kitölteni veszélyes: ha a kivétel mégis bekövetkezik, a mag a 0x00000000-ra próbál ugrani, és másodlagos HardFaultba fut. A biztonságos minta: minden bejegyzés vagy valódi handlerre mutat, vagy egy közös Default_Handler végtelen ciklusára.

16.4.3 Hogyan kerül a tábla a flash elejére?

A linker script feladata, hogy a .vectors szekciót a FLASH régió ORIGIN-jára, azaz 0x00000000-ra helyezze, és semmilyen optimalizálás ne dobja el. A KEEP(*(.vectors)) direktíva azért kötelező, mert a --gc-sections linkelés a nem hivatkozott szekciókat kigyomlálná — a vektortáblára assembly szinten nem hivatkozik bl utasítás, csak a hardver olvassa.

/* ~/arm-labor/cm/a064/link.ld — minimális linker script a vektortáblához
 * Target: qemu-system-arm -machine mps2-an385 (Cortex-M3)
 * FIGYELEM: a FLASH/RAM ORIGIN és LENGTH a QEMU machine verziójától
 * és a valódi MCU-tól függ; ellenőrizni szükséges.
 */
ENTRY(Reset_Handler)

MEMORY
{
    FLASH (rx)  : ORIGIN = 0x00000000, LENGTH = 4M
    RAM   (rwx) : ORIGIN = 0x20000000, LENGTH = 4M
}

_estack = ORIGIN(RAM) + LENGTH(RAM);

SECTIONS
{
    .vectors : {
        KEEP(*(.vectors))
    } > FLASH

    .text : {
        *(.text*)
        *(.rodata*)
    } > FLASH
}

Az ENTRY(Reset_Handler) csak az ELF fejléc e_entry mezőjét állítja be — debugger és bootloader szempontjából hasznos, de nem helyettesíti a vektortáblát. A hardver resetkor kizárólag a 0x00000004-en lévő szót olvassa.

16.4.4 A reset-handler minimális formája

A reset-handler assembly szintű belépési pont. A mag már érvényes SP-vel érkezik, ezért a handler első dolga nem a verem beállítása, hanem a továbbadás a startup kódnak. A .thumb_func direktíva biztosítja, hogy a szimbólumra hivatkozva a linker a cím bit0-ját 1-re állítsa — enélkül a mag páros címet lát, és INVSTATE HardFaultot vált.

/* ~/arm-labor/cm/a064/startup.s — minimális vektortábla és reset-handler
 * ISA: Thumb-2 / Armv7-M
 * Target: qemu-system-arm -machine mps2-an385
 */
    .syntax unified
    .cpu cortex-m3
    .thumb

    .section .vectors, "a", %progbits
    .align 4
    .global __vectors
__vectors:
    .word _estack                 /* 0x00: kezdeti SP       */
    .word Reset_Handler           /* 0x04: Reset            */
    .word NMI_Handler             /* 0x08: NMI              */
    .word HardFault_Handler       /* 0x0C: HardFault        */
    .word 0                       /* 0x10: MemManage        */
    .word 0                       /* 0x14: BusFault         */
    .word 0                       /* 0x18: UsageFault       */
    .word 0                       /* 0x1C: Reserved         */
    .word 0                       /* 0x20: Reserved         */
    .word 0                       /* 0x24: Reserved         */
    .word 0                       /* 0x28: Reserved         */
    .word SVC_Handler             /* 0x2C: SVCall           */
    .word 0                       /* 0x30: DebugMonitor     */
    .word 0                       /* 0x34: Reserved         */
    .word PendSV_Handler          /* 0x38: PendSV           */
    .word SysTick_Handler         /* 0x3C: SysTick          */

    .text
    .thumb_func
    .global Reset_Handler
    .type Reset_Handler, %function
Reset_Handler:
    /* 1) A mag már betöltötte SP-t a vektortábla 0. szavából.   */
    /* 2) Itt még NEM történt meg a .data másolás / .bss törlés. */
    /* 3) Tovább a startup kódra, amely ezt elvégzi.             */
    b    _startup

    .thumb_func
    .global NMI_Handler
    .type NMI_Handler, %function
NMI_Handler:
    b    .

    .thumb_func
    .global HardFault_Handler
    .type HardFault_Handler, %function
HardFault_Handler:
    b    .

    .thumb_func
    .global SVC_Handler
    .type SVC_Handler, %function
SVC_Handler:
    b    .

    .thumb_func
    .global PendSV_Handler
    .type PendSV_Handler, %function
PendSV_Handler:
    b    .

    .thumb_func
    .global SysTick_Handler
    .type SysTick_Handler, %function
SysTick_Handler:
    b    .

    .thumb_func
    .global _startup
    .type _startup, %function
_startup:
    b    .    /* helykitöltő; a .data/.bss kezelés a következő alfejezet */

Az _startup itt még egy helykitöltő végtelen ciklus. A startup kód alfejezetben ez a rutin kapja meg a .data másolását és a .bss törlését, majd onnan bl main következik.

Az, hogy a Reset_Handler nem maga végzi az inicializálást, hanem azonnal továbbadja a vezérlést az _startup-nak, a jelen példaprogram tudatos megoldása: a vektortábla által hivatkozott belépési pont így triviális és stabil marad, miközben a tényleges .data/.bss logika külön, önállóan tesztelhető és bővíthető rutinba kerül.

Az alábbi ábra a teljes boot-folyamatot foglalja össze, a vektortáblától a main()-ig:

Reset- és boot-folyamat: vektortábla → inicializálás → main - A hardveres reset-szekvencia és a Reset_Handler/_startup láncolat összefoglalása.

16.4.5 Kidolgozott példa: build és objektumellenőrzés

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

A példa célja, hogy a vektortábla tényleges bájttartalmát ellenőrizzük, mielőtt a QEMU-ra bíznánk a futtatást.

# ~/arm-labor/cm/a064 munkakönyvtárban
arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb \
    -nostdlib -nostartfiles \
    -T link.ld -Wl,-Map=lab-a064.map \
    -o lab-a064.elf startup.s

# szekciók VMA szerinti ellenőrzése
arm-none-eabi-objdump -h lab-a064.elf

# a vektortábla nyers tartalma
arm-none-eabi-objdump -s -j .vectors lab-a064.elf

# a reset-handler címe és Thumb bitje
arm-none-eabi-nm -n lab-a064.elf | grep -E 'Reset_Handler|_estack'

Kimenet a helyettesítő eszközlánccal (Clang integrált assembler és ld.lld, ugyanezzel a link.ld-vel és startup.s-sel) épített ELF-ből; az objdump -s alakot a könyv eszköze állította elő a .vectors szekció bájtjaiból:

Contents of section .vectors:
 0000 00004020 41000000 45000000 47000000  ..@ A...E...G...
 0010 00000000 00000000 00000000 00000000  ................
 0020 00000000 00000000 00000000 49000000  ............I...
 0030 00000000 00000000 4b000000 4d000000  ........K...M...

Az objdump -s bájtokat ír ki memóriasorrendben, négyesével csoportosítva, ezért minden szót little-endian módon kell visszaolvasni. A 0000 sor első szava 00 00 40 20 → 0x20400000: ez az _estack, a 4 MiB-os SRAM-tartomány kizárólagos felső határa. A második szó 41 00 00 00 → 0x00000041: a Reset_Handler címe a Thumb bittel együtt. Maga a kód a 0x00000040-en kezdődik, közvetlenül a 64 bájtos (16 szavas) vektortábla után, a .text szekció elején. A további handlerek (0x45, 0x47, 0x49, 0x4b, 0x4d) ugyanígy páratlanok, és mind a .text-be mutatnak. A nm -n kimenetében a Thumb-függvények szimbólumértéke szintén páratlan (00000041 T Reset_Handler), mert az ELF az ARM-konvenció szerint a Thumb-bitet a függvényszimbólum értékében tárolja.

A konkrét címeket nem kell kézzel megjósolni: egy másik assembler vagy kapcsoló más utasításhosszt választhat (itt a Clang a b _startup előre mutató ágat 32 bites b.w-ként kódolta, a GNU as választhat 16 bites alakot), és ettől a későbbi handlerek címe eltolódik. Ami fontos, az a tulajdonság. Ezt nem szemmel, hanem programmal ellenőrizzük:

#!/usr/bin/env python3
"""lab-check-vectors.py — a vektortábla tulajdonságainak ellenőrzése egy ELF-en.

Használat: python3 lab-check-vectors.py lab-a064.elf [--relocated] [INDEX=SZIMBÓLUM ...]
Ellenőrzi: (1) van .vectors szekció, mérete szóhatáron van, és legalább a kezdeti
SP-t és a Reset-bejegyzést tartalmazza; (2) a .vectors a 0x00000000 címen kezdődik
(--relocated mellett ez nem kötelező); (3) a tábla bázisa VTOR-kompatibilis:
a 128 bájt és a táblaméretnél nem kisebb legkisebb kettőhatvány közül a nagyobbik
többszöröse; (4) a 0. szó (kezdeti SP) a RAM-régióba esik, vagy annak felső
határa, és 8 bájtra igazított; (5) az 1. szó (Reset) KÖTELEZŐ: nem nulla, páratlan
(Thumb-bit = 1), és a bit0 nélküli címe egy végrehajtható szekcióba esik;
(6) a fenntartott bejegyzések (7-10. és 13. index) nullák; (7) minden további nem
nulla kezelőbejegyzés páratlan, és kódszekcióba mutat (a nulla itt megengedett:
nincs kezelő); (8) minden INDEX=SZIMBÓLUM argumentumra a tábla INDEX-edik szava
létezik, és pontosan a SZIMBÓLUM értéke (pl. 1=Reset_Handler, 24=TIMER0_IRQHandler;
IRQn indexe 16 + n). Függőség: python3-pyelftools.
"""
import struct
import sys
from elftools.elf.elffile import ELFFile

RAM_LO, RAM_HI = 0x20000000, 0x20400000     # a kanonikus 4 MiB-os SRAM
RESERVED = (7, 8, 9, 10, 13)                # Armv7-M: fenntartott kivételszámok

args = [a for a in sys.argv[2:] if a != '--relocated']
relocated = '--relocated' in sys.argv[2:]
fails = 0
def check(ok, msg):
    global fails
    print(('OK   ' if ok else 'HIBA ') + msg)
    fails += not ok

with open(sys.argv[1], 'rb') as f:
    elf = ELFFile(f)
    vec = elf.get_section_by_name('.vectors')
    code = [(s['sh_addr'], s['sh_addr'] + s['sh_size'], s.name)
            for s in elf.iter_sections() if s['sh_flags'] & 0x4]      # SHF_EXECINSTR
    if vec is None:
        check(False, 'nincs .vectors szekció')
        sys.exit(1)
    size, base = vec['sh_size'], vec['sh_addr']
    if size % 4 or size < 8:
        check(False, f'.vectors mérete {size} bájt: nem szóhatáron végződik, '
                     'vagy kevesebb két szónál (SP + Reset)')
        sys.exit(1)
    check(True, f'.vectors mérete {size} bájt ({size // 4} szó)')
    check(relocated or base == 0, '.vectors a 0x00000000 címen' if not relocated
          else f'.vectors áthelyezve, bázis 0x{base:08x}')
    align = 128
    while align < size:
        align *= 2
    # VTOR: a bázis max(128, a táblaméretnél nem kisebb kettőhatvány) többszöröse
    check(base % align == 0, f'táblabázis 0x{base:08x}: {align} bájtra igazított (VTOR)')
    words = struct.unpack(f'<{size // 4}I', vec.data())
    sp = words[0]
    check(RAM_LO < sp <= RAM_HI and sp % 8 == 0,
          f'[0x00] kezdeti SP = 0x{sp:08x} (RAM, 8 bájtra igazított)')
    def in_code(w):
        target = w & ~1
        return target, next((n for lo, hi, n in code if lo <= target < hi), None)
    reset = words[1]
    target, sec = in_code(reset)
    check(reset != 0 and reset & 1 == 1 and sec is not None,
          f'[0x04] Reset (kötelező) 0x{reset:08x}: Thumb-bit={reset & 1}, cél 0x{target:08x} '
          + (f'a(z) {sec} szekcióban' if sec else 'NEM végrehajtható szekcióban'))
    nonzero_reserved = [i for i in RESERVED if i < len(words) and words[i] != 0]
    check(not nonzero_reserved, 'fenntartott bejegyzések (7–10., 13. index): '
          + ('mind 0' if not nonzero_reserved
             else 'NEM nulla: ' + ', '.join(map(str, nonzero_reserved))))
    for i, w in enumerate(words[2:], start=2):
        if w == 0 or i in RESERVED:
            continue
        target, sec = in_code(w)
        check(w & 1 == 1 and sec is not None,
              f'[0x{4 * i:02x}] 0x{w:08x}: Thumb-bit={w & 1}, cél 0x{target:08x} '
              + (f'a(z) {sec} szekcióban' if sec else 'NEM végrehajtható szekcióban'))
    symtab = elf.get_section_by_name('.symtab')
    for arg in args:
        idx, name = arg.split('=', 1)
        idx = int(idx, 0)
        syms = symtab.get_symbol_by_name(name) if symtab else None
        want = syms[0]['st_value'] if syms else None
        if idx >= len(words):
            check(False, f'[{idx}] = {name}: a tábla csak {len(words)} szó, '
                         f'a {idx}. slot hiányzik')
            continue
        got = words[idx]
        check(want is not None and got == want,
              f'[{idx}] = {name}: tábla 0x{got:08x}, szimbólum '
              + (f'0x{want:08x}' if want is not None else 'NINCS'))
    sys.exit(1 if fails else 0)
sudo apt install python3-pyelftools        # egyszer
python3 lab-check-vectors.py lab-a064.elf; echo "kilépési állapot: $?"

Negatív próbák. Az ellenőrzőnek a hibás táblákat rendezett HIBA sorral és nem nulla kilépési állapottal kell elutasítania. A helyettesítő eszközlánccal (Clang + ld.lld) mind kipróbáltuk:

Hibás vagy határeset Eredmény Kilépési állapot
a Reset helyén .word 0 HIBA [0x04] Reset (kötelező) 0x00000000 … 1
a kanonikus 16 szavas tábla, de a labor 24=TIMER0_IRQHandler-t vár HIBA [24] = TIMER0_IRQHandler: a tábla csak 16 szó, a 24. slot hiányzik 1
csonka, 6 bájtos .vectors HIBA .vectors mérete 6 bájt: … 1
áthelyezett tábla 0x20000104-en (--relocated): szóra igazított, de 128-ra nem HIBA táblabázis 0x20000104: 128 bájtra igazított … 1
áthelyezett tábla 0x20000200-on (--relocated) minden sor OK 0

A szóigazítás és a táblabázis igazítása két külön követelmény. Minden bejegyzés 32 bites szó, ezért a táblán belül a szavak 4 bájtos határon állnak; ezt adja a .align 2. A tábla bázisára ennél erősebb szabály vonatkozik, ha a táblát a VTOR (Vector Table Offset Register, 0xE000ED08) segítségével áthelyezzük. Armv7-M-en a bázisnak a tábla méreténél nem kisebb kettőhatványra kell igazítva lennie, de legalább 128 bájtra. A táblaméretet a magban ténylegesen implementált kivételek száma adja: 16 rendszerslot és az implementált IRQ-k; ez a linkelt táblánál nagyobb is lehet. Az ellenőrző a linkelt tábla méretéből számol, ez tehát alsó becslés, a konkrét mag dokumentációjával kell egyeztetni. A könyv kanonikus elhelyezése, a 0x00000000, minden ilyen követelményt teljesít. Áthelyezett táblánál a linker scriptben egy ASSERT(ADDR(.vectors) % 128 == 0, …) jellegű feltétel (a ténylegesen szükséges határral) rögzíti ugyanezt.

A későbbi laborok a bővített vektortáblát ugyanezzel az eszközzel név szerint is ellenőrzik: az INDEX=SZIMBÓLUM argumentum azt igazolja, hogy a tábla adott szava pontosan a megnevezett kezelő címe (az IRQn indexe 16+n16 + n). Például: python3 lab-check-vectors.py lab-a070.elf 1=Reset_Handler 24=TIMER0_IRQHandler. Argumentum nélkül a kimenet a fenti.

A fenti ELF-re a kimenet (a helyettesítő eszközlánccal mérve):

OK   .vectors mérete 64 bájt (16 szó)
OK   .vectors a 0x00000000 címen
OK   táblabázis 0x00000000: 128 bájtra igazított (VTOR)
OK   [0x00] kezdeti SP = 0x20400000 (RAM, 8 bájtra igazított)
OK   [0x04] Reset (kötelező) 0x00000041: Thumb-bit=1, cél 0x00000040 a(z) .text szekcióban
OK   fenntartott bejegyzések (7–10., 13. index): mind 0
OK   [0x08] 0x00000045: Thumb-bit=1, cél 0x00000044 a(z) .text szekcióban
OK   [0x0c] 0x00000047: Thumb-bit=1, cél 0x00000046 a(z) .text szekcióban
OK   [0x2c] 0x00000049: Thumb-bit=1, cél 0x00000048 a(z) .text szekcióban
OK   [0x38] 0x0000004b: Thumb-bit=1, cél 0x0000004a a(z) .text szekcióban
OK   [0x3c] 0x0000004d: Thumb-bit=1, cél 0x0000004c a(z) .text szekcióban
kilépési állapot: 0

Az ellenőrzési feltétel tehát: a .vectors létezik, mérete szóhatáron van, és legalább két szó; a bázisa 0x00000000 (áthelyezett táblánál VTOR-kompatibilis); az első szó érvényes, 8 bájtra igazított SRAM-határ; a Reset-bejegyzés kötelezően nem nulla, páratlan, és kódra mutat; a fenntartott bejegyzések nullák; és minden további nem nulla kezelőbejegyzés páratlan, bit0 nélkül pedig a kódszekcióba esik. A nulla tehát kétféle bejegyzésben megengedett: a fenntartott slotokban, illetve a nem kötelező, kezelő nélküli kivételeknél. A Reset helyén viszont hiba, mert a firmware így nem indul. A labor által ténylegesen használt IRQ-slotokat az INDEX=SZIMBÓLUM argumentum kéri számon; ha a tábla rövidebb, a hiányzó slot is hiba. Ha egy bejegyzés páros, a mag INVSTATE UsageFaultot vált ki, amely HardFaulttá eszkalálódik. Ha a bit0 nélküli cím a vektortábla belsejébe vagy adatterületre mutat, a mag adatot próbálna utasításként végrehajtani.

Ahhoz, hogy a vektortábla tartalma ne csak a linker térképén, hanem a tényleges betöltött képen is stimmeljen, QEMU-ban is ellenőrizhetjük:

# A program végtelen ciklusban áll; timeout-tal futtatjuk.
timeout 2 qemu-system-arm -machine mps2-an385 \
    -kernel lab-a064.elf -nographic -serial null -monitor none
# Várt: nincs kimenet, a QEMU a timeout után kilép (124-es kód).
# Ha a vektortábla rossz, rövid időn belül HardFaultba és/vagy
# hibás PC-értékbe futhat; ezt a QEMU verziója szerint ellenőrizni kell.

16.4.6 Hogyan tölti be a QEMU és a valódi mikrokontroller?

QEMU (qemu-system-arm -machine mps2-an385): az ELF szekcióit a VMA-jukra tölti, majd a reset-szekvenciát szimulálja: a 0x00000000-ról olvassa SP-t és PC-t. Az mps2-an385 géptípus Code régiója a 0x00000000-tól indul; a pontos régióméretek és a QEMU machine-definíció verziófüggők, ezért a -machine mps2-an385 viselkedését a generálás idején a QEMU dokumentációjában ellenőrizni szükséges.

Valódi MCU (pl. STM32 Cortex-M3): a boot-konfiguráció (BOOT0/BOOT1 pinek) dönti el, hogy a 0x00000000-ra a belső flash vagy a rendszer-memória aliasingje látszik-e. A mag ugyanúgy az első két szót olvassa; a flash tartalmát a gyártói bootloader vagy a letöltő eszköz írja oda. A viselkedés eltérhet a QEMU-tól a hibás vektortábla kezelésében (pl. valódi hardveren a lockup állapot részletei), ezt fizikai hoston külön kell validálni.

Közös mindkét esetben: a vektortábla nem opcionális. A reset-handler első utasítása csak akkor fut le, ha a 0x00000004-en lévő szó érvényes, páratlan címet tartalmaz.

16.4.7 Tipikus hibák és ellenpróbák

1. tünet: a QEMU azonnal leáll vagy HardFaultot jelez; a debugger nem áll meg Reset_Handler-ben. Hipotézis: a .vectors szekció nem a 0x00000000-ra került, vagy a KEEP hiányában kiesett a linkelésből. Adatgyűjtés: arm-none-eabi-objdump -h lab-a064.elf — nézzük a .vectors VMA-ját; objdump -s -j .vectors — üres-e. Egy változtatás: KEEP(*(.vectors)) hozzáadása, MEMORY ORIGIN ellenőrzése. Ellenőrzés: újra objdump -h, a .vectors VMA 0x00000000.

2. tünet: a mag INVSTATE HardFaultot vált, bár a vektortábla cím szerint jó helyen van. Hipotézis: a Reset_Handler címe páros a táblában; hiányzik a .thumb_func vagy a .type ..., %function. Adatgyűjtés: objdump -s -j .vectors, a második szó bit0-jának ellenőrzése. Egy változtatás: .thumb_func a handler elé. Ellenőrzés: a második szó páratlan.

3. tünet: a program elindul, de az első push után szemét értékek, vagy a kód felülíródik. Hipotézis: az _estack nem az SRAM teteje (pl. 0 vagy flash-cím), így a verem a kódra nő. Adatgyűjtés: objdump -s -j .vectors első szava; nm _estack; a linker script RAM LENGTH értéke. Egy változtatás: _estack = ORIGIN(RAM) + LENGTH(RAM); Ellenőrzés: az első szó az SRAM felső határára mutat.

Ellenpróba: szándékosan állítsuk a vektortábla első szavát 0-ra, és figyeljük meg QEMU-ban a viselkedést. Ez kontrollált, jelölt demonstráció; a tanulság az, hogy a hardver nem figyelmeztet, egyszerűen rossz veremmel indul.

16.4.8 Feladatok

A feladatcímkék és a kétlépcsős megoldások használatát az Olvasói útmutató írja le.

16.4.9 1. feladat (magyarázó, alapszint — cél: megértés-ellenőrzés)

Magyarázd el, miért a kezdeti veremmutató áll a vektortábla első szavában, és miért nem egy ugrási cím. Hasonlítsd össze az AArch64 reset-viselkedésével (az első program), ahol nincs ilyen módon a címtér elején elhelyezett SP-bejegyzés. Milyen hardveres egyszerűsítést jelent a Cortex-M megoldása, és mi ennek az ára?

16.4.10 2. feladat (implementációs, középszint — cél: rutinépítés)

Egészítsd ki a vektortáblát úgy, hogy a HardFault_Handler a verem tetején lévő stacked frame-ből kiolvassa a mentett PC-t (a folytatási címet) egy regiszterbe, majd végtelen ciklusba kerüljön. A stacked frame a kivétel belépésekor a következő sorrendben kerül a veremre: R0, R1, R2, R3, R12, LR, PC, xPSR. Ellenőrizd objdump -s -j .vectors-sel, hogy a HardFault_Handler bejegyzés a 0x0C offseten van, és páratlan címet tartalmaz.

16.4.11 3. feladat (hibakeresési/mérési, elmélyült — cél: rutinépítés)

Két hibás linker script-változatot kapsz. (a) A .vectors szekcióból hiányzik a KEEP. (b) A .vectors szekció a .text után van a scriptben, és a FLASH ORIGIN változatlan. Mindkét esetben írd le a várható tünetet QEMU-ban, add meg az objdump-parancsot, amellyel a hiba igazolható, és a javítást. Mérési rész: becsüld meg, hány órajel telik el a reset-vektor beolvasása és a Reset_Handler első utasítása között, ha a flash-Hozzáférés 1 várakozási ciklust igényel. Jelöld, hogy ez levezetett érték, nem QEMU-ban mért ciklusszám.

16.4.12 Megoldások

A feladatcímkék és a kétlépcsős megoldások használatát az Olvasói útmutató írja le.

16.4.13 1. megoldás — Segítség

Gondold át, mi történne, ha a mag a reset-handlerbe úgy lépne be, hogy még nincs érvényes SP-je — milyen hardveres lépés előzi meg logikailag az első push-t.

16.4.14 1. megoldás — Teljes megoldás

A Cortex-M hardveresen, utasítás nélkül tölti be SP-t az első szóból. Ezzel a reset-handlernek nem kell külön verembeállítást végeznie, és a C-kód előtti első push már érvényes veremre kerül. Az AArch64-en a reset egy fix, architektúrába égetett címen kezd, és a veremkezelés szoftveres feladat. A Cortex-M megoldás előnye a hardveres egyszerűség és a gyors indulás; ára, hogy a vektortáblát kötelezően a címtér elejére kell helyezni, és a tábla első szava elveszik a kód elől. Ha a boot-konfiguráció miatt a 0x00000000-ra nem a flash látszik, a tábla rossz helyről olvasódik — ez a bring-up egyik klasszikus csapdája.

16.4.15 2. megoldás — Segítség

A stacked frame nyolc szava fix sorrendben kerül a veremre; számold ki, hányadik szó (és milyen bájt-offszet) felel meg a PC-nek, és melyik veremmutatóból (MSP vagy PSP) kell olvasni.

16.4.16 2. megoldás — Teljes megoldás

    .thumb_func
    .global HardFault_Handler
    .type HardFault_Handler, %function
HardFault_Handler:
    mov  r0, lr            /* EXC_RETURN: melyik verem volt használatban */
    tst  r0, #4            /* bit2 = 0: MSP, bit2 = 1: PSP */
    ite  eq
    mrseq r0, msp
    mrsne r0, psp
    ldr  r1, [r0, #24]     /* stacked PC a frame 24. bájtján */
    b    .                 /* r1-ben a mentett PC (folytatási cím) */

Az első mov/tst azt dönti el, hogy a kivétel a fő veremből (MSP) vagy a folyamat veremből (PSP) érkezett-e. A ldr r1, [r0, #24] a stacked frame 7. szavát, a PC-t olvassa. Ez a folytatási cím: precíz szinkron hibánál (például UsageFault) jellemzően a hibát okozó utasítás címe, de pontatlan BusFault esetén egy később végrehajtott utasításra mutathat, ezért a kiértékelésnél a fault-státuszregisztereket is meg kell nézni (kivételmodell). A b . szándékos: a handler nem tér vissza, mert a fault oka nem megszűnt. Az objdump -s -j .vectors kimenetén a 0x0C offseten a HardFault_Handler címe látható, páratlan bit0-val.

16.4.17 3. megoldás — Segítség

Mindkét hibánál abból indulj ki, hogy a linker mit ír ténylegesen a .vectors VMA-jára — a --gc-sections és a szekciósorrend is a “mi kerül a 0x00000000-ra” kérdést dönti el, csak más-más okból.

16.4.18 3. megoldás — Teljes megoldás

(a) Hiányzó KEEP: a --gc-sections linkelés a .vectors szekciót eltávolíthatja, mert assembly szinten nincs rá hivatkozás. Tünet: a .vectors mérete 0, a 0x00000000-n szemét vagy üres flash; a QEMU nem indul, vagy HardFaultba fut. Igazolás: objdump -h — a .vectors hiányzik vagy 0 méretű. Javítás: KEEP(*(.vectors)).

(b) Rossz sorrend: a .vectors a .text után kerül, így a VMA nem 0x00000000, hanem a .text vége utáni cím. Tünet: a mag a 0x00000000-ról olvas, ahol vagy üres flash, vagy a .text első szava van; SP és PC értelmezhetetlen. Igazolás: objdump -h, a .vectors VMA nem 0x00000000. Javítás: a .vectors szekciót a SECTIONS első helyére tenni.

Mérési rész (levezetés): reset után a mag két szót olvas a 0x00000000-ról és 0x00000004-ről. Ha a flash 1 várakozási ciklust igényel szavanként, a két olvasás elméleti alsó korlátja 2 órajel a betöltéshez, plusz a belső szekvenálás. Ez levezetett érték; QEMU-emulációban nincs hardverhű abszolút ciklusszám, ezért mért értékként nem szerepelhet.

16.4.19 Rövid összefoglaló

A Cortex-M reset-szekvenciája a címtér első nyolc bájtjára támaszkodik: a 0x00000000-n a kezdeti SP, a 0x00000004-en a reset-handler címe áll. A vektortábla első 16 bejegyzése fix sorrendű rendszerkivétel, ezután következnek az IRQ-k. A linkerneK a KEEP direktívával a flash elejére kell helyeznie a .vectors szekciót. A reset-handler érvényes SP-vel érkezik, és a .data/.bss kezelés előtt csak a startup kódra adja tovább a vezérlést. Az objdump -s -j .vectors a tábla tényleges tartalmát mutatja: az első szó SRAM-cím, a második páratlan Thumb-cím.

Továbbhasznált definíciók: - vektortábla (vector table): a címtér elején elhelyezkedő, 4 bájtos bejegyzések listája; első szava a kezdeti SP, második a reset-handler címe. - kezdeti veremmutató (initial stack pointer): a vektortábla 0. szava, amelyet a mag reset után hardveresen SP-be tölt. - reset-vektor: a 0x00000004 címen lévő szó, a reset-handler Thumb-jelölt belépési címe.

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

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 fennmaradnak: - Az mps2-an385 pontos Code/SRAM régióméretei és a 0x00000000 aliasing viselkedése a QEMU verziójától függ; a generálás idején ellenőrizendő. - A közölt .vectors-dump és az ellenőrző program kimenete a helyettesítő eszközlánccal (Clang + ld.lld) épített ELF-ből származik; GNU as/ld mellett a handlercímek eltérhetnek, a tulajdonságoknak (páratlan bejegyzés, a .text-be eső cél) viszont teljesülniük kell. A korábbi kiadás kézzel írt dumpja (0x0000000d a vektortábla belsejébe mutatott) hibás volt. - A reset és a handler első utasítása közötti ciklusszám levezetett, QEMU-ban nem hardverhű mérés.