Hallo zusammen,
Ich hänge gerade fest und komme nicht weiter...
Ich programmiere unter Ubuntu 13.10 mit dem arm-none-eabi Compiler aus
einem Repository [1] einen ARM Cortex M4, genau genommen den TI Tiva
TM4C123GH6PGEI. Eigentlich funktioniert das auch, doch eine UARTprintf()
Funktion von TI hängt sich nach spätestens ein paar Sekunden immer auf.
Eine Recherche im Internet ergab dann, dass der Stack vergrössert werden
muss damit diese printf() Funktion richtig funktioniert, da sie viel
Speicher im Stack benötigt. Ein Versuch unter Windows mit dem Code
Composer Studio hat diesen Verdacht bestätigt, nach dem Vergrössern des
Stacks/Heaps in den Projekteinstellungen läuft mein Programm
einwandfrei.
Doch wie kann ich bei meinem Compiler bzw. Linker arm-none-eabi-g++ die
Stack Grösse angeben? Im Linker Script? Wenn ja, wie?
Ich habe bisher erst AVRs programmiert und habe die ganze Geschichte mit
den Linker Scripts noch nicht so richtig durchschaut...
Ich verwende das Linker Script aus den Tiva Beispielen (siehe Anhang).
Compilliert wird folgendermassen:
Irgendwo in deinem Linker File wird ein Symbol für den Stack definiert
sein, sowas wie __stack_end oder ähnlich. Üblicherweise wird es irgendwo
hinter dem .bss und .data Segment definiert. Der Abstand zwischen der
Adresse dieses Symbols und dem Ende von .bss definiert deine Stack
Größe.
Das Symbol wird dann im Startup Code benutzt im den Stackpointer zu
initialisieren bzw. steht beim Cortex M in der Interrupt Vector Tabelle.
Quick und Dirty kann man das Symbol einfach mit der letzten RAM Adresse
definieren.
Habe mir gerade dein Linkerfile angeschauht, das ist ja echt mal
übersichtlich ;-).
Du kannst folgendes nach .bss einfügen:
/* User_heap_stack section, used to check that there is enough RAM
left */
._user_heap_stack :
{
. = ALIGN(4);
PROVIDE ( end = . );
PROVIDE ( _end = . );
_heap_start_ = .;
. = . + _Min_Heap_Size;
_heap_end_ = .;
_stack_start_ = .;
. = . + _Min_Stack_Size;
_stack_end_ = .;
. = ALIGN(4);
} >RAM
Und irgendwo an den Anfang:
/* Generate a link error if heap and stack don't fit into RAM */
_Min_Heap_Size = 0x400; /* required amount of heap */
_Min_Stack_Size = 0x200; /* required amount of stack */
In der Vector Tabelle muss dann als erster Eintrag das _stack_end_
stehen.
Damit auch ein malloc() mit Heap funktioniert brauchst du auch noch die
passende sbrk() Funktionen, die diese Symbole benutzt:
caddr_t _sbrk(int incr) {
static char * heap_end;
char * prev_heap_end;
if (heap_end == NULL) {
heap_end = & _heap_start_;
}
prev_heap_end = heap_end;
if (heap_end + incr > &__heap_end__) {
/* Some of the libstdc++-v3 tests rely upon detecting
out of memory errors, so do not abort here. */
errno = ENOMEM;
return (caddr_t) -1;
}
heap_end += incr;
return (caddr_t) prev_heap_end;
}
Hallo,
Vielen Dank für die Antworten!
Langsam kommt Licht in die ganze Sache... Dank dem Hinweis, dass der
Stackpointer an die ersten Stelle der Vektortabelle geschreiben werden
muss, konnte ich jetzt herausfinden wo mein Stack geblieben ist :-)
Witzigerweise gibts in meinem Startup Code ein Array, welches
anscheinend den Platz für meinen Stack reserviert:
1
staticuint32_tpui32Stack[64];
In die Vektortabelle wird dann dieser Stackpointer reingeschrieben:
Vergrössere ich mein mysteriöses Stack-Array, läuft mein Programm
einwandfrei, keine Abstürze mehr :-)
Jetzt wird mir die ganze Stack-Geschichte schon viel klarer.
Der Vorschlag von "Guest" möchte ich jetzt aber doch gerne noch
ausprobieren (bzw. implementieren, wenns dann funktioniert). Das Linker
Script habe ich ergänzt, doch im Startup Code hänge ich noch fest...
Als Initialer Stackpointer habe ich einfach "_stack_end_"
reingeschrieben. Doch stack_end ist ja dem Compiler zur Compillierzeit
noch unbekannt, daher müsste wohl noch eine extern Deklaration rein
oder? Aber wie müsste das aussehen? Ich habe schon ein paar
Möglichkeiten probiert, doch der uC will damit einfach nicht mehr
aufstarten...
mfg
Das funktioniert so auch nicht. Die Reset-Funktion löscht beim Startup
den Speicher und somit auch den eigenen Stack. Entweder du schreibst dir
eine eigene Reset-Funktion (evtl. gibt es die bei dir auch schon im
Projekt) oder du lässt es einfach so. Hat alles seine Vor- und
Nachteile.
Ohh schrieb:> Das funktioniert so auch nicht. Die Reset-Funktion löscht beim Startup> den Speicher und somit auch den eigenen Stack.
Was möchtest du uns damit sagen?? ;-)
Welche Reset funktion löscht welchen Speicher?
Und wieso sollte das ein Problem sein?
Was nach dem Reset auf dem Stack steht ist völlig irrelevant.
Urban B. schrieb:> Als Initialer Stackpointer habe ich einfach "_stack_end_"> reingeschrieben. Doch stack_end ist ja dem Compiler zur Compillierzeit> noch unbekannt, daher müsste wohl noch eine extern Deklaration rein> oder?
extern unsigned int stack_end;
und als ersten Eintrag in die Tabelle:
&_stack_end_,
Urban B. schrieb:> Witzigerweise gibts in meinem Startup Code ein Array, welches> anscheinend den Platz für meinen Stack reserviert:static uint32_t> pui32Stack[64];
Das geht im Prinzip natürlich auch, hat aber den Nachteil das dieser
Stackbereich irgendwo im .bss liegt und bei der Segmentinitialisierung
mit 0 gefüllt ist, was völlig unnötig ist.
Zweiter Nachteil ist das wenn dieser Stack überläuft man sich sofort
irgendwelche Variablen überschreibt.
Guest schrieb:> Ohh schrieb:>> Das funktioniert so auch nicht. Die Reset-Funktion löscht beim Startup>> den Speicher und somit auch den eigenen Stack.>> Was möchtest du uns damit sagen?? ;-)
:-)
Guest schrieb:> Urban B. schrieb:>> Witzigerweise gibts in meinem Startup Code ein Array, welches>> anscheinend den Platz für meinen Stack reserviert:static uint32_t>> pui32Stack[64];>> Das geht im Prinzip natürlich auch, hat aber den Nachteil das dieser> Stackbereich irgendwo im .bss liegt und bei der Segmentinitialisierung> mit 0 gefüllt ist, was völlig unnötig ist.> Zweiter Nachteil ist das wenn dieser Stack überläuft man sich sofort> irgendwelche Variablen überschreibt.
... und nicht den Heap.
Genau, was natürlich auch nicht unbedingt besser sein muss ;-).
Letztlich ist man natürlich völlig frei wo man Stack und Heap in den
Speicher legt. Klassische GCC Linker files definieren z.B. den Heap
Anfang auf die erste Adresse nach dem Ende des .bss und den Stack auf
die letzte RAM Adresse. Stack und Heap treffen sich dann halt irgendwo
in der Mitte... ;-).
Guest schrieb:> Genau, was natürlich auch nicht unbedingt besser sein muss ;-).>> Letztlich ist man natürlich völlig frei wo man Stack und Heap in den> Speicher legt. Klassische GCC Linker files definieren z.B. den Heap> Anfang auf die erste Adresse nach dem Ende des .bss und den Stack auf> die letzte RAM Adresse. Stack und Heap treffen sich dann halt irgendwo> in der Mitte... ;-).
Es ist bei der Cortex-MCU bzw. allgemein bei ARM eher unüblich einen von
den zwei oder mehr Stacks an das Speicherende zu legen.
Ohh schrieb:> Es ist bei der Cortex-MCU bzw. allgemein bei ARM eher unüblich einen von> den zwei oder mehr Stacks an das Speicherende zu legen.
Ich habe ja auch nur von klassischen GCC Linkerfiles gesprochen...aber
ja, ich habe sowas auch schon zu genüge bei Cortex M Linkerfiles
gesehen, sinnig finde ich das auch nicht.
OK jetzt läufts mit der Variante von "Guest (Gast)". Der Fehler war die
Bezeichnung "RAM" statt "SRAM" in der Ergänzung im Linkerscript.
Die Funktion _sbrk() kann ich einfach in irgend ein C File packen, auch
ich den Startup Code nehme ich an?
Ohh schrieb:> Es ist bei der Cortex-MCU bzw. allgemein bei ARM eher unüblich einen von> den zwei oder mehr Stacks an das Speicherende zu legen.
Hat das einen bestimmten Grund? Um Probleme vorzubeugen wäre es doch
ideal den Stack und den Heap möglichst weit auseinander zu legen.
Mein ursprüngliches Problem ist jetzt jedenfalls gelöst, vielen Dank für
eure Hilfe!
mfg
Urban B. schrieb:> Die Funktion _sbrk() kann ich einfach in irgend ein C File packen, auch> ich den Startup Code nehme ich an?
Im Prinzip ja, ich nehme an du benutzt die NewLib?
Schau mal hier rein: http://www.sourceware.org/newlib/
Urban B. schrieb:> Hat das einen bestimmten Grund? Um Probleme vorzubeugen wäre es doch> ideal den Stack und den Heap möglichst weit auseinander zu legen.
Optimal ist es das sowas gar nicht erst passiert. Beim Stack kann man
das nicht vermeiden, aber beim Heap sorgt gerade die sbrk() Funktion
dafür das ein malloc() NULL zurück liefert falls kein Heap mehr frei
ist.
Ohh schrieb:> Es ist bei der Cortex-MCU bzw. allgemein bei ARM eher unüblich einen von> den zwei oder mehr Stacks an das Speicherende zu legen.
Bei den Cortex-M Prozessoren wird ohne RTOS oft nur ein Stack verwendet.
Man kann zwar User/System trennen, aber das lohnt eigentlich nur, wenn
man den Speicherschutz verwendet (so überhaupt vorhanden).
Guest schrieb:> Urban B. schrieb:>> Die Funktion _sbrk() kann ich einfach in irgend ein C File packen, auch>> ich den Startup Code nehme ich an?>> Im Prinzip ja, ich nehme an du benutzt die NewLib?> Schau mal hier rein: http://www.sourceware.org/newlib/
Nein, bisher benutze ich die NewLib nicht. Vielleicht brauche ich die
später noch. Die UARTprintf() Funktion habe ich von TI übernommen, die
haben in Ihren Utils selbst eine geschrieben (eine ganz hässliche, mit
goto's...).
Macht es denn in Bezug auf meine Frage einen Unterschied ob ich die
NewLib benutze?
A. K. schrieb:> Ohh schrieb:>> Es ist bei der Cortex-MCU bzw. allgemein bei ARM eher unüblich einen von>> den zwei oder mehr Stacks an das Speicherende zu legen.>> Bei den Cortex-M Prozessoren wird ohne RTOS oft nur ein Stack verwendet.> Man kann zwar User/System trennen, aber das lohnt eigentlich nur, wenn> man den Speicherschutz verwendet (so überhaupt vorhanden).
Ab und an kann es dann doch zu einer Exception kommen und wenn die einen
frischen Stackpointer für den Debug-Output hat, ist es schon nicht so
unnötig und spart etwas Zeit.
Ich verwende aber auch nur RTOS auf den Controllern und dort ist es mit
drin.