Forum: Compiler & IDEs STM32 / GCC, Linkerscript & NOLOAD => richtig verwendet?


von Ralf (Gast)


Lesenswert?

Hallo,

ich bin grad über die Tücken von LinkerScripts gestolpert. Ich habe ein 
STM32F429-Discovery. Da hat's u.a. ein 8MByte-SDRAM und ein LCD. Das 
SDRAM möchte ich für den LCD Grafikbuffer sowie Heap und sonstige Buffer 
verwenden. Der Stack bleibt im internen RAM - ein bisschen soll da ja 
auch drin bleiben und ich sah keinen Sinn drin, den Stack auch im SDRAM 
zu haben.

Das SDRAM an sich kann ich verwenden, das ist passend konfiguriert und 
getestet. Das Verschieben des Heap ins SDRAM hat auch geklappt. Die 
LCD-Treiber sind noch nicht geschrieben. Bis hierhin dachte ich, dass 
ich Linkerscripts relativ(tm) gut verstanden habe. Auf die Nase gefallen 
bin ich nun beim erstmaligen Anlegen eines Buffers im SDRAM - das Binary 
wurde mehrere Gigabyte groß. Kurz recherchiert, NOLOAD für die 
entsprechende Linkersektion als Stichwort gefunden - soweit, so gut...

Hier der entsprechende Ausschnitt aus dem Linkerscript (ohne NOLOAD, der 
Vollständigkeit halber mit Stack):
1
/* User_heap_stack section, used to check that there is enough "RAM" Ram  type memory left */
2
  ._user_stack :
3
  {
4
    . = ALIGN(4);
5
    PROVIDE ( end = . );
6
    PROVIDE ( _end = . );
7
    . = . + _Min_Stack_Size;
8
    . = ALIGN(4);
9
  } >RAM
10
11
  /* SDRAM section, containing LCD buffer and heap */
12
  ._user_lcd_heap :
13
  {
14
    . = ALIGN(4);
15
    PROVIDE ( _slcd_buffer = . );
16
    . = . + _lcd_buffer_size - 1;
17
    PROVIDE ( _elcd_buffer = . );
18
    . = ALIGN(8);
19
    PROVIDE ( _heap_start = . );
20
    . = . + _Min_Heap_Size;
21
    . = ALIGN(8);
22
  } >SDRAM
23
24
  _heap_end = ORIGIN(SDRAM) + LENGTH(SDRAM) - 1;

Nur hat dann der Heap nicht mehr funktioniert :) Also habe ich das SDRAM 
nun in drei Sektionen aufgeteilt, wobei der Grafikbuffer und der Bereich 
für sonstige Buffer/Daten nun mit NOLOAD angegeben sind, der Heap ist 
quasi unverändert.
Hier die geänderte Version:
1
  /* LCD buffer section */
2
  ._user_lcd (NOLOAD) :
3
  {
4
    . = ALIGN(4);
5
    PROVIDE ( _slcd_buffer = . );
6
    . = . + _lcd_buffer_size - 1;
7
    PROVIDE ( _elcd_buffer = . );
8
    . = ALIGN(4);
9
  } >SDRAM
10
11
  /* user data section */
12
  ._user_data (NOLOAD) :
13
  {
14
    . = ALIGN(4);
15
  } >SDRAM
16
17
  /* heap section */
18
  ._user_heap :
19
  {
20
    . = ALIGN(4);
21
    PROVIDE ( _heap_start = . );
22
    . = . + _Min_Heap_Size;
23
    . = ALIGN(4);
24
  } >SDRAM
25
26
  _heap_end = ORIGIN(SDRAM) + LENGTH(SDRAM) - 1;
Der Grafikbereich ist 1Mbyte groß und der Buffer, welchen ich angelegt 
hatte ebenfalls. Also bleiben 6MByte für den Heap. Das scheint nun wie 
gewünscht zu funktionieren. Geprüft habe ich es, indem ich in den Buffer 
geschrieben habe und im Debugger den Speicher ausgelesen hatte - der 
Inhalt steht beim Offset 1MByte. Das Heap-Gekräuse beginnt bei Offset 
2MByte, scheint also nun auch wieder zu funktionieren.

