Ich dachte bisher, dass der statische RAM Bedarf data+bss (also 1632
bytes) ist und habe das nie weiter hinterfragt. Heute wollte ich zum
ersten mal im map File nachschauen, was das drin ist:
Im groben und ganzen kann ich das nachvollziehen. Insbesondere habe dort
die beiden größten Variablen UsbTxBuf und UsbRxBuf mit ihrer korrekten
Größe (jeweils 256 bytes) wieder gefunden.
Aber, ich sehe hier, dass die letzte Belegte Adresse im RAM 258 ist. Das
wären 600 Bytes. Da fehlen noch 1032 Bytes. Wo sind die denn?
Stefan F. schrieb:> Aber, ich sehe hier, dass die letzte Belegte Adresse im RAM 258 ist. Das> wären 600 Bytes. Da fehlen noch 1032 Bytes. Wo sind die denn?
Da:
1
/* User_heap_stack section, used to check that there is enough RAM left */
2
._user_heap_stack :
3
{
4
. = ALIGN(8);
5
PROVIDE ( end = . );
6
PROVIDE ( _end = . );
7
. = . + _Min_Heap_Size;
8
. = . + _Min_Stack_Size;
9
. = ALIGN(8);
10
} >RAM
1
0x0000000000000400 _Min_Stack_Size = 0x400
Die fehlenden 8 Byte scheinen Alignment zu sein.
Der Stack scheint insgesamt durchaus größer zu sein als dies; diese
Rechnung dient nur dazu sicherzustellen, dass mindestens dieses 1 KiB
für den Stack vorhanden ist.
Stefan F. schrieb:> Die Ausgabe des Compilers beinhaltet also den für Stack reservierten> Bereich.
Wobei das nicht wirklich der vom Stack benutzte Bereich ist. Ich sehe
natürlich deinen Startup-Code nicht, vermute aber, dass dort der Stack
bei top of RAM anfängt.
Der Linkerscript hat lediglich Vorkehrungen dafür, dass man oberhalb des
statisch belegten RAMs noch eine Mindestgröße für Stack und Heap
vorgeben kann. Sollte die statische Belegung so groß werden, dass diese
(kombinierte) Mindestgröße nicht mehr gewährleistet ist, dann gibt es
dadurch einen Linkerfehler. Nebeneffekt dieses Tricks ist, dass die so
garantierten Bereiche als nicht initialisierter (.bss) RAM-Verbrauch im
ELF-File auftauchen.
Jörg W. schrieb:> Wobei das nicht wirklich der vom Stack benutzte Bereich ist.
Ist mir klar. Ich gehe davon aus, dass dieses einfache Testprogramm fast
gar keinen Stack braucht. Sicher weniger als 100 Bytes. Das war einfach
bloss der Default Wert, den der Projektassistent vorgibt.
Stefan F. schrieb:> Ich gehe davon aus, dass dieses einfache Testprogramm fast gar keinen> Stack braucht.
Ich meinte das jetzt auch von den Adressen her: dieser garantierte
Bereich beginnt bei 0x20000258 und endet bei 0x20000657, aber dein Stack
beginnt beispielsweise bei (je nach Device) 0x2000FFFF und geht von dort
nach unten.
Das ist ein purer Rechentrick, damit der Linker meckern kann, wenn die
statische Belegung so groß wird, dass für Heap + Stack zu wenig übrig
bleibt.
Wobei ich es sehr unglücklich finde, wenn der Stack ganz oben ist - der
gehört nach ganz unten. Sonst hat man bei Stackoverflow merkwürdige
Fehler, während man mit Stack ganz unten in einen sauberen Hardfault
rennt. Dankenswerterweise sind ja beim ARM ganz unten nicht auch noch
irgendwelche Register.
Nop schrieb:> Wobei ich es sehr unglücklich finde, wenn der Stack ganz oben ist - der> gehört nach ganz unten.
Ich bin das so gewohnt.
> Sonst hat man bei Stackoverflow merkwürdige Fehler
Spätestens wenn Variablen offensichtlich überschrieben wurden, ist mir
klar, was los ist.
Stefan F. schrieb:> Ich bin das so gewohnt.
Was kein Argument ist. Man kann Sachen auch jahrzehntelang falsch
machen. Bei AVRs ist ja eine Begründung da, weil ganz unten im
RAM-Bereich die Register sind, aber auf Cortex-M eben nicht.
Das sind nur Hersteller-Beispiele für Linkerscripte, die eben nur als
Beispiel gedacht sind und keineswegs zum Produktiveinsatz gedacht oder
geeignet sind.
> Spätestens wenn Variablen offensichtlich überschrieben wurden, ist mir> klar, was los ist.
Das kannst Du nur feststellen, solange Du den Debugger angeschlossen
hast. Im regulären Betrieb wirst Du nur merken, daß das Gerät sich
manchmal irgendwie komisch verhält.
Die maximal unglückliche Konstellation hast Du nämlich dann, wenn der
Overflow nur passiert, wenn Dein Hauptprogramm gerade maximal tief ist
UND genau dann auch noch ein Interrupt kommt. Oder wenn dazu zwei
Interrupts kaskadiert kommen müssen.
Hier ein schon etwas älteres, aber relevantes Blogposting von Nigel
Jones dazu:
https://embeddedgurus.com/state-space/2014/02/are-we-shooting-ourselves-in-the-foot-with-stack-overflow/
Nop schrieb:> Man kann Sachen auch jahrzehntelang falsch machen.
Alle mir bekannten Projektassistenten legen den Stack standardmäßig ans
Ende des RAM. Auch CubeMx. Wenn das alle so machen, kann es nicht so
"falsch" sein, wie du es darstellst.
Egal wo der Stack liegt, muss ich als Programmierer sicherstellen, dass
er niemals überläuft. Ansonsten liegt ein "fail by design" vor. Wenn er
nicht überläuft, ist es wiederum egal, was beim Überlauf passiert - weil
es nicht passiert.
Ich muss mir von niemandem vorschreiben lassen, wie ich das während der
Entwicklung verifiziere.
Stefan F. schrieb:> Alle mir bekannten Projektassistenten legen den Stack standardmäßig ans> Ende des RAM. Auch CubeMx. Wenn das alle so machen, kann es nicht so> "falsch" sein, wie du es darstellst.
Projektassistenten sind genauso doof wie Projektleiter. Beide haben
typisch keinerlei Ahnung vom Projekt...
Wenn die Hardware eine Möglichkeit bereithält, billig (d.h.: ohne
zusätzlichen Rechenaufwand) Heap<->Stack-Kollisionen bzw. die jeweiligen
Overflows zu erkennen, dann nutzt man die NATÜRLICH. Ganz egal, was
irgendein verschissener Projektassistent irgendeiner verschissenen IDE
hier vorgibt...
Nop schrieb:> während man mit Stack ganz unten in einen sauberen Hardfault> rennt.
Definier mal bitte oben und unten.
Ich hab schon Speicherdiagramme gesehen die mit der höchsten Adresse
anfangen und welche die mit der niedrigsten anfangen ;)
Ich versuch mal genauer zu erzählen was du meinst:
Also den Stack an eine niedrige Adresse packen, der ist fully descending
bei ARM und rennt dann aus dem SRAM Speicherbereich raus.
Das gibt nen Busfault. (der zu einem hardfault wird wenn dort kein
Handler registriert ist)
Dementsprechend den Heap dann an die höchstmögliche RAM Adresse packen,
der wächst dann auch raus und generiert einen Busfault.
Das ist aber auch nicht immer möglich, es gibt ja SoCs mit mehreren
direkt hintereinander folgenden SRAM Blöcken und manchmal soll einer DMA
exklusiv sein.
Stefan F. schrieb:> Auch CubeMx. Wenn das alle so machen, kann es nicht so> "falsch" sein, wie du es darstellst.
Also genau der GammelMX ist jetzt kein Beispiel für guten Code oder eine
gute Projektstruktur ;)
Stefan F. schrieb:> Alle mir bekannten Projektassistenten legen den Stack standardmäßig ans> Ende des RAM. Auch CubeMx. Wenn das alle so machen, kann es nicht so> "falsch" sein, wie du es darstellst.
Du hast die Wahl zwischen "fail early with error" und "fail late but
silently". Such dir das übel aus, was dir besser passt.
Wenn du den Stack an den Anfang des RAMs legst ist er halt nur so groß,
wie du das vorgibst, z.B. 1kB. Dann ist absolut Schluss... Sense...
Bus-Fault!
Wenn man den Stack ans Ende packt, dann kannst du ihm natürlich auch
eine Größe zuweisen, um eine Warnung zu bekommen, falls für den Stack
nicht der Platz zur Verfügung wäre. Der Witz ist aber, dass der durchaus
größer werden kann als der "reservierte" Speicher und trotzdem
funktioniert alles bestens. Wenn der Stack jedoch irgendwann mal den
Heap überschreibt und das vielleicht auch noch in Abhängigkeit der
dynamischen Größe des Heap, dann wünsche ich viel Spaß beim Auffinden
solcher Heisenbugs ;)
Stefan F. schrieb:> Ich muss mir von niemandem vorschreiben lassen, wie ich das während der> Entwicklung verifiziere.
Das haben sich die Toyota-Leute aus dem verlinkten Blog auch gedacht.
Für Hobby ist es letztlich ja auch nur Dein Ding. Profis hingegen hören
hin, wenn jemand wie Nigel Jones was sagt, anstatt "meine IDE macht das
aber so" (WTF?!) zu sagen.
Mw E. schrieb:> Definier mal bitte oben und unten.
Niedrige Zahlen sind immer unten. Das ist bei Höhenangaben genau wie
beim Kontostand. Wie herum man die Seite des Refmans ausdruckt, ist
dafür egal. ;-)
> Ich versuch mal genauer zu erzählen was du meinst:> Also den Stack an eine niedrige Adresse packen, der ist fully descending> bei ARM und rennt dann aus dem SRAM Speicherbereich raus.> Das gibt nen Busfault. (der zu einem hardfault wird wenn dort kein> Handler registriert ist)
Richtig.
> Dementsprechend den Heap dann an die höchstmögliche RAM Adresse packen,> der wächst dann auch raus und generiert einen Busfault.
Genau. Die Hauptidee: ein klarer Absturz nebst Reboot ist besser und vor
allem leichter zu troubleshooten als mit irreguläres Verhalten
weiterzulaufen. Bei Computern gibt es ja kein "ein bißchen" - entweder,
man hat das Programm noch unter Kontrolle, oder man hat es nicht mehr.
Idealerweise hat man ohnehin irgendeine Art Fehlerspeicher für diese
Faulthandler, so daß man das am Gerät auslesen kann. Beispielsweise die
Backup-Register der RTC oder das Backup-RAM bieten sich an, je nachdem,
was der konkrete Chip bietet.
> Das ist aber auch nicht immer möglich, es gibt ja SoCs mit mehreren> direkt hintereinander folgenden SRAM Blöcken und manchmal soll einer DMA> exklusiv sein.
Dann muß man halt schauen, wie man den DMA in einen geeigneten Bereich
verfrachtet. Bei Chips mit CCM (z.B. STM32F4) hat man das Problem ganz
grundsätzlich nicht, weil der Stack ja ins CCM gehört, welches gar
keinen DMA-Zugriff erlaubt.