Ethernut Home Hardware Firmware Tools Download Community
 
 
Suchen | Impressum | English

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

Ein typischer Speicher-Layout für Ethernut 3 ist:

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:

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.