Ich hätte nun drei Fragen zum Verständnis:
1) Der Heap darf nicht mit NOLOAD angelegt werden, weil die dynamische 
Speicherverwaltung zwar an sich funktioniert, aber die entsprechenden 
Variablen nicht initialisiert werden, ist das korrekt?
2) Ist die oben gezeigte Aufteilung in drei Sektionen korrekt oder nur 
"korrekter"? Bzw. geht's auch einfacher, bspw. Grafik- und sonstige 
Buffer in eine Sektion?
3) Das Initialisieren von Variablen geht ja nun nicht - ist das die 
Kröte die man schlucken muss oder geht das doch so wie bei den Variablen 
im internen RAM? Für das aktuelle Projekt sind abgesehen vom Heap 
eigentlich wirklich nur Buffer im SDRAM angedacht, d.h. eine 
Initialisierung ist nicht nötig - aber wäre interessant zu wissen.

Grüße

von Bauform B. (bauformb)


Lesenswert?

Initialisierte Variablen im SDRAM könnten daran scheitern, dass die 
SDRAM-Hardware zu spät konfiguriert wird. Der data Bereich wird ja ganz 
kurz nach dem Reset kopiert (in der crt0?). Diese Routine müsste auch 
zwei getrennte Bereiche kopieren - nicht unmöglich, aber 
unwahrscheinlich, dass das vorgesehen ist. Beides nichts, was man im 
Linker Script ändern könnte.

von Ralf (Gast)


Lesenswert?

Hallo Bauform,

> Initialisierte Variablen im SDRAM könnten daran scheitern, dass
> die SDRAM-Hardware zu spät konfiguriert wird.
Ich wusste, ich hatte etwas wichtiges vergessen zu erwähnen, Asche auf 
mein Haupt: sämtliche SDRAM-relevante Initialisierung, etc. wird direkt 
im StartUp-File gemacht, noch bevor die Daten geladen/initialisiert 
werden.

> Der data Bereich wird ja ganz kurz nach dem Reset kopiert (in der crt0?).
> Diese Routine müsste auch zwei getrennte Bereiche kopieren - nicht
> unmöglich, aber unwahrscheinlich, dass das vorgesehen ist. Beides nichts,
> was man im Linker Script ändern könnte.
Ich denke im LinkerScript müsste man da dann nix mehr ändern, lediglich 
dem Linker müsste man austreiben, ein mehrere GByte großes Binary zu 
erstellen... Allerdings ist das nur ne Vermutung.

Grüße

von D. B. (d_b)


Lesenswert?

Hast du dir mal das map-File angeschaut? Dort sieht man unter der 
"Linker script and memory map" Sektion mit aufsteigender Adresse welche 
Objekte platziert werden. Außerdem sieht man was mit dem Adressoperator 
passiert wenn er durch einen Ausdruck manipuliert wird. GNU LD erzeugt 
das map-File mit -Map.
Wenn ich mich recht entsinne hatte ich schon Probleme mit einem Linker 
Skript, wo Ausdrücke wie ". = 0x1234" interpretiert wurden als ". += 
0x1234" - warum auch immer. Dadurch entstehen natürlich riesig große 
Sections. ". += 0x456" hat dann korrekt funktioniert. Das könnte man 
dann an besagter Stelle im map-File entdecken.
Die (NOLOAD) Direktive sorgt in der Tat dafür, dass keine Daten in der 
Section geladen werden, sie also nicht in der Binärdatei landen. Warum 
da überhaupt was gelandet ist kann ich dir nicht sagen, das Problem 
hatte ich auch schon und habe dann (NOLOAD) benutzt. Vielleicht wurde 
SDRAM im Memory Layout nicht korrekt angelegt?

von Ralf (Gast)


Lesenswert?

Hallo DB,

> Hast du dir mal das map-File angeschaut? ...
das Map-File schau ich mir mal an, danke.

> Vielleicht wurde SDRAM im Memory Layout nicht korrekt angelegt?
Mmmh, das bezweifle ich, das hab ich eigentlich doppelt und dreifach 
geprüft.
1
MEMORY
2
{
3
  FLASH  (rx)     : ORIGIN = 0x08000000,   LENGTH = 2048K
4
  CCMRAM (xrw)    : ORIGIN = 0x10000000,   LENGTH = 64K
5
  RAM    (xrw)    : ORIGIN = 0x20000000,   LENGTH = 192K
6
  SDRAM  (rw)     : ORIGIN = 0xD0000000,   LENGTH = 8M
7
}
Und selbst wenn es falsch im Layout angelegt wäre, dann erklärt das 
nicht, warum das Binary explodiert. Ich werd es mir aber nochmal 
anschauen, weil ApplicationNotes von ST bzgl. LCD-Controller und dem 
2D-Beschleuniger gefunden, in denen steht, dass man den FrameBuffer auf 
separate Bänke im SDRAM legen soll - also muss ich das LinkerScript eh 
nochmal ändern. Das wird spaßig, denn ich muss rausfinden, ob der Platz 
zwischen den beiden Buffern dann noch genutzt werden kann bzw. wie das 
zu lösen ist.

