Sie sind auf Seite 1von 5

Technische Universität Berlin

Fakultät IV – Elektrotechnik und Informatik


Security in Telecommunications

Betriebssystempraktikum im Wintersemester 2023/2024

Aufgabe 2

Behandlung von Ausnahmen

Bearbeitungszeit: 07.11.2023 – 27.11.2023 um 22:00 Uhr

Nach der ersten Kontaktaufnahme mit der Zielplattform soll nun der ARM-Kern vollständig initialisiert werden und
euer Code erste Aufgaben übernehmen, die über Low-level-Anwendungsentwicklung hinausgehen. Konkret sollt ihr
Ausnahmesituationen abfangen, die entstehen, wenn euer Anwendungsprogramm derart Unsinn macht, dass der
Prozessorkern an der weiteren Ausführung von Instruktionen gehindert wird.
Mit dem Abfangen von Ausnahmen ist unser „Betriebssystem“ noch kein wirkliches Betriebssystem, sondern eher
ein Bare-Metal-Programm mit ein wenig Programmierkomfort. Um diesen Umstand auszuräumen, erweitern wir es
in dieser Aufgabe auch um Hardware Interrupts. Hardware Interrupts sind später die einzige Möglichkeit, wie das
Betriebssystem die Kontrolle über den Prozessor wiedererlangen kann, wenn ein Nutzerprozess in einer Endlosschleife
hängen sollte. Außerdem geben uns Hardware Interrupts die Möglichkeit, zeitnah auf Ereignisse, wie Tastendrücke,
zu reagieren, auch wenn der Prozessor gerade eigentlich mit etwas anderem beschäftigt ist.
Die Erkennung von Ausnahmen und Interrupts erledigt der ARM-Kern von selbst, ihr sollt daran anknüpfen, eine
Meldung über Art und Ort der Ausnahme ausgeben und das System anhalten. Es sollte eine hilfreiche Meldung mit
weiteren Informationen sein. (Definition siehe unten)
Interrupt-getriebenes Lesen verringert die Chance, dass wir ein Zeichen verpassen, weil wir es nicht rechtzeitig
abgeholt haben. (Interrupt-getriebenes Schreiben würde ein Fortsetzen der Programmausführung ermöglichen, ohne
dass auf die Fertigstellung der Übertragung des vorherigen Zeichens gewartet werden muss.)
Damit sichergestellt ist, dass uns ein Hardware Interrupt ereilt (auch wenn der Benutzer keine Taste drückt),
setzen wir außerdem proaktiv den Systimer (BCM2835 Seite 172) ein, um so periodisch Interrupts zu erzeugen und
unserem Betriebssystem eine garantierte Eingriffsmöglichkeit zu geben.
Um dies zu erreichen, sind folgende Teile zu erledigen (nicht unbedingt in dieser Reihenfolge):

Ausnahmen
1. Initialisiert die Interrupt Vector Table und entwickelt ein einfaches trampoline mit Handlern für Interrupts.
2. Bei der Ausführung eines Handlers befindet sich der ARM-Kern in einem anderen Modus. Bei ARM haben
die verschiedenen Modi unterschiedliche Stacks. Überlegt euch also, wo ihr die Stacks im Speicher ablegen
wollt und initialisiert sämtliche Stackpointer des Prozessors. (Stacks sind bei ARM übrigens full-descending
gemäß dem Procedure Call Standard for the ARM Architecture, an den sich gcc hält.) Beachtet bitte, dass
nur die ersten 128MB des physikalischer Speicher nutzbar sind.
3. Entwickelt entsprechende Handler für die verschiedenen Ausnahmen mit folgenden Aufgaben:
a) Sichern des lr mit angepasstem Offset (Ursprung der Ausnahme).
b) Sichern aller wichtigen (nicht banked) Register auf dem Stack (r0 bis r12).
c) Sprung zu c Handler mit Pointer zu den gesicherten Registern als Argument.
d) Wiederherstellen aller Register, nachdem aus dem c Handler zurück gesprungen wurde.
e) Rücksprung aus Ausnahme.

