Hallo,
ich versuche nun seit Tagen ein STM32 Board zum Laufen zu bekommen. Nach
ewigem Gefummel mit den Loaderfiles habe ich immerhin nun erste
Ergebnisse. Es blinkt und gibt etwas auf dem USART aus. Letzteres
allerdings mit 9600Bd anstelle der eingestellt 19200Bd. Und genau darum
geht es: warum ist die USART Geschwindigkeit falsch.
Das Board hat einen STM32F103VET Prozessor mit 8MHz Quarz.
Als Entwicklungsumgebung nutze ich Code::Blocks mit ARM-GCC
(arm-none-eabi-gcc).
Ich habe mal die Funktion RCC_GetClocksFreq genutzt um die
Taktfrequenzen anzuzeigen:
1
SYSCLK: 48000000
2
HCLK : 48000000
3
PCLK1 : 48000000
4
PCLK2 : 48000000
5
ADCCLK: 24000000
Eigenlich sollte SYSCLK ja auf 72MHz stehen. Warum nur tut es das nicht?
Um zu verstehen was ich so treibe füge ich im Folgenden mal meine
Quelltexte an. Ich hoffe, das sprengt hier nicht den Rahmen.
Vielleicht kann mir jemand die Nase auf die Stelle stossen wo ich einen
Fehler mache.
Meine Compileroptionen sehen so aus:
Sowas natürlich für alle beteiligten C-Dateien. In meinem Programm lasse
ich auch per SYSTICK eine LED im Sekundentakt blinken. Das klappt mit
korrekter Geschwindigkeit.
Meine stm32f10x_conf.h:
Leider kann ich nicht durch den Code debuggen, da ich den OpenOCD mit
STLINK-V2 unter Codeblocks nicht ordentlich zum Laufen bekomme. GDB
startet das Target immer sofort nach dem Download des Binaries und
stellt dann erst die Breakpoints ein. Mit dem Erfolg, dass die
Breakpoints erst gesetzt werden wenn das Programm bereits in der
Hauptschleife läuft.
Vielleicht dazu noch meine Debugger Settings:
>Eigenlich sollte SYSCLK ja auf 72MHz stehen. Warum nur tut es das nicht?
CubeMX zeigt ua. den Clocktree an.
Sollte beim Durchblicken helfen...
>Vielleicht dazu noch meine Debugger Settings:>load ./default/Blink_STM32F1.bin>file ./bin/default/Blink_STM32F1.bin
'load' kann direkt ein elf File laden. Dann hast Du auch die
Debuginformationen im GDB zur Verfügung.
'load' und 'file' zusammen ist sinnlos.
>monitor sleep 1000
Warum?
Angstschlaf?
>GDB startet das Target immer sofort nach dem Download des Binaries
Mit der gezeigten GDB Initialisierung nicht...
Das Target startet erst mit einem 'continue'
Mglw. sendet Dein Codeblocks das implizit...
Vielleicht kannst Du die Breakpoint Kommandos den Debugger Settings
hinzufügen.
Es muss aber auch einen "offiziellen" Weg geben. Ich kenne CB aber
nicht.
>monitor reset
Bei OOCD
mon reset halt oder
mon reset init benutzen.
Siehe OOCD Manual.
Das mit dem CubeMX hatte ich tatsächlich schon gemacht. Insofern war mir
theoretisch klar wie es gehen sollte. Problem sind die mehrfach
ineinander geschachtelten Includes der ST Standard Libraries die alle
möglichen Defines erzeugen und nutzen um den Taktpfad zu konfigurieren.
Wahrscheinlich wäre es einfacher, die Standard Peripheral Library NICHT
zu nutzen, sondern die paar Bits einfach so zu setzen.
Das Geheimnis war am Ende ganz einfach. Der STM32F103VET ist KEIN "Value
Line" Prozessor. Daher gehört in den Compileraufruf statt
-DSTM32F10X_HD_VL einfach nur -DSTM32F10X_HD. Warum das nun Auswirkungen
auf den USART aber nicht auf den Systick hat, habe ich nicht weiter
untersucht.
Nun zum Debug. Die Anregungen von "Schnupftabak" waren augenöffnend. Die
Halt-Befehle heissen nicht nur "halt", sonder "reset halt". Nun habe ich
es so eingestellt:
1
monitor reset halt
2
load ./default/Blink_STM32F1.elf
3
monitor reset halt
Der Sleep-Befehl aus der Ursprungskonfig sollte dazu dienen dem Debugger
eine Chance zu geben, die Breakpoints zu setzen bevor der Prozessor
losrennt. Ist aber nicht nötig.
Nun startet zwar der Prozessor sofort los (warum bloß) wird aber sofort
bei einem Breakpoint auf dem ResetHandler angehalten. Dies zeigt
Code::Blocks allerdings nicht an. Nun kann man irgendwohin einen
Breakpoint setzen und "Start/Continue" drücken und er läuft wie geplant
bis dorthin.
Also alles gut. Danke!
Schade, dass es weder im Code::Blocks noch auf meiner Platine einen
Resetknopf gibt.
Nun muss ich sehen, dass ich malloc und Co zum Laufen bekomme. Im
Gegensatz zur antiken WinAVR Installation muß man ja offensichtlich beim
modernen ARM Zeugs alles selber machen. :-(
Thorsten E. schrieb:> Wahrscheinlich wäre es einfacher, die Standard Peripheral Library NICHT> zu nutzen, sondern die paar Bits einfach so zu setzen.>> Das Geheimnis war am Ende ganz einfach. Der STM32F103VET ist KEIN "Value> Line" Prozessor. Daher gehört in den Compileraufruf statt> -DSTM32F10X_HD_VL einfach nur -DSTM32F10X_HD. Warum das nun Auswirkungen> auf den USART aber nicht auf den Systick hat, habe ich nicht weiter> untersucht.
Also mit anderen Worten: Es läuft zwar mittlerweile, aber du weißt noch
immer nicht, was du da eigentlich getan hast und wie das Ganze
funktioniert. Obendrein kommen mir die genannten Kommandozeilenparameter
suspekt vor. Kann da nicht einfach sowas wie --cpu=Cortex-M3 stehen (wie
beim Keil)? Falls ich mich recht erinnern sollte, wäre das beim Gcc etwa
so: -mcpu=cortexm3 oder so ähnlich.
Ansonsten ist dein gepostetes Zeugs dramatisch zu viel, als daß sich da
jemand dort durcharbeiten wollte. Und mit deiner Erkenntnis, daß man
diese ST-Lib lieber außen vor lassen sollte, hast du Recht. Sag ich
schon seit langem. Die Unbedarften glauben, mit Cube, ST-Lib und anderem
einschlägigen Zeugs die Bürde des Lesens in den Referenzmanuals und das
Verstehenlernen der zugrundeliegenden Hardware umgehen zu können, aber
in Wirklichkeit bleiben sie bloß ahnungslose Dilettanten, die sich in
ihrer IDE verlieren, anstatt sich um den µC zu kümmern.
W.S.
> Also mit anderen Worten: Es läuft zwar mittlerweile, aber du weißt noch> immer nicht, was du da eigentlich getan hast und wie das Ganze> funktioniert.
Stimmt so halb. Ich habe mir die StdPeriphLib nicht im Detail angesehen.
Muß ich aber eigentlich aber auch nicht. Ich schau mir ja auch nicht den
Quelltext meines Windows an. Bei einigen Dingen sollte man auch glauben,
dass sich der Entwickler etwas dabei gedacht hat. :-)
> suspekt vor. Kann da nicht einfach sowas wie --cpu=Cortex-M3 stehen (wie> beim Keil)? Falls ich mich recht erinnern sollte, wäre das beim Gcc etwa> so: -mcpu=cortexm3 oder so ähnlich.
Genau das steht ja auch drin (s.o.). Allerdings fehlte es in der Tat
beim Linkeraufruf. Es ist mir aber schleierhaft wozu der Linker wissen
muss, welche CPU genutzt wird. Der Compiler erzeugt den Code, der Linker
fügt ihn nur zusammen. Aber man lernt halt nie aus. Achja, der Linker
will auch wissen, dass er Thumb-Code erzeugen soll staun.
> Die Unbedarften glauben, mit Cube, ST-Lib und anderem> einschlägigen Zeugs die Bürde des Lesens in den Referenzmanuals und das> Verstehenlernen der zugrundeliegenden Hardware umgehen zu können, aber> in Wirklichkeit bleiben sie bloß ahnungslose Dilettanten, die sich in> ihrer IDE verlieren, anstatt sich um den µC zu kümmern.
Naja, da kann ich Dir nur halb zustimmen. Wie oben schon angedeutet will
ich mich ja eher um meine Applikation kümmern und nicht erst das
"Betriebssystem" entwickeln. Das ist Ressourcenverschwendung wenn das
jeder für sich tun muss.
Insofern finde ich es auch traurig, dass man so viel "frickeln" muss um
in Gang zu kommen.
Warum wird nicht eine vernünftige clib samt den hardwarenahen Routinen
(_sbrk, _write, ...) zur Verfügung gestellt, die man einfach benutzen
kann. Ab besten als (quelloffene) Library und nicht als Haufen
Quellfiles die man umständlich und unübersichtlich in sein Projekt
kopieren muss.
Ein Musterbeispiel war da m.E. der WinAVR. Einmal installieren, ein
Headerfile einbinden und ein mitgeliefertes Makefile anpassen und
loslegen. WinAVR hatte ich ohne AVR Vorkenntnisse in ca einer Stunde am
Laufen, mit malloc, printf und Co. Beim ARM bin ich nun schon seit 1,5
Wochen dabei und habs immer noch nicht vollständig im Gange.
Falls jemand eine funktionierende und zuverlässige Implementierung von
_sbrk und Co kennt, bitte mal zeigen.
So, nun habe ich vermutlich einen Flamewar zwischen Puristen und
"Mausschubsern" ausgelöst. Ich lass mich überraschen.
>> W.S.
Thorsten E. schrieb:> So, nun habe ich vermutlich einen Flamewar zwischen Puristen und> "Mausschubsern" ausgelöst. Ich lass mich überraschen.
Nö.
Ein jeder kann es halt so treiben wie er will. Aber manche kriegen
mangels eigener Fähigkeiten oder zu großer Faulheit keinen Fuß auf den
Teppich und dann wird behauptet, daß es ja so unsäglich schwer sei. Und:
Das ist es auch. Wenn ich mir das Quellcode-Gestrüpp so ansehe, was da
mancher mit dem Benutzen der ST-Lib erzeugt und wo man sich mit
Sicherheit darin verheddert, dann wundert mich nix mehr.
Und dein "Ich habe mir die StdPeriphLib nicht im Detail angesehen. Muß
ich aber eigentlich aber auch nicht.... Bei einigen Dingen sollte man
auch glauben, dass sich der Entwickler etwas dabei gedacht hat" ist ein
Denkweise in die falsche Richtung.
Ich kann dir sagen, WOZU die Firma ST ihre unsägliche Lib unter die
Leute gebracht hat: Um die Naiven fest an sich zu binden. Wer nicht
anders kann, als sowas zu benutzen und die HW nicht mehr kennt oder
kennen WILL, der muß halt zum Chip von ST greifen und obendrein auch
noch nen größeren nehmen als eigentlich nötig, da er einen Haufen
Geschwulst damit am Bein hat.
Verstehe mal: es gibt da nen Interessenkonflikt zwischen µC-Hersteller
und Programmierer.
Nochwas: dein "Ich schau mir ja auch nicht den Quelltext meines Windows
an." ist auch falsche Denke. Natürlich tust du das nicht, denn du bist
kein PC-Mainboard-Hersteller und auch keiner von Bills Programmierern.
Aber wenn du Firmware für nen µC schreiben willst, dann bist du sehr
wohl µC-Entwickler und damit eigentlich in der Pflicht, deine HW zu
kennen. Du bist kein 'Benutzer' oder 'Verbraucher', sondern einer der im
Herstellungsprozeß angesiedelt ist, also im weiten Sinn bist du
Hersteller.
Und was dein "..will ich mich ja eher um meine Applikation kümmern und
nicht erst das "Betriebssystem" entwickeln. Das ist
Ressourcenverschwendung wenn das jeder für sich tun muss." betrifft, da
sag ich dir, daß das durchaus keine Ressourcenverschwendung ist. Gerade
das Gebiet der µC-Anwendungen ist IMMER eine Mischung aus Gerätetechnik,
Schaltungstechnik, qualifizierter Kenntnis des betreffenden µC und
Programmieren. Da ist das Schreiben oder Anpassen von Lowlevel-treibern
für dies und das ein wirklich notwendiger Teil des Kennenlernens des µC.
Wer das ignoriert, bleibt mit den Füßen in der Luft hängen. Und
abgesehen davon ist das Ganze ja auch nicht wirklich schwer - jedenfalls
nicht für einen Ingenieur.
W.S.
Ich glaube da haben wir uns etwas falsch verstanden. Ich WILL durchaus
den Chip verstehen (sonst würde ich Arduino nehmen). Und ich habe ja
auch selbst schon bemerkt, das die ST Lib alles andere als ein
Musterbeispiel der Programmierung ist. Ich muß ja zum Beispiel den Chip
kennen lernen um Dinge wie einen DMA-Transfer zum LCD zu bauen, oder die
Ansteuerung eine LED Matrix. Da will ich dann gerne lernen wie DMA,
USART und Co zu konfigurieren sind. Das musste ich beim AVR auch und das
ist ja auch gerade der Spaß an der Sache.
Aber ein funktionsfähiges Grundgerüst erleichtert den Einstieg und
schafft nicht schon Frustration bevor es losgeht. Dazu gehört meines
Erachtens die Prozessorinitialisierung und vor allem eine halbwegs
vollständige Implementierung der C Library. Warum soll jeder für sich
wieder neu die Speicherverwaltung erfinden.
Und die Prozessorinitialisierung mit seinen vielen Taktmöglichkeiten ist
ja nun nicht gerade einsteigerfreundlich. Da hilft durchaus ein
grafisches Tool wie CubeMX. Nicht um Code zu erzeugen, sondern einfach
nur die richtigen Bits in den richtigen Registern anzuzeigen.
Ich denke, in der Tat werde ich die StdPeriphLib auf Dauer nicht
verwenden, da sie viel zu viel Unützes mitbringt und unübersichtlich
ist.
Allein schon diese unsäglichen Initialisierungsorgien über Structs.
Warum nicht einfach:
Nun da ich einen laufenden Prozessor habe, kann ich ja auch damit
experimentieren und mir eine für mich ausreichende übersichtlicher Lib
bauen. Dann vielleicht auch lieber einfach eine für jeden verwendeten
Prozessortyp anstatt dutzender unübersichtlicher Defines. Was brauch die
denn schon groß? Prozessorinit, die Basic Funktionen für die CLib und
einige übersichtliche Initfunktionen für USART, Portpins und Timer.
Alles andere wird eher selten genutzt und kann bei Bedarf dazugebaut
werden.
Zurück zum Thema: Wenn Du eine Möglichkeit in deinem Debugsystem hast,
die SFRs anzuzueigen, ist es ja relativ einfach, das Problem zu finden:
Du weisst dann ja, welche Sollwerte in die USART und SYSCFG Register
hineinmüssen und kannst durch schrittweises Step over herausfinden,
welche Routinen wann und wie die Registerinhalte verdrehen...
Thorsten E. schrieb:> Warum nicht einfach:UsartInit(uint8_t usartno, uint8_t pintx, uint8_t> pinrx, uint32_t baudrate);
Nö, lieber nicht sowas. Denk doch mal nach: den USART (als UART) zu
initialisieren ist was ganz anderes, als die Pins zu verteilen.
Abgesehen davon solltest du recht genau wissen, welchen U(S)ART du
initialisieren willst. Also bleibt eigentlich nur die Baudrate und wenn
du willst die Kommunikationseinstellung wie z.B 8N1 oder so.
Was die Grundinitialisierung betrifft, so ist die eigentlich bei den
STM32F10x relativ leicht. Sie ist nicht wirklich gut, sowas können
andere besser, aber sie geht. Zumindest ist das Taktschema
leichtverständlich und bei den Ports kann man erstmal zwischen GPIO und
Alternativfunktion wählen. Das nicht wirklich Gute (um nicht zu sagen as
Blöde daran) ist das nötige Procedere, wenn man mehr als nur eine
Alternativfunktion auf dem Pin hat. Dann muß man bei diesen µC nämlich
alle anderen irgendwie aus dem Wege schaffen und das bedeutet, in die
generelle Taktversorgung und in die jeweiligen Register der
ungewünschten Funktion hineinzugrätschen. Geht, ist aber unschön. Bei
anderen hat man ein dediziertes Register pro Pin, wo man die gewünschte
Alternativfunktion direkt eintragen kann und das ist viel angenehmer.
So. Ich guck mal, ob ich für dich ein bissel Beispielcode habe.
W.S.
In meinem Vorschlag der UART Initialisierung war ja die Uartnummer mit
drin. Ok, Datenformat nicht. Brauch ich in aller Regel auch nicht.
Mich stört aber, das jede Konfiguration sich über zig Zeilen erstreckt.
Aber das ist vielleicht Geschmacksache.
Ich habe inzwischen funktionsfähige Grundfunktionen für die Nanolib
gefunden. Nur eines funktioniert nicht.
Es wird die Funktion __MFP_Sp oder so ähnlich (sorry, hab den genauen
Namen grad unterwegs nicht parat) genutzt um den Stackpointer
auszulesen. Diese gibt es aber in meiner StdPeriphLib nicht. Wie kriegt
man das hin? Assembler?
W.S. schrieb:> Kann da nicht einfach sowas wie --cpu=Cortex-M3 stehen (wie> beim Keil)? Falls ich mich recht erinnern sollte, wäre das beim Gcc etwa> so: -mcpu=cortexm3 oder so ähnlich.
Ja für den GCC schon. Diese Defines werden aber "gebraucht", weil man
darüber in diesem ST-Gestrüpp irgendwelche Sachen ein- und ausblendet.
Also werden sie in dem Fall über Kommandozeile beim Compileraufruf
definiert.
> Ich kann dir sagen, WOZU die Firma ST ihre unsägliche Lib unter die> Leute gebracht hat: Um die Naiven fest an sich zu binden.
Richtig. Das ist keine "Hardware Abstraction Layer", weil es nichts
abstrahiert, sondern das sind nur Wrapperfunktionen für die Register.
Eigentlich ein HOL, Hardware Obfuscation Layer, und um die Parameter zu
verstehen, die 1:1 aus dem reference manual abkopiert sind, muß man
selbiges dann trotzdem lesen.
Thorsten E. schrieb:> Bei einigen Dingen sollte man auch glauben,> dass sich der Entwickler etwas dabei gedacht hat. :-)
Ich schätze, wenn Du den ST-Library-Krams noch weiter nutzt, wird dieser
Glaube sich bei Dir auch noch erledigen.
Thorsten E. schrieb:> Aber ein funktionsfähiges Grundgerüst erleichtert den Einstieg und> schafft nicht schon Frustration bevor es losgeht. Dazu gehört meines> Erachtens die Prozessorinitialisierung
Nein, denn bei ARM ist das Grundprinzip, daß Du vom Energieverbrauch her
nur das zahlst, was Du auch benutzt, und per Default ist nichts
aktiviert. Man muß also (fast) alles selber einschalten, und das ist
auch gut so, weil man nur das einschaltet, was man braucht.
Ist auch besser so herum: denn wenn man vergißt, etwas einzuschalten,
merkt man es sehr schnell daran, daß etwas nicht geht. Wenn alles an
wäre, und man vergißt, es auszuschalten, ist das viel schwieriger, weil
man nur einen erhöhten Energieverbrauch hat, aber nirgendwohin konkret
zeigen kann, woran er denn liegen muß.
> und vor allem eine halbwegs> vollständige Implementierung der C Library.
Ist doch bei GCC dabei, oder nicht? Ich nutz die eh nicht. :-)
> Warum soll jeder für sich> wieder neu die Speicherverwaltung erfinden.
Embedded mit malloc arbeiten ist ohnehin in 95% der Fälle schlichtweg
die falsche Entscheidung und kommt normalerweise von Leuten, die sonst
nur auf dem PC programmieren.
> Und die Prozessorinitialisierung mit seinen vielen Taktmöglichkeiten ist> ja nun nicht gerade einsteigerfreundlich.
Sie bietet halt eine Menge Flexibilität, und wenn Dein einziges Anliegen
ist "wie kriege ich den Takt voll aufgedreht", dann nutzt Du davon nur
einen Bruchteil aus, und deswegen erscheint es umständlich.
Thorsten E. schrieb:> Der Compiler erzeugt den Code, der Linker> fügt ihn nur zusammen. Aber man lernt halt nie aus. Achja, der Linker> will auch wissen, dass er Thumb-Code erzeugen soll staun.
Der Compiler erzeugt den Objektcode. Aber ich könnte mir vorstellen,
warum der Linker Thumb wissen sollte. Weil nämlich beim Aufruf einer
Thumbfunktion im LSB der Zieladresse immer eine 1 steht, wenn die
angesprungene Funktion Thumb ist. Das ist bei ARM einfach so. Wenn die
physikalische Zieladresse im Flash z.B. "0x40" wäre, aber die
Zielfunktion in Thumb ist, dann geht der Jump im Binärfile nach 0x41.
Das geht schon beim Resetvektor so los, wo das, was in der Tabelle
steht, nicht ganz mit dem übereinstimmt, was im Mapfile steht, genau
deswegen.
Thorsten E. schrieb:> Ich denke, in der Tat werde ich die StdPeriphLib auf Dauer nicht> verwenden, da sie viel zu viel Unützes mitbringt und unübersichtlich> ist.
Eben. Die kannste nehmen als Ergänzung zum reference manual, d.h. als
Samplecode, aber nicht als Realcode.
Thorsten E. schrieb:> ich versuche nun seit Tagen ein STM32 Board zum Laufen zu bekommen.
Hast Du mal versucht, in main() nach Aufruf von
SystemInit ();
auch
SystemCoreClockUpdate ();
aufzurufen? Du zeigst die Funktion zwar oben, aber Du rufst sie
nirgendwo auf.
Nop schrieb:>>> Warum soll jeder für sich>> wieder neu die Speicherverwaltung erfinden.>> Embedded mit malloc arbeiten ist ohnehin in 95% der Fälle schlichtweg> die falsche Entscheidung und kommt normalerweise von Leuten, die sonst> nur auf dem PC programmieren.>
So eine Aussage kommt in 95% der Fälle von Leuten, die vor 10-15 Jahren
aufgehört haben, sich mit dem technologischen Fortschritt
auseinanderzusetzen...
1. Wer heutzutage auf dem PC programmiert (Klugscheissermodus an: Du
wolltest wahrscheinlich schreiben "FÜR den PC programmiert," denn 99%
der Embedded Entwicklung findet auch auf PC hosts statt, aber das nur am
Rande), weiß noch nicht mal mehr, was malloc() ist. Dynamische
Speicherverwaltung heisst new, den Rest macht der Garbage Collector.
Welcome to 2001 Technology!
2. Vermutlich arbeitest Du noch in der Embedded Welt der AVR Generation,
in denen 512K Flash mehr war als Jemals ein Mensch brauchen würde, und
mehr als Messfühlerdatenpumpen, die Nullen und Einsen von A nach B
geschoben haben, hattest Du nie programmiert?
- Jede halbwegs anspruchsvolle Middleware, die heutzutage eingesetzt
wird (RTOS,Filesystem,DB System, Netzwerkstack, Kryptierbibliotheken
etc) setzt zwingend dynamisch verwalteten Speicher voraus
- Gerade Ressourcenarme Prozessoren profitieren enorm davon, dass nicht
immer das Maximum an benötigtem Speicher vorgehalten wird (triviales
Beispiel: Kommunikationsprotokolle mit variablen Payloadgrößen).
- Standard C Lib Implementationen von malloc() und free() sind in jedem,
wirklich jedem Ökosystem zu finden
Ich dachte immer, ICH sei schon ein Fossil, weil ich mich mit Händen und
Füßen gegen die Javaphilosophie bei Embedded wehre, aber obwohl ich z.T.
mit sehr sehr ressourcenbeschränkten boards arbeite, kann ich mir
heutzutage keine noch so primitive Anwendung vorstellen, die OHNE
dynamische Speicherverwaltung auskommt.
Gnugnu schrieb:> So eine Aussage kommt in 95% der Fälle von Leuten, die vor 10-15 Jahren> aufgehört haben, sich mit dem technologischen Fortschritt> auseinanderzusetzen...
Oder die ernsthafte Projekte haben, die z.B. auch Zulassungs-Regularien
unterliegen.
> 2. Vermutlich arbeitest Du noch in der Embedded Welt der AVR Generation,
Deine Kristallkugel spinnt.
> - Jede halbwegs anspruchsvolle Middleware, die heutzutage eingesetzt> wird (RTOS,Filesystem,DB System, Netzwerkstack, Kryptierbibliotheken> etc) setzt zwingend dynamisch verwalteten Speicher voraus
Auch falsch. Bzw. nur richtig, wenn man von embedded Linux spricht, und
da hat man natürlich ein so großes System, daß es praktisch wie ein PC
ist.
> - Standard C Lib Implementationen von malloc() und free() sind in jedem,> wirklich jedem Ökosystem zu finden
Das ändert nichts an issues wie Speicherfragmentierung, besonders wenn
die CPU keine MMU hat. Es ändert auch nichts daran, daß so ein System
nicht mehr testbar ist, weil die Fragmentierung sehr davon abhängt, was
wie wo in welcher Reihenfolge vorher gemacht wurde.
> aber obwohl ich z.T.> mit sehr sehr ressourcenbeschränkten boards arbeite, kann ich mir> heutzutage keine noch so primitive Anwendung vorstellen, die OHNE> dynamische Speicherverwaltung auskommt.
Dann bin ich froh, wenn Du keine kritischen Controller und dergleichen
entwickelst. Wahrscheinlich sagt Dir aus dem Grund auch sowas wie MISRA
nichts, und das ist ja noch harmlos.
Mit Deinem Ansatz würde Deine Software in solchen Branchen nie zum
Kunden gehen, weil sie bereits im Codereview durchfallen würde.
Aber für Hobby ist schon OK.
Gnugnu schrieb:> Ich dachte immer, ICH sei schon ein Fossil, weil ich mich mit Händen und> Füßen gegen die Javaphilosophie bei Embedded wehre, aber obwohl ich z.T.> mit sehr sehr ressourcenbeschränkten boards arbeite
Ach, auch du kein Fan von Jamaica-VM ?
Also mal ganz im Ernst: bei 99.9% aller kleineren µC-Projekte, wo
Controller wie die kleineren STM32F10x mit typisch 64K Flash und 8..20K
RAM eingesetzt werden, ist sowas wie du es genannt hast, einfach nicht
anzutreffen - weil es keinen Sinn ergibt. Und bei etwas größerem Zeugs
nehmen die Leute mittlerweile nen Modul, z.B. Colibri, Armstone und so.
Entweder mit nem WinCE drauf oder mit nem Linux.
So. und nun warte ich mal, ob der Thorsten sich nochmal meldet.
W.S.
Ich dachte, ich halte mich mal vornehm zurück und was aus der für und
wider dyn. Speicherverwaltungsdiskussion so wird.
Vielleicht erstmal zu meinem Umfeld: ich betreibe das Mikrocontrollern
als Hobby. Insofern werden keine Flugzeuge vom Himmel fallen oder
Atomkraftwerke explodieren wenn mein Controller mal stehen bleibt.
Derzeit habe ich zwei STM32 Projekte in Planung:
Einen LED Würfel (gähn, nicht schon wieder). Der Würfel liegt hier schon
fünf Jahre ohne Controller rum. Übrigens mit einem Haufen TI's TLC5940
als PWM Controller. Als µC ist auf der Platine ein LPC2148 vorgesehen,
aber noch nicht bestückt. Es gibt aber auch eine Pfostenleiste an der
man einen externen Controller anschliessen könnte. Übrigens liegt noch
eine Leerplatine rum, falls jemand Lust hat.
Eine Heizungs"uhr", die vielleicht später eine richtige
Heizungssteuerung wird. Dazu wollte ich das STM32 Board nehmen mit dem
ich gerade übe. Ist Teil vom Chinesen mit 2,4" TFT dran.
Ich nutze malloc und Co selten, aber für arbeiten mit z.B. SD-Karten
oder TCPIP Stacks ist es schon nützlich wenn man die riesigen Buffer die
so ein Filesystemtreiber meist braucht auch mal freigeben kann wenn man
ihn gerade nicht braucht. Oder wenn man Daten sammeln will deren Menge
und Struktur nicht exakt vorhersehbar sind. Deswegen nehm ich ja gerade
die größeren ARMs. Sonst könnte ich auch beim AVR bleiben.
Ausserdem funktionieren diverse Ausgabefunktionen nicht ohne _sbrk.
Jetzt wird der eine oder andere sagen printf sei eh böse, weil riesig.
Aber erstens hab ich gerade gelernt, dass es ein deutlich kleineres
iprintf gibt und zweitens kratzt mich das bei 512kB Flash kaum. Und
falls mal C++ kommen sollte, ist da dann vermutlich eh Schluss ohne dyn.
Speicherverwaltung.
Inzwischen habe ich eine funktionsfähige Minimalbibliothek. Ich musste
allerdings die Stackprüfung rauslassen, weil ich noch nicht weiß wie man
den SP in C lesen kann. Ich wollte eine einfach Prüfung reinbauen:
1
char *stack = __get_MSP();
2
if (heap_end + incr > stack - securedistance)
Leider gibt es __get_MSP nicht.
Das ist also die derzeit verbleibende Frage. Ansonsten werde ich mir
erstmal eine kleine "ARMlibc" bauen um das ST Monster loszuwerden.
Vielen Dank auf jeden Fall für die Hinweise und Tipps. Insbesondere auch
zum Debugger, der jetzt halbwegs funktioniert.
Thorsten E. schrieb:> Ansonsten werde ich mir> erstmal eine kleine "ARMlibc" bauen um das ST Monster loszuwerden.
Ja, tu das. Du wirst es irgendwann mal schätzen, ein eigenes Portfolio
an Lowlevel-Treibern und kleinen Problemlösern zu haben.
Ich hänge dir hier mal ne kleine Anregung dran, da du ja mit einem
"STM32F103VET" den Thread angefangen hast.
Das Ganze ist eigentlich nur ne Fingerübung meinerseits gewesen. Es
basiert auf einem STM32F103C8T6, den die chinesischen Ebay-Händler wohl
sehr zu lieben scheinen. Es gibt ihn solo oder im Fünferpack oder als
ST-Link2 mit Gehäuse oder als Minimalst-Leiterplatte.
Ich habe die Prinzipschaltung des ST-Link2, die von ST veröffentlicht
wurde, mal in eine kleine eigene LP umgesetzt. Da hat mich so einiges
sehr geärgert, vor allem daß offenbar mehrere Pins einfach
parallelgeschaltet sind. Macht man eigentlich nicht, wenigstens ein
Entkoppel-R sollte dazwischen sein. Aber Augen zu und durch. Die ST-Link
Firmware (V2.16.4 - STM32 + STM8 Debug), die es im Netz gibt, läuft
drauf, die Entwurfsfehler mit den parallelgeschalteten Pins scheinen
also OK zu sein - aber das war ja nicht mein Interesse.
Was also kann die gepostete Eigenkreation? Sie hat nen Startup in
Assembler, eine Systemkonfiguration nach eigener Machart, einen USB-VCP
über den man mit dem Teil kommunizieren kann, dito über USART1 und 2,
eine Kommandoauswertung, eine Systemuhr, Ein- und
Ausgabe-Konvertierungen, ein Event-System und serielle Treiber. stolpere
nicht über gio.c, das dient zur Vereinheitlichung. Damit kann man alle
installierten seriellen Ports einheitlich ansprechen (incl. USB) und man
kann auch in einen String drucken, falls man sowas je brauche sollte. Du
brauchst mit diesem Zeugs vermutlich nie wieder sprintf.
Und das ganze braucht nur 11.5 K im Flash - allerdings mit dem Keil
übersetzt. Nach meiner Erfahrung braucht es beim Gcc etwa 20% mehr.
Natürlich habe ich das Ganze NICHT speziell für den kleinen
STM32F103C8T6 geschrieben. Fast alle Teile bis auf config.c stammen aus
anderen Projekten, der USB von einem NUC100-Projekt (Nuvoton), die
seriellen Treiber aus ganz früheren Zeiten (frühe ARM7TDMI von Atmel),
conv.c aus Motorola-Zeugs (ist steinalt) und so weiter. Das Neueste sind
eigentlich die Events, die stammen aus der Lernbetty. (womit das viel
weiter oben genannte Stichwort gefallen wäre...)
Wenn man erstmal eine Sache mit bedachtsam konzipierten Treibern
erledigt hat, dann bleibt einem das für alle nachfolgenden Projekte
erhalten. Lediglich in einigen Details der hardwareabhängigen Teile muß
man dann noch ne Anpassung an den konkreten µC machen. Die Headerfiles
bleiben dabei aber unverändert. So hat man für alle draufgesetzten
Programmteile eine wirklich hardwareunabhängige Schnittstelle.
Also guck dir es an und verwende es nach deinem Gusto. Den USB-VCP hab
ich auch für einige LPC-Typen (2478, 4088, u.a.). Man kann ihn in der
vorliegenden Form auch für die STM32F30x benutzen.
Hau rein! Und viel Glück.
W.S.
Thorsten E. schrieb:> char *stack = __get_MSP();> if (heap_end + incr > stack - securedistance)
Autsch. Das ganze Speicherlayout, was dem zugrundeliegt, ist doch schon
unglücklich. Hier liegt offensichtlich der Stack oben und wächst als
descending stack nach unten auf den Heap zu. Ein Desaster, was nur
darauf wartet zu passieren:
http://embeddedgurus.com/state-space/2014/02/are-we-shooting-ourselves-in-the-foot-with-stack-overflow/
Dieser Check ist sinnlos, weil er zwar kontrolliert, daß der Heap nicht
zu arg nach oben wächst - aber das gilt ja nur in genau dem Moment der
Allokation. Wenn das erstmal alloziert ist, hindert nichts den Stack
daran, weiter nach unten zu wachsen.
Sinnvoller ist es, das Speicherlayout so zu wählen, daß man den Stack
nach unten an den Anfang des RAM legt. Dann wächst er auf 0 zu, und bei
einem Stack Overflow rauscht man in einen hard fault. Das ist immerhin
ein klarer Absturz - viel schlimmer ist es, wenn es wie mit dem
aktuellen Layout nicht abstürzt, sondern sich nur undefiniert verhält.
Extrem schwer zu debuggen, sowas.
Umgedreht kann dann der Heap oberhalb der globalen Variablen verbleiben,
und seine Endbegrenzug ist dann einfach das Ende des RAM-Bereiches.
Wie dimensioniert man den Stack nun? Hoffen und beten? Die Pfuschmethode
des Stack-Spraying (alles mit einem Muster initialisieren und gucken,
bis wohin es mal eingerissen ist)?
Nein, bei GCC setzt man beim Compiler-Aufruf die Option -fstack-usage.
Dann sieht man den Stackbedarf der einzelnen Funktion. Pro Call-Level
muß man noch 8 Bytes hinzurechnen. Dann verfolgt man seinen Call-Tree
der Applikation und schaut, welchen Bedarf man im Worst Case hat.
Hierzu addiert man dann noch den Stackbedarf aller Interruptfunktionen
plus natürlich dem Sichern der Register (bei Exceptions werden 8
Register gesichert). Also man nimmt an, daß sie allesamt zugleich genau
dann anfallen, wenn man auch in der Applikation den schlimmsten Punkt
erreicht hat. Dann schlägt man noch 10-20% drauf, damit man die ganze
Rechnung nicht bei jeder Kleinänderung nochmal machen muß.
Und schon braucht man den Ansatz mit dem Stackregister gar nicht mehr.
Ach ja, und nochwas.. GCC hat mitunter eine merkwürdige Inline-Logik, da
sollte man embedded sicherheitshalber mehr Kontrolle ausüben.
Wenn man zwei Funktionen hat, die beide beachtlich Stack verbrauchen,
aber nie zugleich aufgerufen werden, dann sollte man sie mit dem
Attribut noinline versehen. Ansonsten kann GCC auf die Idee kommen, sie
zu inlinen, und ihr Stackbedarf würde sich dann bei einem ungünstigen
Calltree trotzdem addiern.
Faustregel: Wenn es viel Stack verbraucht, dann unbedingt prüfen, wie
der Stackverbrauchspfad im Falle von Inlining aussähe. Im Zweifel dann
ein noinline setzen.
Nochmal zum Thema Speicherfragmentierung: Wenn man seine
Allokationsmengen immer zur nächsten Zweierpotenz aufrundet, dann hat
man keine Fragmentierung und verschwendet nie mehr als 50%. Alternativ
kann man auch immer dieselbe Blockgröße allozieren, dann fragmentiert
auch nichts.
Thorsten E. schrieb:> Insofern werden keine Flugzeuge vom Himmel fallen oder> Atomkraftwerke explodieren wenn mein Controller mal stehen bleibt.
Das ist auch nicht so der Punkt. Selbst wenn ich mal Bastelprojekte
mache, gucke ich mir doch an, was in kritischen Systemen gemacht wird
und welche Ideen ich übernehmen kann. Weil ich möchte, daß auch meine
Bastelsachen zuverlässig laufen - ich find's halt befriedigend.
Denn die verbieten malloc ja nicht, weil es vom Teufel gemacht wurde,
der hat bekanntlich stattdessen ohnehin ein struct mit drei
void-Pointern.
Vor ca. 20 Jahren habe ich mal eine LED Matrix mittels Ethernut
programmiert. Mit ganz vielen Mallocs und dem furchtbaren
Speicherlayout. Und was soll ich sagen. Die Uptime steht derzeit auf
11,2 Jahre. Der eine Absturz war ein Stromausfall.
Das ist mir zuverläsig genug.
Die spannende Frage, warum läßt sich die core_cm.c nicht compilieren und
wie finde ich wo der Fehler ist, wenn der Compiler von irgendwelchen
Temporärdateien redet, die man nicht anschauen kann, weil sie weg sind.
Hi W.S.,
irgendwie hatte ich dein Mail mit dem ZIP File überlesen.
Hochinteressante Bibliothek. Werd ich mir die Tage mal zu Gemüte führen.
Jetzt bastele ich aber gerade erstmal eine kleine Testhardware für
meinen LED Würfel (damit ich nicht immer den klobigen Würfel
umherschleppen muss wegen doppelter Haushaltsführung)
Was mir auf die Schnelle an der Bibliothek aufgefallen ist, dass die
Interruptroutinen mit __isr getaggt sind. Das scheint beim gcc nicht
nötig zu sein. Was steckt denn da dahinter?
Mich hat es schon etwas gewundert, dass in einigen gcc Beispielen die
ISRs ganz normale Funktionen sind. Eigentlich muss man doch bestimmt
einige wichtige Register sichern.
Thorsten E. schrieb:> Hi W.S.,>>> Was mir auf die Schnelle an der Bibliothek aufgefallen ist, dass die> Interruptroutinen mit __isr getaggt sind. Das scheint beim gcc nicht> nötig zu sein. Was steckt denn da dahinter?> Mich hat es schon etwas gewundert, dass in einigen gcc Beispielen die> ISRs ganz normale Funktionen sind. Eigentlich muss man doch bestimmt> einige wichtige Register sichern.
Das hat mit dem gcc nichts zu tun, sondern mit dem target. Manche
Prozessoren benötigen speziellen Code für die ISRs, manche nicht (der
ARM Cortex hat z.B. keinen von "normalen" Funktionsaufrufen
unterscheidbaren stack frame, zumindestens was die Sicht der
aufgerufenen Funktion betrifft, aber manche andere Prozessoren behandeln
Exceptions anders (dort gibt es z.B. eine RTE statt einer RTS Andweisung
zur Rückkehr)). Vermutlich übersetzt das __isr je nach konfiguriertem
target anders.
M.M. nach ist es aber kein Fehler, eine Funktion als ISR Funktion
zumindestens kenntlich zu machen, damit man beim Lesen des Codes weiss,
dass ein anderer Prozessorkontext aktiv ist als bei einer "normalen"
Funktion. Deswegen schreibe ich auch in jedem Funktionheader den
Kommentar "wird im Kontext <der task xyz>|<des abc isr>" aufgerufen
Ja, das mit den unterschiedlichen Return Befehlen kenne ich ja auch vom
AVR (und vom Z80). Oder es gibt gar unterschiedliche Stacks (Usermode,
Systemmode oder so).
Aber selbst wenn es von daher nicht nötig wäre, müssen doch eigentlich
innerhalb jeder ISR mindestens die vom Compiler genutzen Register
gesichert werden, da man ja nicht vorhersehen kann, wann der Interrupt
zuschlägt.
Deswegen müsste der Compiler (oder man selbst) ja in der ISR eine
Registersicherung durchführen. Also muss er wissen, das die betreffende
Routine eine ISR sein soll. Wie sagt man das dem gcc.
Oder macht der ARM sowas womöglich in Hardware?
Anscheinend geht das im gcc so:
1
__attribute__((interrupt("IRQ")))
Komischerweise finde ich nur wenig Beispiele, die das tatsächlich so
machen. Ist es nun nötig oder nicht?
Das hängt vom ABI ab. Beim Cortex M ist es so, dass R0-R4 lt. AAPCS
destruktiv benutzt werden dürfen, d.h. es ist nicht garantiert, dass der
Wert einer dieser Register bei Austritt aus der Funktion gleich dem bei
Eintritt ist (das ABI benutzt R0 für den 1. Parameter und die Ausgabe
und R1-R4 für weitere Parameter).
Der Cortex Kern sichert folgerichtig beim ISR Eintritt R0-R4 sowie
weitere Register auf dem Stack (welcher geht hier zu weit), was aber für
den Compiler komplett unsichtbar ist (glaubt der Compiler z.B. einen
Rückgabewert ermitteln zu müssen, wird der in R0 geschrieben, aber da
nach ISR Rückkehr R0 wieder restautiert wird, ist der "neue" Wert beim
Aufruf als ISR weg). Andere Register, die benützt werden, müssen
explizit gesichert und am Ende wieder restauriert werden, aber wenn der
Compiler AAPCS kompatiblen Code generiert, passiert das sowieso.
Also je nachdem, welche Annahmen das Toolset über die Registernutzung
trifft, kann (und hoffentlich wird) so ein Schlüsselwort _interrupt()
oder wie immer es heisst Alles tun, um den Teil der vollständigen
Prozessorstatusrestaurierung, der nicht vom ABI abgedeckt ist, zu
ergänzen.
Thorsten E. schrieb:> Was mir auf die Schnelle an der Bibliothek aufgefallen ist, dass die> Interruptroutinen mit __isr getaggt sind. Das scheint beim gcc nicht> nötig zu sein. Was steckt denn da dahinter?
Interrupt-Service-Handler müssen beim ARM als solche gekennzeichnet
sein, sonst geht die Sache schief. Das hängt mit den Aufruf-Konventionen
und mit dem Sichern und Restaurieren von Registern und mit dem
Rücksprung zusammen, also mit Funktionskopf und -ende.
Beim Gcc gibt's das ganz genauso, es wird nur völlig anders formuliert -
die konkrete Bezeichnung dafür hab ich vergessen, war ziemlich länglich.
Ich hatte mich zuletzt bei der Lernbetty mit dem Gcc befaßt, ist also
schon ein paar Jahre her - normalerweise benutze ich den Keil und dort
wird das wie oben mit __isr gemacht. Ich hatte aber bei der Lernbetty in
irgend eine Headerdatei ein #define dafür hineingesetzt, um bei den
C-Quellen möglichst Codegleichheit Keil<-->Gcc zu ermöglichen.
W.S.
Thorsten E. schrieb:> Anscheinend geht das im gcc so:> __attribute__((interrupt("IRQ")))> Komischerweise finde ich nur wenig Beispiele, die das tatsächlich so> machen. Ist es nun nötig oder nicht?
Ja, jetzt hab ich's auch gelesen.
Also das war's.
Zumindest beim ARM7TDMI war das zwingend nötig, denn der hatte ja noch
eine ganze Reihe unterschiedlicher Stacks. User, Szupervisor, FastInt
und so weiter.
Ich würde das auch beim Cortex vor jede ISR schreiben, mehr als daß der
Gcc meint, daß dies nicht nötig sei, kann dabei ja wohl nicht passieren.
W.S.
Anscheinend muss der Stackpointer ein vielfaches von 8 sein wenn man
unterfunktionen nach AAPCS aufruft. Das muss nach einem Interrupt nicht
automatisch der Fall sein, und _attribute_... erzeugt Code, der den
Stackpointer richtet.
rmu schrieb:> Anscheinend muss der Stackpointer ein vielfaches von 8 sein wenn man> unterfunktionen nach AAPCS aufruft.
Da ist AAPCS leider etwas unklar; in 5.2.1.1 ist als "Universal stack
constraint" ein word alignment (also 4) aufgeführt, in 5.2.1.2 in Rahmen
eines "public interfaces" (das leider nicht definiert ist) ein double
word alignemnt (also 8). 4 muss also IMMER erfüllt sein, 8 nur in
Spezialfällen.
> Das muss nach einem Interrupt nicht> automatisch der Fall sein, und _attribute_... erzeugt Code, der den> Stackpointer richtet.
? Wiese muss das NACH einem Interrupt nicht automatisch der Fall sein?
Ein Interrupt muss den Prozessor im selben Zustand hinterlassen, bei dem
der Interrupt aufgerufen wurde, d.h. nach dem Interrupt muss der SP
identisch zu vorher sein, deswegen macht deine Aussage keinen Sinn -
meintest Du "alignment bei Auftreten des Interrupts?"
In jedem Fall arbeite ich schon seit vielen Jahren mit Cortex M
Prozessoren, und eine explizite Deklaration eines ISR durch ein
Attribute ist sehr selten. Ein guter Indikator ist FreeRTOS, wo der
SysTick Handler in den meisten ports einfach nur als void
SysTickHandler(void) deklariert ist; da Richard Barry die meisten ports
selber gesehen und abgesegnet hat und er sehr genau weiss was er tut UND
FreeRTOS auf Cortex Systemen weltweit extrem häufig und fehlerfrei
läuft, wäre ein Problem hier früher oder später aufgetaucht...
allerdings sind in den Standard Cortex ports der Pending Service Handler
und der Portservicehandler mit _attribute_ ((naked)) getaggt. Werde
ich mir bei Gelegenheit mal ansehen, was dahinter steckt - das ist aber
von der Diskussion hier unabhängig...
Ruediger A. schrieb:> ? Wiese muss das NACH einem Interrupt nicht automatisch der Fall sein?> Ein Interrupt muss den Prozessor im selben Zustand hinterlassen, bei dem> der Interrupt aufgerufen wurde, d.h. nach dem Interrupt muss der SP> identisch zu vorher sein, deswegen macht deine Aussage keinen Sinn -> meintest Du "bei Auftreten des Interrupts?"
Hab mich vielleicht falsch ausgedrückt...
Interrupt unterbricht Prozessor, der sichert (ein paar) Register (inkl.
aktuellen PC), und springt in die ISR. Dort angelangt ist der SP u.U.
zwar ein vielfaches von 4, aber nicht von 8. Der ISR Eintrittscode, den
der Compiler mit _attribute_ erzeugt sollte dann feststellen, ob er
den SP um 4 kleiner machen muss.
Nachdem die ISR abgearbeitet ist wird dann im Falle des Falles der SP
wieder erhöht und die ISR beendet, der Proz erledigt den Rest.
Ich hab in meinen ISRs eigentlich auch kaum noch wo so ein attribute
angegeben, funktionieren trotzdem, allerdings rufe ich auch nicht
unbedingt andere Funktionen in meinen ISRs auf...
Ruediger A. schrieb:> 4 muss also IMMER erfüllt sein, 8 nur in Spezialfällen.
Ich meine mich zu entsinnen, daß man letzteres für den Datentyp double
braucht.
> allerdings sind in den Standard Cortex ports der Pending Service Handler> und der Portservicehandler mit attribute ((naked)) getaggt.
Das bedeutet, daß die Register beim Aufrufen dieser Funktion nicht
gesichert werden, sondern darum muß die Funktion sich selber kümmern.
Das kann man beispielsweise gebrauchen, wenn man einen stack overflow
handler schreiben will, wo also der hard fault kommt, weil der SP in den
Wald außerhalb des RAM zeigt. Würde man da die Register sichern, dann
würde wieder in den Wald geschrieben.
Also muß man vor dem exception handling erstmal den SP prüfen und gff.
wieder auf einen gültigen Wert setzen, bevor man mit einer C-Funktion
(die aus dem exception handler aufgerufen wird) irgendwelche Sachen wie
die Anzeige eines Bluescreens machen kann.
Nop schrieb:> Ruediger A. schrieb:>>> 4 muss also IMMER erfüllt sein, 8 nur in Spezialfällen.>> Ich meine mich zu entsinnen, daß man letzteres für den Datentyp double> braucht.>>> allerdings sind in den Standard Cortex ports der Pending Service Handler>> und der Portservicehandler mit attribute ((naked)) getaggt.>> Das bedeutet, daß die Register beim Aufrufen dieser Funktion nicht> gesichert werden, sondern darum muß die Funktion sich selber kümmern.>
ok, danke für die Auffrischung (wußte ich auch irgendwann mal). Macht
auch komplett Sinn, da die beiden Handler den Context Switch
implementieren, also momentanen Taskzustand einfrieren, einen anderen
Taskzustand restaurieren und mit fake bx in einen völlig anderen Zustand
springen als bei Eintritt... da wäre eine Stackmanipulation durch vom
Compiler generierten Code natürlich tödlich.
Thx