Grüße

von Johannes S. (Gast)


Lesenswert?

ich hatte mir gestern auch ein issue mit genau diesem Problem angesehen, 
da ging es zwar um einen LPC1549, aber mit dem gleichen Effekt: das .bin 
wurde genau 32 MB groß. Der LPC1549 hat auch mehrere RAM Bereiche, in 
der Linker Description werden nicht-initialisierte angelegt, aber warum 
auch immer wird die Lücke zwischen Flash und RAM komplett gefüllt. LD 
ist hier:
https://github.com/ARMmbed/mbed-os/blob/mbed-os-5.15.7/targets/TARGET_NXP/TARGET_LPC15XX/device/TOOLCHAIN_GCC_ARM/LPC1549.ld

Das .bin ändert sich nicht wenn es mit objcopy aus gcc 6, 9 oder 10 
erzeugt wird. Im Mapfile habe ich auch keine LOAD Bereiche zwischen 
Flash und Ram finden können.
Mit der Deklaration als (NOLOAD) kann man das auch da beheben, die 
Ursache verstehe ich allerdings nicht. Eine kleine Unschärfe in den 
binutils?

Bauform B. schrieb:
> Der data Bereich wird ja ganz
> kurz nach dem Reset kopiert (in der crt0?)

das passiert meist im startup code der ja bei den CM üblicherweise auch 
im Projekt liegt, das kann also auch für Daten im SDRAM angpepasst 
werden.

von D. B. (d_b)


Lesenswert?

Ich kann noch folgendes zum Debugging solcher Probleme sagen: man kann 
sich das bin file mit einem Hexeditor anschauen und prüfen, welche 
unerwünschten Bereiche dort enthalten sind. Ist z.B. etwas an RAM 
Adressen? Dann kann man im map-file vergleichen was der Linker dort hin 
gelinkt hat und dann die Kette zum Code und Linkerskript verfolgen und 
dort Änderungen ausprobieren.

Dann ist mir noch ein Fall eingefallen bei dem der Linker auch für 
nullinitialisierte oder uninitialisierte Daten Initialisierer im ROM 
vorgehalten hat. Auch das sollte man im map-file sehen, da steht dann wo 
die LMA zu einer VMA zu finden ist.
Da gibt es wohl auch einen Bug (?) wo auch (NOLOAD) nicht ausreicht, 
siehe hier: 
https://stackoverflow.com/questions/14453996/gnu-linker-map-file-giving-unexpected-load-addresses
Sollte das zutreffen, kannst du versuchen die LMA auf die VMA zu mappen, 
z.B: mit
1
  ._user_lcd_heap :
2
  {
3
    ...
4
  } >SDRAM AT> SDRAM

Zuguterletzt gibt es noch die unschönste Möglichkeit, wenn man die 
unerwünschten Sektionen ermittelt hat, diese mit der -R Option von 
objcopy zu löschen.

von Ralf (Gast)


Lesenswert?

@Johannes:
Okay, solche Probleme sind dann offensichtlich nicht auf mich beschränkt 
:)
Wie es genau zustande kommt weiß ich aber leider auch (noch) nicht.

@DB:
Ich bin noch nicht zum MapFile gekommen - ich wollte erstmal das Display 
ans Zicken...äh... Zucken bekommen. Ersteres war gestern abend der Fall, 
zweiteres gerade eben freu
Das Display funktioniert nun, soweit ich das feststellen kann. Ich 
wünschte, ST würde nicht viel, sondern gut dokumentieren - aber das ist 
ein anderes Thema.
Die Linkerakrobatik und MapFile muss ich mir mal in aller Ruhe 
vorknöpfen, ich will das sauber verstehen. Die FrameBuffer muss ich nach 
wie vor noch aufteilen, und dann eben rausfinden, ob dazwischen dann 
immer noch was abgelegt werden kann. Und natürlich rausfinden, warum das 
Binary explodiert ist.

Grüße

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.