Forum: Mikrocontroller und Digitale Elektronik arm-gcc RAM Belegung untersuchen


von Stefan F. (Gast)


Angehängte Dateien:

Lesenswert?

Bei einem Projekt gibt der Compiler folgende Meldungen aus:
1
Generating binary and Printing size information:
2
arm-none-eabi-objcopy -O binary "STM32F103_usb_test.elf" "STM32F103_usb_test.bin"
3
arm-none-eabi-size -B "STM32F103_usb_test.elf"
4
   text     data      bss      dec      hex  filename
5
   3512       12     1620     5144     1418  STM32F103_usb_test.elf

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:
1
.data           0x0000000020000000        0x4 load address 0x0000000008000dc0
2
                0x0000000020000000                . = ALIGN (0x4)
3
                0x0000000020000000                _sdata = .
4
 *(.data)
5
 .data          0x0000000020000000        0x4 src/main.o
6
                0x0000000020000000                SystemCoreClock
7
 *(.data*)
8
                0x0000000020000004                . = ALIGN (0x4)
9
                0x0000000020000004                _edata = .
10
11
.igot.plt       0x0000000020000004        0x0 load address 0x0000000008000dc4
12
 .igot.plt      0x0000000020000004        0x0 /opt/SystemWorkbench/plugins/fr.ac6.mcu.externaltools.arm-none.linux64_1.17.0.201812190825/tools/compiler/bin/../lib/gcc/arm-none-eabi/7.3.1/thumb/v7-m/crtbegin.o
13
                0x0000000020000004                . = ALIGN (0x4)
14
15
.bss            0x0000000020000004      0x254 load address 0x0000000008000dc4
16
                0x0000000020000004                _sbss = .
17
                0x0000000020000004                __bss_start__ = _sbss
18
 *(.bss)
19
 .bss           0x0000000020000004       0x1c /opt/SystemWorkbench/plugins/fr.ac6.mcu.externaltools.arm-none.linux64_1.17.0.201812190825/tools/compiler/bin/../lib/gcc/arm-none-eabi/7.3.1/thumb/v7-m/crtbegin.o
20
 .bss           0x0000000020000020        0x4 src/main.o
21
                0x0000000020000020                systick_count
22
 *(.bss*)
23
 *(COMMON)
24
 COMMON         0x0000000020000024      0x234 src/usb.o
25
                0x0000000020000024                Dtr_Rts
26
                0x0000000020000028                UsbTxBuf
27
                0x0000000020000128                txr
28
                0x000000002000012c                UsbRxBuf
29
                0x000000002000022c                LineCoding
30
                0x0000000020000234                rxw
31
                0x0000000020000238                CMD
32
                0x0000000020000250                txw
33
                0x0000000020000254                rxr
34
                0x0000000020000258                . = ALIGN (0x4)
35
                0x0000000020000258                _ebss = .
36
                0x0000000020000258                __bss_end__ = _ebss

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?

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

: Bearbeitet durch Moderator
von Stefan F. (Gast)


Lesenswert?

Vielen Dank, ich hab es verstanden.

Die Ausgabe des Compilers beinhaltet also den für Stack reservierten 
Bereich. Das war der Teil, der mir fehlte.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von Stefan F. (Gast)


Lesenswert?

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.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von Nop (Gast)


Lesenswert?

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.

von Stefan F. (Gast)


Lesenswert?

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.

von Nop (Gast)


Lesenswert?

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/

von Stefan F. (Gast)


Lesenswert?

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.

von c-hater (Gast)


Lesenswert?

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...

von Mw E. (Firma: fritzler-avr.de) (fritzler)


Lesenswert?

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 ;)

von Christopher J. (christopher_j23)


Lesenswert?

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 ;)

von Nop (Gast)


Lesenswert?

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.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.