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