1
4. Gebt aus eurem c Handler einen Registerdump aus, der alle relevanten Informationen erhält (siehe unten).
5. (Optional) Ab dem nächsten Aufgabenblatt werden wir fast ausschließlich aus dem User/System Modus
Ausnahmen erhalten und dabei häufig beim Rücksprung die Registerwerte austauschen (Kontextwechsel).
Wer will, kann zusätzlich innerhalb seines Handlers auch direkt das banked User/System lr und sp Register
sowie das SPSR Register mit auf den Stack legen.
Die Ausgabe der Ausnahme-Handler soll dabei folgendermaßen aussehen:

############ EXCEPTION ############


Data Abort an Adresse: 0x000081a8
Data Fault Status Register: 0x00000001 -> Alignment fault
Data Fault Adress Register: 0x00000001

>> Registerschnappschuss <<


R0: 0x00000061 R5: 0x00000000 R10: 0x00000000
R1: 0x00000000 R6: 0x00000000 R11: 0x07fffff4
R2: 0x00000001 R7: 0x00000000 R12: 0x0000007f
R3: 0x00000000 R8: 0x00000000
R4: 0x00000000 R9: 0x00000000

>> Modusspezifische Register <<


User/System | LR: 0x00000000 | SP: 0x07ffc000
IRQ | LR: 0x00008338 | SP: 0x07ffd000 | SPSR: _ZC_ _F_ Supervisor 0x60000153
Abort | LR: 0x00008f84 | SP: 0x07ffef80 | SPSR: N___ _F_ Supervisor 0x80000153
Undefined | LR: 0x00000000 | SP: 0x07ffe000 | SPSR: ____ ___ Invalid 0x00000000
Supervisor | LR: 0x00008134 | SP: 0x07ffffe8 | SPSR: ____ ___ Invalid 0x00000000

Bitte versucht, die oben gezeigte Abgabe möglichst genau so wie abgebildet zu reproduzieren. Ihr erleichtert uns
damit enorm die Korrektur. Achtet bitte insbesondere auf die 0x-Präfixe vor den Hexadezimalzahlen.

Handlerausgaben
Art der Exception, Adresse der Exception
Nur bei Data Abort: Art und Zugriffsadresse des verursachenden Speicherzugriffs (DFSR/DFAR)
Nur bei Prefetch Abort: Art und Adresse der verursachenden Instruktion (IFSR/IFAR)
Registerschnappschuss der Register bei Eintritt in die Exception
Statusregister des aktuellen Modus mit Flags (NZCV E IFT) und Modusname in Klartext
Modusspezifische Register aller Modi (außer FIQ): LR, SP und SPSR mit Flags (NZCV E IFT) und Modusname
in Klartext.

6. Zum Testen bzw. zur Demonstration von zumindest einem Teil eurer Handler müsst ihr Code schreiben, der
entsprechende Ausnahmen provoziert. Diese Anwendung soll bei der Eingabe von einem s einen Supervisor
Call auslösen, bei der Eingabe eines a einen Data Abort auslösen, bei der Eingabe eines u eine Undefined
Instruction ausführen und bei der Eingabe eines p einen Prefetch Abort auslösen.
7. Beim starten eures Betriebssystems soll nach abgeschlossenem Booten (Initialisierung der Hardware und
sonstiger Kernelfunktionen) der String === Betriebssystem gestartet === ausgegeben werden. Dieser
markiert den Beginn der regulären Ausführung des Kernels.
8. Unmittelbar nachdem ihr den String ausgegeben habt, soll die Funktion test_kernel() aufgerufen werden.
Diese Funktion wird den Tutories die Korrektur eures Betriebssystems erleichtern. Da diese Funktion allerdings
nicht von euch implementiert wird, würde der Compiler eine entsprechende Fehlermeldung ausgeben. Um dies
zu verhindern, muss in der Datei config.h eine Funktionsdeklaration mit dem Attribut weak hinzugefügt
werden. Dadurch ignoriert der Linker, dass dem Symbol (noch) keine Implementierung zugeordnet werden
kann.

2
[...]
kprintf("=== Betriebssystem gestartet ===\n");
test_kernel();
[...]

Beispielcode, wie die test_kernel()-Funktion aufgerufen werden soll

[...]
void test_kernel() __attribute__((weak));
[...]

Definition der test_kernel()-Funktion in der config.h

