ARM Exceptions
Kontextwechsel |
---|
Das Verfahren zum Speichern und Wiederherstellen des Zustands einer CPU nennt man Kontextwechsel (englisch Context Switch). |
Mikroprozessoren können auf asynchrone Ereignisse mit einem Kontextwechsel reagieren. Üblicherweise aktiviert eine externe Hardware eine bestimmte Eingangsleitung, was den Prozessor veranlasst, den aktuellen Programmablauf vorübergehend zu unterbrechen um eine spezielle Routine abzuarbeiten. Eine solche Unterbrechung nennt man Interrupts, oder genauer, Hardware-Interrupt. Für viele Plattformen existiert auch der Begriff Software-Interrupt, bei dem spezielle Programmbefehle den Kontextwechsel auslösen.
Bei ARM Prozessoren werden solche Unterbrechungen Exceptions (Ausnahmen) genannt. Diese Architektur kennt sieben Prozessor-Modi, davon sechs priviligierte, gennant FIQ-, IRQ-, Supervisor-, Abort-, Undefined und System-Modus und einen nicht-priviligierten User-Modus. Der Wechsel zwischen den Modi kann softwaregesteuert oder durch Auslösen einer Exception erfolgen. Der User-Modus kann allerdings nur durch Auslösen einer Exception verlassen werden.
Tritt eine Bedingung für eine Exception auf, wird der CPU-Status und die Rückkehraddresse gesichert. Dann wechselt die CPU in den entsprechenden Modus und maskiert ggf. die Behandlung von Hardware-Interrupts. Der Programmablauf wird dann ab einer fest definierten Adresse fortgeführt, die man Exception-Vektor nennt.
Viele ARM Prozessoren können zwischen dem 32-Bit ARM Befehlssatz und einem reduzierten 16-Bit Thumb Befehlssatz umschalten. Dabei ist zu berücksichtigen, dass die CPU vor dem Sprung auf den Exception-Vektor immer auf den ARM Befehlssatz wechselt.
Die folgende Tabelle bietet einen Überblick über die verschiedenen ARM Exceptions und wie diese bearbeitet werden.
Ereignis | Exception | Priorität 1 | Rückkehraddresse | Status | Modus | FIQ | IRQ | Vektor 2 | Empfohlener Return-Befehl |
---|---|---|---|---|---|---|---|---|---|
Reset Signal deaktiviert | Reset | 1 | Not available | Not available | Supervisor | Disabled | Disabled | Basisadresse+0 | Not available |
Lesen oder Schreiben mit ungültiger Adresse | Data Access Memory Abort (Data Abort) | 2 | R14_abt=PC+8 4 | SPSR_abt=CPSR | Abort | Unchanged | Disabled | Basisadresse+16 | SUBS PC,R14_abt,#8 8 |
FIQ Eingang aktiviert | Fast Interrupt (FIQ) | 3 | R14_fiq=PC+4 5 | SPSR_fiq=CPSR | FIQ | Disabled | Disabled | Basisadresse+28 7 | SUBS PC,R14_fiq,#4 |
IRQ Eingang aktiviert | Normal Interrupt (IRQ) | 4 | R14_irq=PC+4 5 | SPSR_irq=CPSR | IRQ | Unchanged | Disabled | Basisadresse+24 | SUBS PC,R14_irq,#4 |
Befehl BKPT 3 ausgeführt oder Befehl an ungültiger Adresse | Instruction Fetch Memory Abort (Prefetch Abort) | 5 | R14_abt=PC+4 6 | SPSR_abt=CPSR | Abort | Unchanged | Disabled | Basisadresse+12 | SUBS PC,R14_abt,#4 |
Befehl SWI ausgeführt | Software Interrupt (SWI) | 6 | ARM state: R14_svc=PC+4 Thumb state: R14_svc=PC+2 6 |
SPSR_svc=CPSR | Supervisor | Unchanged | Disabled | Basisadresse+8 | MOVS PC,R14_svc |
Unbekannter Befehls-Code | Undefined Instruction | 6 | ARM state: R14_und=PC+4 Thumb state: R14_und=PC+2 6 |
SPSR_und=CPSR | Undefined | Unchanged | Disabled | Basisadresse+4 | MOVS PC,R14_und |
Anmerkung 1: Die Priorität entscheided über die Reihenfolge der Abarbeitung beim gleichzeitigen Auftreten mehrerer Exceptions. 1 ist die höchste, 6 die niedrigste Priorität.
Anmerkung 2: Die normale Basisadresse für Vektoren ist 0x00000000. Einige Implementierungen erlauben es, diese nach 0xFFFF0000 zu verschieben.
Anmerkung 3: Wenn der Befehl an einem Breakpoint einen Prefetch Abort verursacht, dann wird der Abort zuerst behandelt. Wurde der Grund für den Abort in der Abort-Handler-Routine behoben und die Routine kehrt zum Breakpoint zurück, dann wird der Debug-Request behandelt.
Anmerkung 4: PC ist die Adresse des Befehls, der die Data Abort Exception verursacht hat.
Anmerkung 5: PC ist die Adresse des Befehls, dessen Ausführung von dem Interrupt Signal verhindert wurde.
Anmerkung 6: PC ist die Adresse des SWI- oder PKPT-Befehls oder des ungültigen Befehls-Codes, der die Prefetch Abort Exception verursacht hat.
Anmerkung 7: Dies ist mit Absicht der letzte Eintrag in der Vektortabelle. Die Behandlungs-Routine kann so direkt hier beginnen. Ein zusätzlicher Sprungbefehl ist nicht notwendig.
Anmerkung 8: Damit wird der Befehl, der die Exception verursacht hat, wiederholt. Ist dies nicht beabsichtigt, sollte statt dessen SUBS PC,R14_abt,#4 verwendet werden.
ARM Exceptions und Nut/OS
Nut/OS, ursprünglich für AVR-Mikrocontroller entworfen, stellt lediglich Routinen zur Behandlung von Hardware-Interrupts (IRQ und FIQ Exceptions) bereit. Lassen Sie uns untersuchen, welchen Nutzen wir aus einer Behandlung der Abort Exceptions gewinnen könnten.
Die ARM7TDMI CPU, die beim Ethernut 3 verwendet wird, erlaubt die Konfiguration der zu Verfügung stehenden Speicherbereiche. Dieses Remapping geschieht während der frühen Initialisierungssphase. Nach einem Hardware-Reset stehen lediglich der Flash-Speicher von 0x00000000 bis 0x000FFFFF und das interne RAM von 0x00300000 bis 0x0033FFFF zur Verfügung.
Die Konfiguration der Speicheraufteilung, die später der Anwendung zur Verfügung steht, erfolgt entweder
- durch den Bootloader oder
- durch die Nut/OS Initialisierung, wenn diese im Flashspeicher liegt, oder
- durch Programmier-Tools, wie JTAG-O-MAT oder OpenOCD.
Ein typischer Speicher-Layout für Ethernut 3 ist:
- 0x00000000 - 0x0003FFFF RAM
- 0x10000000 - 0x103FFFFF FLASH
- 0x20000000 - 0x200FFFFF Ethernet Controller
- 0x21000000 - 0x210FFFFF CPLD Register
- 0x22000000 - 0x220FFFFF Externer Speicher-Bus am Erweiterungsstecker
Versucht die Firmware von irgendeiner anderen Speicheradresse, z.B. 0x90000000, zu lesen oder in diese zu schreiben, wird eine Data Abort Exception ausgelöst. Springt das Programm an eine solche Speicherstelle, reagiert die CPU mit einem Prefetch Abort. Liegt die Speicherstelle in einem definierten Bereich, enthält aber einen ungültigen Befehls-Code, löst dies eine Undefined Exception aus.
Man kann sich leicht vorstellen, dass die Auswertung solcher Exceptions sehr hilfreich bei der Fehlersuche sein könnte. Glücklicherweise stehen entsprechende Routinen seit Nut/OS Version 4.7.5 zur Verfügung.
Analyse einer Abort Exception
Dieser Abschnitt beschreibt interne Details zur Behandlung von Abort Exceptions und stellt eine Möglichkeit vor, diese in die eigene Anwendung zu integrieren. Wenn Sie lediglich daran interessiert sind, die mit der Version 4.7.5 eingeführte Unterstützung zu aktivieren, können Sie diesen Abschnitt überspringen.
Wie bereits mehrfach erwähnt, werden Abort Exceptions von älteren Nut/OS Versionen nicht behandelt. Dies ist nicht ganz korrekt. Tatsächlich springt Nut/OS in einem solchen Fall in eine Endlos-Schleife und das System friert ein.
Schauen wir und das folgende, fehlerhafte Programm an:
#include <stdio.h> #include <io.h> #include <dev/board.h> #include <sys/timer.h> #include <sys/version.h> int main(void) { u_long baud = 115200; int *bad; /* * Register and initialize the DEBUG device as stdout. */ NutRegisterDevice(&DEV_DEBUG, 0, 0); freopen(DEV_DEBUG_NAME, "w", stdout); _ioctl(_fileno(stdout), UART_SETSPEED, &baud); /* * Print a banner, so we can show that we are running. */ printf("\n\nData Abort Sample - Nut/OS %s\n", NutVersionString()); /* * Set a pointer to a bad memory address. */ bad = (u_long *)0x09000000; /* * This will crash. */ *bad = 0x12345678; /* * We will never reach this point. */ puts("Brave new world!"); return 0; }
Nach der Übersetzung des Quellcodes in eine Binärdatei und dem Upload auf das Zielsystem sollte folgende Ausgabe an der RS232/DBGU Schnittstelle erscheinen:
Data Abort Sample - Nut/OS 4.0.2.1
Wie erwartet friert das System ein und es gibt keinen Hinweis auf die Benutzung eines ungültigen Pointers. Wenn ein JTAG Adapter (z.B. Turtelizer) angeschlossen wird, können wir jtagomat, OpenOCD oder ein ähnliche Dienstprogramm verwenden, um die CPU zu stoppen um den Programmzähler abzufragen.
$ jtagomat -v HALT Turtelizer 1.2.4 $ jtagomat LOAD PC 1 STDOUT PC 0x00000038
Der Programmzähler zeigt auf Adresse 0x00000038, der zuletzt ausgeführte Befehl liegt also an der Adresse 0x00000034.
Ein Blick zur Linker-Map-Datei unserer Anwendung ergibt, dass mehrere Sprungmarken auf diese Stelle zeigen:
0x00000034 __xcpt_dummy 0x00000034 __swi 0x00000034 __data_abort 0x00000034 __prefetch_abort 0x00000034 __undef
Nehmen wir an, dass diese Anwendung auf Ethernut 3 läuft und vom Bootloader ins interne RAM geladen wurde. Der zugehörige Quellcode befindet sich dann in der Datei arch/arm/init/crtat91_ram.S. Die Quellcode-Dateien für andere Zielsysteme finden Sie übrigens im selben Verzeichnis und werden sich in diesem Bereich kaum unterscheiden. Selbst wenn Sie nicht mit ARM Assembler vertraut sind, können Sie die Exception-Vektoren ausfindig machen und erkennen, dass alle indirekt auf die Sprungmarke __xcpt_dummy verweisen, welche slebst auf eine Endlosschleife zeigt. Die Anweisung b bedeutet branch (springe). Der Befehl an der Marke __xcpt_dummy springt also auf sich selbst.
.global __vectors __vectors: ldr pc, [pc, #24] /* Reset */ ldr pc, [pc, #24] /* Undefined instruction */ ldr pc, [pc, #24] /* Software interrupt */ ldr pc, [pc, #24] /* Prefetch abort */ ldr pc, [pc, #24] /* Data abort */ ldr pc, [pc, #24] /* Reserved */ /* * On IRQ the PC will be loaded from AIC_IVR, which * provides the address previously set in AIC_SVR. * The interrupt routine will be called in ARM_MODE_IRQ * with IRQ disabled and FIQ unchanged. */ ldr pc, [pc, #-0xF20] /* Interrupt request, auto vectoring. */ ldr pc, [pc, #-0xF20] /* Fast interrupt request, auto vectoring. */ .word _start .word __undef .word __swi .word __prefetch_abort .word __data_abort .weak __undef .set __undef, __xcpt_dummy .weak __swi .set __swi, __xcpt_dummy .weak __prefetch_abort .set __prefetch_abort, __xcpt_dummy .weak __data_abort .set __data_abort, __xcpt_dummy .global __xcpt_dummy __xcpt_dummy: b __xcpt_dummy
Sie stimmen sicher zu, dass eine Endlosschleife nicht sehr nützlich ist. Lassen Sie uns eine beispielhafte Routine zu unserer Anwendung hinzufügen, die hilfreicher ist.
Zuerst bemühen wir aber nochmal den Debugger, um an eine wichtige Information zu kommen: Den Inhalt des Link Registers.
$ jtagomat LOAD LR 1 STDOUT LR 0x00000560
Wir wir auf der Tabelle im ersten Abschnitt erkennen, wird die Adresse der Anweisung, die die Exception verursachte, mit einem Versatz von 8 im Link Register r14 gespeichert. In unserem Fall ist der Inhalt des Registers 0x00000560. Ein Blick in die Map-Datei des Linkers zeigt, dass sich diese Stelle zwischen den Marken NutAppMain und NutInit befindet.
0x000004d0 NutAppMain 0x000005dc NutInit
Hier sind ggf. zusätzliche Erklärungen nötig. Die Namen von Funktionen werden vom Compiler in Assembler-Sprungmarken umgewandelt. NutInit ist die Nut/OS Initialisierungs-Routine. NutAppMain ist eine Besonderheit von Nut/OS und entspricht dem C Hauptprogramm main, welches einfach per Präprozessoranweisung umdefiniert wird. Damit wird dem Compiler vorgegaukelt, es handele sich um eine ganz normale C Funktion. Einige Compilerversionen behandeln main nämlich speziell. Sie laden möglicherweise den Stackpointer neu, was die Multithreading-Unterstützung von Nut/OS durcheinander bringen würde.
Das Resultat dieser langen Erklärungen: Wir konnten feststellen, dass sich die Ursache für die Data Abort Exception im Hauptprogramm befinden muss.
Aber ich versprach, um eine Routine zur Behandlung vorzustellen. Hier ist sie:
void __data_abort(void) __attribute__ ((naked)); void __data_abort(void) { puts("Data Abort\n"); for(;;); }
Fügen Sie diese Funktion einfach zu dem oben vorgestellten Programm mit dem fehlerhaften Pointer hinzu.
Glücklicherweise erlaubt uns das Device DEBUG die Verwendung von stdio Funktionen, selbst wenn sich die CPU in einem anderen Context befindet, als auch innerhalb von Interrupt- oder Exception-Routinen. Mit dieser Änderung liefert das Programm folgendes Resultat:
Data Abort Sample - Nut/OS 4.0.2.1 Data Abort
Offensichtlich wird unsere neue Routine bearbeitet. Die Skeptiker unter uns werden aber fragen: Wie das sein kann? Was passierte mit der Endlosschleife bei __xcpt_dummy? Wenn Sie nochmal zu der Vektortabelle zurückkehren, können Sie sehen, dass die Marke __data_abort das Attribut .weak (schwach) erhalten hat. Jede weitere Definition von __data_abort würde diese schwache Definition ersetzen, und genau das tut unsere gleichnamige C Routine.
Sie wissen vielleicht, dass eine normale C Routine am Eintrittspunkt zusätzliche Befehle zur Verwaltung enthält, welche bei einer Exception-Behandlung stören würden. Um sicherzustellen, dass der Compiler nur reinen Code erzeugt, erhält unsere Funktion __data_abort noch das Attribut naked (nackt).
Jetzt haben wir zumindest eine Funktion, die uns darüber informiert, was passiert ist. Gegenüber dem ursprünglichen Programm, welches einfach einfriert ist dies ein merklicher Fortschritt. Wirklich hilfreich wäre diese Funktion aber erst, wenn sie die Adresse, an der das Problem auftrat, ausgeben würde. Wir wissen, dass sich diese Information im Link-Register befindet. Mit der folgenden Erweiterung wird der Inhalt des Registers mit Hilfe des Inline Assemblers ausgelesen.
void __data_abort(void) __attribute__ ((naked)); void __data_abort(void) { register u_long *lnk_ptr; __asm__ __volatile__ ( "sub lr, lr, #8\n" "mov %0, lr" : "=r" (lnk_ptr) ); /* On data abort exception the LR points to PC+8 */ printf("Data Abort at %p 0x%08lX\n", lnk_ptr, *(lnk_ptr)); for(;;); }
Diese Erweiterung liefert nun auch die Position des Problems:
Data Abort Sample - Nut/OS 4.1.4.1 pre Data Abort at 0x558 0xE5823000
Wir können das Resultat überprüfen, indem wir in die Listing-Datei des Compilers schauen:
0078 0934A0E3 mov r3, #150994944 @ tmp75, 007c 14300BE5 str r3, [fp, #-20] @ tmp75, bad 0080 14201BE5 ldr r2, [fp, #-20] @ bad, bad 0084 30309FE5 ldr r3, .L2+20 @ tmp77, 0088 003082E5 str r3, [r2, #0] @ tmp77,* bad
Register r3 wird mit 150994944 dezimal geladen, was 0x90000000 hexadezimal entspricht. Hier haben wir unseren fehlerhaften Pointerwert. Die folgende Anweisung an 0x007C speichert diesen Wert in die lokale Pointervariable, unter Verwendung des Framepointer-Registers fp. Dann wird der Inhalt des Pointers in Register r2 und die Konstante 0x12345678 von Speicherstelle .L2+20 in Register r3 geladen. Die letzte Anweisung führt dann zum Abbruch, wenn nämlich versucht wird, den Inhalt von r3 in die Speicherstelle zu schreiben, auf die r2 zeigt.
Falls Sie über die Adressen in der ersten Spalte gestolpert sein sollten: Der Compiler, von dem die Listing-Datei stammt, erzeugt nur relative Adressen. Erst der Linker wird diese in absolute Adressen umsetzen. Die Befehlscodes in der zweiten Spalte erscheinen übrigens in umgekehrter Byte-Reihenfolge.
Nut/OS Abort Exception Behandlung aktivieren
Seit Version 4.7.5 stellt Nut/OS eigene Routinen zur Behandlung von Abort Exceptions zur Verfügung. Der ursprüngliche Quellcode stammt vom LostARM Projekt und wurde unter GPL Version 2 veröffentlicht. Der Autor Duane Ellis stellte den Code freundlicherweise für Nut/OS unter BSD Lizenz zur Verfügung.
Die Routinen stehen aber nicht standardmäßig zur Verfügung,
sondern müssen explizit aktiviert werden. Zunächst ist aber
sicherzustellen, dass der Compiler Code erzeugt, der eine
Sprungrückverfolgung ermöglicht. Normalerweise wird Nut/OS
mit mit der Option -fomit-frame-pointer
übersetzt,
um Speicherplatz zu sparen. Wählen Sie also im Konfigurator
unter Settings als Plattform arm-gccdbg, wodurch eine Reihe
unterschiedlicher Compiler-Einstellungen aktiviert werden, u.a.
die Erzeugung von Framepointern. Alternativ können Sie
natürlich die Option einfach mit einem Texteditor aus den Dateien
Makedefs.arm-gcc und app/Makedefs.arm-gcc entfernen.
Die Routinen zur Behandlung von Abort Exceptions befinden sich in einzelnen Objekt-Dateien, die wir zum LIBS Eintrag im Makefile der Anwendung hinzufügen:
LIBS = $(LIBDIR)/arm-da.o $(LIBDIR)/arm-pfa.o $(LIBDIR)/arm-udf.o \ $(LIBDIR)/nutinit.o -lnutos -lnutdev -lnutarch -lnutcrt
Folgende Module stehen zur Verfügung:
-
arm-da.o
Data Abort, ausgelöst durch Lesen oder Beschreiben ungültiger Speicheradressen. -
arm-pfa.o
Prefetch abort, ausgelöst durch Code-Ausführung an ungültigen Adressen. -
arm-swi.o
Software Interrupt. -
arm-udf.o
Undefined Instruction Abort, ausgelöst durch Ausführung von ungültigem Code.
Beim Auftreten einer Exception erfolgt eine Ausgabe auf stdout. Stellen Sie sicher, dass stdout einem Treiber zugewiesen ist, der Ausgaben im Interruptkontext erlaubt, z.B. DEV_DEBUG.
Generieren Sie nun neue Nut/OS Bibliotheken mit den geänderten Einstellungen und testen Sie die folgende Beispielanwendung. Sie entspricht im wesentlichen dem oben genannten Beispiel, enthält aber verschachtelt aufgerufene Funktionen, um Backtracing zu demonstrieren.
#include <stdio.h> #include <io.h> #include <dev/board.h> #include <sys/timer.h> #include <sys/version.h> int global_int; void sub3(void) { int *bad = (int *)0x09000000; printf("Bye bye\n"); *bad = 0x12345678; } void sub2(void) { int *good = &global_int; *good = 2; printf("In sub%d\n", global_int); sub3(); } void sub1(void) { int *good = &global_int; *good = 1; printf("In sub%d\n", global_int); sub2(); } /* * Main application routine. */ int main(void) { u_long baud = 115200; /* * Register and initialize the DEBUG device as stdout. */ NutRegisterDevice(&DEV_DEBUG, 0, 0); freopen(DEV_DEBUG_NAME, "w", stdout); _ioctl(_fileno(stdout), UART_SETSPEED, &baud); /* * Print a banner, so we can show that we are running. */ printf("\n\nData Abort Sample - Nut/OS %s\n", NutVersionString()); sub1(); /* * We will never reach this point. */ puts("Brave new world!"); for (;;) { NutSleep(1000); putchar('.'); } return 0; }
Dieses Beispiel produziert dann etwa folgende Ausgabe:
Data Abort Sample - Nut/OS 4.7.5.0 In sub1 In sub2 Bye bye Unexpected: DA R0 : 0x00000000 R8 : 0xaa55aa55 R1 : 0x0000000d R9 : 0x55aa55aa R2 : 0x09000000 R10: 0xaa55aa55 R3 : 0x12345678 R11: 0x20000f94 R4 : 0xaa55aa55 R12: 0x20000ee8 R5 : 0x55aa55aa R13: 0x20000f34 R6 : 0xaa55aa55 R14: 0x000002cc R7 : 0x55aa55aa R15: 0x000002d8 PSW: 0x600000df nZCv...FIt sys-mode Backtrace: 0) 0x000002bc 1) 0x000002fc 2) 0x0000034c 3) 0x0000039c
Um den Backtrace zu interpretieren, schauen wir in die Map-Datei des Linkers, in der wir die Anfangsadressen unserer C Funktionen finden:
.text 0x000002ac 0x198 testxcept.o 0x000002ac sub3 0x000002ec sub2 0x0000033c sub1 0x0000038c main
Die Exception trat an Adresse 0x000002bc auf, also in der Funktion sub3. Dieses wurde an Adresse 0x000002fc aufgerufen, die innerhalb der Funktion sub2 liegt, welche wiederum an Adresse 0x0000034c, also von sub1 aufgerufen wurde. Letztere wurde an Adresse 0x0000039c aufgerufen, also von unserem Hauptprogramm.
Alternative können Sie auch das Programm addr2line aus der GNU Toolchain verwenden:
arm-elf-addr2line -f -e beispiel.elf 0x000002bc
Frühe Initialisierung von stdio
Bei der Fehlersuche in eigenen Anwendungen reicht es aus, stdout am Beginn des Hauptprogramms zur Verfügung zu haben. Probleme werden wir bekommen, wenn Teile aus Nut/OS selbst verändert wurden, die vor dem Eintritt in das Hauptprogramm ausgeführt werden und eine Exception verursachen. Da stdout zu diesem Zeitpunkt noch nicht zur Verfügung steht, führt die Ausgabe der Exception-Behandlung selbst zu einem neuen Fehler. Dieser Abschnitt zeigt eine Möglichkeit zur Lösung dieses Problems.
Wir nehmen an, dass der Initialisierungscode fehlerfrei läuft. Dieser in Assembler geschriebene Teil ist ohne JTAG Debugger nur mühsam zu überprüfen. Direkt anschließend wird NutInit aufgerufen, die erste in C geschriebene Funktion. Hier werden wird, möglichst am Anfang, die Initialisierung des stdout Streams einbauen. Für Zielsysteme, die auf dem ARM Prozessor basieren, befindet sich der zugehörige Quellcode in der Datei arch/arm/os/nutinit.c.
Öffnen Sie die Datei mit Ihrem Texteditor und fügen Sie folgende Zeilen hinzu, direkt vor der Funktion NutInit:
#ifdef EARLY_STDIO_DEV #include <sys/device.h> #include <stdio.h> #include <fcntl.h> struct __iobuf { int iob_fd; uint16_t iob_mode; uint8_t iob_flags; int iob_unget; }; #endif
Dieser Teil liefert alle erforderlichen Deklarationen. Am Beginn von NutInit, direkt nach möglicherweise zusätzlich nötigen Hardware-Initialisierungen, können wir nun stdout zuweisen. Fügen Sie folgende Zeilen hinzu, üblicherweise direkt vor dem Aufruf von NutHeapAdd:
#ifdef EARLY_STDIO_DEV { extern NUTDEVICE EARLY_STDIO_DEV; static struct __iobuf early_stdout; EARLY_STDIO_DEV.dev_init(&EARLY_STDIO_DEV); stdout = &early_stdout; stdout->iob_fd = (int)EARLY_STDIO_DEV.dev_open(&EARLY_STDIO_DEV, "", 0, 0); stdout->iob_mode = _O_WRONLY | _O_CREAT | _O_TRUNC; puts("\nNutInit"); } #endif
Wie Sie sehen, haben wir den Code in Präprozessorbedingungen eingerahmt. Dies macht es einfach, diese zusätzliche Funktionalität bei Bedarf ein- oder auszuschalten. Zur Aktivierung fügen Sie am Anfang der Quellcodedatei folgende Zeile ein:
#define EARLY_STDIO_DEV devDebug
Eine andere Möglichkeit wäre, folgende Zeile in die Datei nutbld/UserConf.mk einzufügen:
HWDEF+=-DEARLY_STDIO_DEV=devDebug
Das Device devDebug, welchem stdout damit zugewiesen wird, schickt die Ausgaben an die DBGU Schnittstelle. Steht diese nicht zur Verfügung, z.B. beim Ethernut 3 Board, nehmen Sie statt dessen devDebug0.
Erzeugen Sie nun die Nut/OS Bibliotheken und Ihre Anwendung neu. Wenn alles wie geplant funktioniert, wird Ihre Anwendung beim Start die Zeile
NutInit
ausgeben und evtl. auftretende Exceptions während der Initialisierungsphase des Systems melden. Falls nötig, können Sie problems weitere printf Befehle hinzufügen, selbst innnerhalb von Interruptroutinen.
Ergebnis
Im Vergleich mit Desktop-Computern weisen eingebettete Systeme beachtliche Unterschiede auf und erfordern besondere Vorgehensweisen bei der Fehlerbehandlung. Die hier demonstrierten Methode einer anwendungsspezifischen Routine erlaubt, in einer für die Anwendung geeigneten Weise darauf zu reagieren, während die von Nut/OS zur Verfügung gestellte Behandlung wertvolle Dienste beim Debugging leistet.
Harald Kipp
Castrop-Rauxel, 26.06.2009
Copyright
Copyright (C) 2008-2009 by Harald Kipp. Kopieren, Verbreiten und/oder Verändern ist unter den Bedingungen der GNU Free Documentation License, Version 1.3 oder einer späteren Version, veröffentlicht von der Free Software Foundation, erlaubt.
Document History
Date | Change | Thanks to |
---|---|---|
26.06.2009 | Inhalt von R14_abt beim Prefetch-Abort korrigiert, welcher im Thumb- und ARM-State gleich ist. | Stephen M. Rumble |
Note 3 zur Übersichtstabelle hinzugefügt. | ||
Copyright-Notiz hinzugefügt. |