IRQs Nun sollten eure Ausnahme-Handler funktionieren und es kann mit den Hardware-Interrupts weiter gemacht
werden:
9. Initialisiert den Systimer (BCM2835) so, dass er regelmäßig Interrupts auslöst. Macht dabei die Häufigkeit
der Interrupts abhängig von TIMER_INTERVAL aus config.h.
10. Konfiguriert alle Komponenten, durch die ein Interrupt Signal durchgeht (Interrupt Register und ARM Kern
selbst), so dass ihr einen Interrupt auch tatsächlich bemerkt.
11. Entwickelt einen Interrupt Handler, der den Interrupt an allen notwendigen Komponenten bestätigt (und falls
notwendig updatet) und anschließend die eigentliche Programmausführung fortsetzt.
12. Auf Tastendruck d soll eine globale Variable umgeschaltet werden, die steuert, ob bei Interrupts ebenfalls ein
Registerdump ausgegeben werden soll.

Serial Port
13. Stellt den UART zum Empfangen von Zeichen auf Interrupt-Betrieb um. Die interne FIFO des UART darf
deaktiviert werden.
14. Bei einem UART Interrupt wird das Zeichen gelesen und zwischengespeichert. Auf Nachfrage der Anwendung
wird das Zeichen der Anwendung zur Verfügung gestellt. Implementiert einen (Ring-)Buffer der angekommene
Zeichen zwischenspeichert, auch wenn die Anwendung das vorherige noch nicht abgeholt hat. Macht die Größe
des Buffers abhängig von UART_INPUT_BUFFER_SIZE aus config.h.
15. Erweitert die Anwendung aus Punkt 6 um folgende Funktionalitäten: Nach der Eingabe eines e (um das
„Unterprogramm“ aufzurufen) wartet die Anwendung auf weitere Tastendrücke, um anschließend pro Tasten-
druck, dieses Zeichen zehn mal, mit kleinen Pausen zwischen den Ausgaben, asuzugeben. Gleichzeitig soll
außerdem eine Ausgabe stattfinden, wenn ein Timer Interrupt festgestellt wird: ein Ausrufezeichen gefolgt
von einem Zeilenumbruch.
16. Macht die kleinen Pausen zwischen der Ausgabe einzelner Zeichen abhängig von BUSY_WAIT_COUNTER
aus config.h
17. Damit ergibt sich eine Ausgabe, die der folgenden ähneln sollte (je nach Häufigkeit der Timer Interrupts, und
Länge der Pausen):
=== Betriebssystem gestartet ===
!
!
!
AAAAAAAAAAAAAAAAAAAAAAAAAA!
AAAAAAAAAAAAAAAAAAAAAAAA!
AAAAAAAAAA!
!
!
BBBBBBBBBBBBBBBBBBBBBBBBB!
BBBBBBBBBBBBBBBBBBBBBBBBBB!

3
BBBBBBBBBBBBBBBBBBBCCCCCCCCCCC!
CCCCCCCCCCCCCCCCCCCCCCCC!
CCCCCCCCCCCCCCCCCCCCCCCCCC!
CCCCCCCCC!
!
!
(C wurde direkt nach B eingegeben, während die Ausgabe von B noch lief)
Sobald ihr einmal in dem Unterprogramm seid, könnt ihr dort für immer bleiben.
18. Außerdem muss bei der Eingabe eines c der Register Checker von der BSPrak ISIS-Webseite ausgeführt
werden. Mit diesem könnt ihr prüfen, ob euer Interrupt Handler die richtigen Register sichert. Stellt sicher,
dass während der Register Checker läuft ausreichend Timer Interrupts auftreten und diese auch durch !
erkennbar sind.

Zusammenfassung

- Nach dem Starten gibt das OS === Betriebssystem gestartet === aus und ruft die test_kernel()-
Funktion auf
- Der Ausnahme-Handler erzeugt die vorgegebene Ausgabe in möglichst gleicher Form.
- Eure Lösung reagiert auf folgende Tasterturbefehle:
s: Einen Supervisor Call auslösen.
a: Einen Data Abort erzeugen.
p: Einen Prefetch Abort erzeugen.
u: Eine Undefined Instruction ausführen.
c: Den Register Checker ausführen.
e: Das interaktive Unterprogramm starten.
d: IRQ-Debug Modus umschalten (default ist aus).
- Der Timer und die serielle Schnittstelle erzeugen Interrupts.

Hinweise
– GDB ließt das VBAR immer als 0x0 aus.
– Ein Prefetch Abort kann mit der bkpt Instruktion ausgelöst werden.
– Ein Data Abort ist auf QEMU nur unter der gepatchten Version möglich und kann mit einem unaligned
memory Zugriff erzeugt werden.
– Direktes Zugreifen auf ein banked Register mit mrs/msr ist undefiniert, falls auf das Register normal zuge-
griffen werden könnte. Bsp: msr lr_irq, r0 ist invalide, falls bereits im IRQ Modus.
– Die Exception Handler sollten für die Zukunft (also ab dem nächsten Aufgabenblatt) alle rücksprungfähig
sein. Für dieses Aufgabenblatt reicht es jedoch wenn der IRQ Handler rücksprungfähig ist.
– In der aktuellen Aufgabe kann es natürlich passieren, dass ein korrekter Rücksprung aus einer Exception ein
Abort in Endlosschleife verursacht.
– Falls ihr euch wundert, warum in der Interrupt Tabelle im BCM2835 auf Seite 113 der Systimer fehlt, dann
schaut doch einmal im errata nach ;)
– Der Systimer besitzt 4 Compare Register. Jedoch sind auf der Hardware C0 und C2 für die GPU reserviert.
Ihr solltet euch deshalb auf C1 und C3 beschränken, falls ihr Kompatibel mit der Hardware bleiben wollt.

4
Fehlersuche

Einige der gängigen Probleme sind im Folgenden zusammengefasst.


1. Kein Interrupt:
- Timer, UART oder IRQ Register falsch konfiguriert?
- Interrupts in CPRS noch maskiert?
- Interrupt Handler falsch installiert?
2. Kein weiterer Interrupt:
- Interrupt an Timer oder UART nicht richtig zurückgesetzt?
- Falscher Rücksprung aus Handler?
3. Sofort wieder ein Interrupt:
- Interrupt an Timer oder UART nicht richtig zurückgesetzt?
4. Register im Anwendungsprogramm korrumpiert:
- Falscher Offset beim Rücksprung?
- Nicht alle Register ordentlich gesichert und wiederhergestellt?
5. Data Abort:
- Sprung an falsche Adresse?
- Falscher Offset beim Rücksprung?
- Nicht alle Register ordentlich gesichert und wiederhergestellt?
- IRQ während CPU im IRQ Modus?
Neben diesen Fehlern werdet ihr eventuell feststellen, dass Interrupts auftreten, für die ihr keine Quelle ermitteln
könnt – sogenannte spurious Interrupts. Solange das nicht andauernd geschieht, ist das in Ordnung. Der umgekehrte
Fall, dass es mehrere Auslöser für einen Interrupt gibt, ist aufgrund der geteilten Interrupt Leitung auch normal.
Falls euer Interrupt Handler recht lange braucht, werdet ihr das häufiger sehen.
Der Rücksprung aus der Ausnahmebehandlung kann (bis auf LR Offset) unter relativ kontrollierten Bedingungen am
SVC geübt werden. Die Registerausgabe soll aber trotzdem stattfinden. Dazu solltet ihr allerdings den Supervisor
Modus verlassen, da ihr euch sonst eventuell das Link Register und das SPSR korrumpiert: bei einem SVC wird in
den Supervisor Modus gewechselt und LR_svc und SPSR_svc überschrieben.
Wir werden euch ein Stück Assembler-Code zur Verfügung stellen (der Register Checker), das hoffentlich recht
verlässlich nicht gesicherte Register und falsche Rücksprung Offsets entdeckt.

Abgabe Die Abgabe erfolgt als Gruppe mit Hilfe des Befehls make submission. Dieser verpackt alle von git
getrackten Dateien auf dem von euch in der Makefile fest gelegten branch. Das Ergebnis gebt ihr bitte bei ISIS ab.
Benennt das dabei entstandene Archiv nicht um! Während des Bearbeitungszeitraums kann beliebig oft abgegeben
werden, bewertet wird aber nur die letzte Abgabe!

Das könnte Ihnen auch gefallen