Hallo Kollegen
ich programmiere etwas für einen STM32F407.
Ich habe mir dazu ein Makefile und Linkerscript zusammengeschustert. Des
Weiteren verwende ich FreeRTOS.
Compilieren usw. geht alles, aber ich habe festgestellt, dass der Linker
ein paar Symbole aus FreeRTOS weg"optimiert" hat, und ich möchte gern
verstehen, warum.
Ich habe in der Beilage mein Linkerscript und die crt0.s hinzugefügt.
Wie iht seht, beinhaltet die Vektortabelle den Programmstart sowie den
Stackpointer, und die Vektortabelle liegt in der Section ".vectors". Mit
"KEEP" wird dafür gesorgt, dass der Linker die Vektortabelle auf keinen
Fall wegoptimieren kann. Da die Vektortabelle damm im Endeffekt auf den
Programmstart, und dieser auf "main" verweist, müsste das meiner Meinung
nach dafür sorgen, dass absolut alle verwendeten Symbole vom Linker auch
übernommen werden.
Nun habe ich aber mit einer FreeRTOS Datei Probleme. In der Beilage ist
die "port.c" beigefügt, welche ich direkt so aus FreeRTOS übernommen
habe. Die Variable "pcInterruptPriorityRegisters" ist als "static const
volatile uint8_t * const" und liegt damit im rodata Bereich, und es wird
auch aus dem Programmcode auf die Variable zugegriffen
(configASSERT_DEFINED ist definiert). Damit müsste der Linker meiner
Meinung nach dazu verpflichtet sein, diese Variable auch im
entsprechenden rodata Bereich zu inkludieren. Macht er aber nicht: das
Mapfile gibt die folgenden Auskünfte
und in der Tat, wenn ich den Code debugge und an diese Stelle gerate,
sieht man, dass diese Variable mit 0 initialisiert ist und damit an eine
ungültige Position verweist!
Jetzt verstehe ich nicht, warum der Linker die weg optimiert. Und wenn
das bei dieser Variable passiert, was denn sonst ist auch noch weg
optimiert worden, von dem ich nichts weiss?
Ich habe mein Linkerscript darauf hin derart angepasst, dass ich alle
Sections (rodata, text, rodata*) mit dem KEEP versehen habe. Und siehe
da, mein Binary wächst von ursprünglich 63kB auf jetzt 77kB. Warum wird
da ohne das KEEP so viel Zeug weg gelassen?
einfach KEEP hinschreiben ist keine richtige Lösung des Problems, weil
ich damit ja auch möglicherweise Zeug in mein Binary hinein nehme, das
ich in der Tat nicht benötige, aber andererseits scheint das Weglassen
des KEEP dem Linker Tür und Tor zu öffnen, Zeug zu entfernen, und ich
wüsste gern, weshalb.
Das Mapfile besagt ja; der Linker ist sich über die Existenz dieser
"pcInterruptPriorityRegisters" Variable im Klaren, und auf diese wird ja
in der port.c auch zugegriffen, trotzdem wird sie weg optimiert.
Kann das jemand erklären?
(Edit: [pre]-Tags eingefügt. Mod.)
Die Variable ist static und wird nie beschrieben.
Deshalb kann der Optimizer bei der Zuweisung ucCurrentPriority =
pcInterruptPriorityRegisters ... direkt den Wert nehmen, mit dem
pcInterruptPriorityRegisters statisch initialisiert wird.
Und damit entfällt die Notwendigkeit für die globale Variable komplett.
Der Linker entfernt sie.
ah ja, sie wird nicht beschrieben und damit hat sie quasi den Wert 0.
Leuchtet ein! kann ich auf irgend eine Weise prüfen, ob es noch andere
solche Fälle im Code von FreeRTOS hat, oder muss ich, um sicher zu sein,
im Linkerscript wirklich KEEP() einfügen?
Tobias P. schrieb:> ah ja, sie wird nicht beschrieben und damit hat sie quasi den Wert 0.
Nein, sie wird initialisiert, damit hat sie einen Wert.
Das ist allerdings alles ziemlich wortreich, und den Beweis, dass da
wirklich der Linker was wegnimmt, bist du irgendwie schuldig geblieben,
also bspw. das Stück disassemblierte Funktion.
Wenn du das auf ein minimales compilierbares Beispiel runterbrechen
kannst, kann man darüber auch diskutieren, aber durch diesen Klumpen
blickt man nicht einfach so vom Draufschauen durch – und die mehr oder
weniger wild *) benutzte "ungarische Notation" macht das Lesen auch
nicht einfacher.
*) Eine uint32_t Variable wird mit "ul" gepräfixt, eine andere mit "x".
Achso, die Variable pcInterruptPriorityRegisters kann der Compiler
natürlich sehr wohl weg optimieren (aber nicht der Linker!), denn sie
ist nur ein Typ-Alias für portNVIC_IP_REGISTERS_OFFSET_16 welches den
konstanten Wert 0xE000E3F0 hat. Damit kann er den kompletten Zugriff
gleich von vornherein auf diese Adresse beziehen.
Tobias P. schrieb:> ah ja, sie wird nicht beschrieben und damit hat sie quasi den Wert 0.> Leuchtet ein!
Sie wird gar nicht erst benötigt. Die einzige Verwendung ist:
Und wie MaWin schon sagt, kann dort einfach direkt der Wert eingesetzt
werden. Aber da sie static ist, soll sie sowieso gar nicht erst ein
Linkersymbol bekommen. Damit sagt man ja schließlich ausdrücklich, dass
nur in dieser einen Übersetzungseinheit der Name überhaupt sichtbar sein
soll.
> kann ich auf irgend eine Weise prüfen, ob es noch andere solche Fälle im> Code von FreeRTOS hat, oder muss ich, um sicher zu sein, im Linkerscript> wirklich KEEP() einfügen?
Welches Problem versuchst du denn eigentlich zu lösen? Wenn der Compiler
die Variable hier wegoptimiert, tut er das, weil er sie nicht braucht.
Rolf M. schrieb:> Welches Problem versuchst du denn eigentlich zu lösen? Wenn der Compiler> die Variable hier wegoptimiert, tut er das, weil er sie nicht braucht.
offenbar braucht er sie. Ich hatte das Problem, dass, sobald die
Funktion vPortValidateInterruptPriority aufgerufen wurde, der Code
versucht, auf pcInterruptPriorityRegisters[ulCurrentInterrupt]
zuzugreifen - und da diese pcInterruptPriorityRegisters Variable mit 0
initialisiert ist, kracht es da (Pointer mit Wert 0 - Zugriff auf
falsche Adresse).
Aber ich sehe ein, dass die Frage so nicht gut gestellt ist. Ich werde
morgen ein Minimalbeispiel erstellen, wo nur FreeRTOS drin ist und sonst
nichts. Dann sieht man im Mapfile, dass die Variable wegoptimiert wurde.
Jörg W. schrieb:> und die mehr oder weniger wild *) benutzte "ungarische Notation" macht> das Lesen auch nicht einfacher.> *) Eine uint32_t Variable wird mit "ul" gepräfixt, eine andere mit "x".
ja ich finde die ungarische Notation auch sehr wüst. Aber FreeRTOS nutzt
die halt 😞
Tobias P. schrieb:> Dann sieht man im Mapfile, dass die Variable wegoptimiert wurde.
Im Mapfile siehst du davon gar nichts, weil sie statisch ist. Die taucht
nie als globales Symbol auf.
Jörg W. schrieb:> Im Mapfile siehst du davon gar nichts, weil sie statisch ist. Die taucht> nie als globales Symbol auf.
doch, klar findet man diese Variable im Mapfile. Ich habe sie ja schon
mal gefunden.
Ich bin grad am falschen PC und habe die Projektfiles grade nicht zur
Hand, aber wollen wir eine Wette abschliessen? ;-)
Tobias P. schrieb:> kracht es da (Pointer mit Wert 0 - Zugriff auf> falsche Adresse).
Das fände ich allerdings komisch.
Das wäre ein separater Fehler im Optimizer. Mit dem Verschwinden des
Symbols hat das nichts zu tun.
Der Compiler darf, wie gesagt, den Zugriff auf die Variable
wegoptimieren und der Linker darf dann in Folge die ganze Variable
wegoptimieren.
Aber Die Zuweisung im Code muss dann immer noch den Konstantwert von
pcInterruptPriorityRegisters bekommen und der Code muss korrekt
funktionieren.
MaWin schrieb:> Aber Die Zuweisung im Code muss dann immer noch den Konstantwert von> pcInterruptPriorityRegisters bekommen und der Code muss korrekt> funktionieren.
Jein - das gilt natürlich nur unter der Voraussetzung, dass mein selber
gebasteltes Linkerscript auch korrekt ist. Was ich nicht weiss.
Tobias P. schrieb:> Jein - das gilt natürlich nur unter der Voraussetzung, dass mein selber> gebasteltes Linkerscript auch korrekt ist. Was ich nicht weiss.
Ich glaube nicht, dass das vom Linkerscript abhängt, da die Variable
static ist.
Der Zugriff wird bereits vor dem Linken optimiert.
Oder verwendest du LTO? Da weiß ich es jetzt nicht.
So, ich habe jetzt ein Testprojekt zusammengenagelt. Damit kann man das
Problem nachvollziehen.
Wenn man das Projekt mit "make" so erstellt, wie es gegenwärtig ist,
dann wird unter "lst" die "test.map" erstellt. Dort findet man die Info
die Variable liegt also, abhängig davon ob eine Sektion im Linerscript
als KEEP deklariert wird oder nicht, am unterschiedlichen Orten und im
einen Fall ist sie sogar komplett falsch.
LTO wird nicht verwendet. (und die Wette mit Jörg habe ich gewonnen
;-)).
Ich würde gerne verstehen, warum es das KEEP im Linkerscript braucht,
damit die Variable am rechten Ort liegt. Ich möchte das KEEP lieber
nicht verwenden, da so möglicherweise massenhaft Zeug in meinem Binary
landet, das wirklich nicht gebraucht wird.
Also, jetzt mal rein statistisch. Der Compiler, der Linker und FreeRTOS
sind millionenfach getestet und wer hat da jemals ein KEEP gebraucht?
Wenn ich wetten würde, dann eher auf eine falsche config in der Nähe von
MAX_SYSCALL_INTERRUPT_PRIORITY oder verschobene priority-Bits oder eine
falsch interpretierte Debugger-Ausgabe oder...
Tobias P. schrieb:> Jörg W. schrieb:>> Im Mapfile siehst du davon gar nichts, weil sie statisch ist. Die taucht>> nie als globales Symbol auf.>> doch, klar findet man diese Variable im Mapfile. Ich habe sie ja schon> mal gefunden.
Ok, bei dir sind soweit ich sehen kann alle Optimierungen abgeschaltet.
Es scheint, dass der Compiler dann die Variable nicht rauswirft, aber
der Linker sie dann ignoriert. Mit Optimierungen verschwindet die
Variable bei mir ganz aus dem Mapfile.
Rolf M. schrieb:> Mit Optimierungen verschwindet die Variable bei mir ganz aus dem> Mapfile.
Was ja auch Sinn hat, denn keiner braucht sie.
Sorry, auf die Idee, dass jemand ohne Optimierungen compiliert, war ich
gar nicht erst gekommen. ;-)
Da siehst du, dass er selbst bei -O0 den konstanten Wert 0xe000e3f0
benutzt. Deine ganze schöne "const volatile static"-Variable
pcInterruptPriorityRegisters interessiert den Compiler nicht die Bohne.
Jörg W. schrieb:> Was ja auch Sinn hat, denn keiner braucht sie.
doch, wenn aus einer ISR heraus auf einen Semaphor oder Mutex
zugegriffen wird, dann wird diese vPortValidateInterruptPriority
aufgerufen, und dann findet auch ein Zugriff auf diese Variable statt.
Diese Variable sollte eigentlich ein Pointer auf die Tabelle des NVIC
sein, wo die Interruptprioritäten eingetragen werden.
Der Code ist allerdings vom FreeRTOS Projekt und nicht von mir, ich find
den auch nicht so schön, aber umschreiben möchte ich ihn auch nur
ungern, weil man dann nicht mehr so einfach eine neue Version von
FreeRTOS benutzen kann.
> Sorry, auf die Idee, dass jemand ohne Optimierungen compiliert, war ich> gar nicht erst gekommen. ;-)
ohne Optimierungen compilieren will man doch, wenn man noch debuggen
will?!
Johannes S. schrieb:> mit .text :> {> . = ALIGN(4);> KEEP(*(.vectors*))> *(.text*)> KEEP(*(.rodata*))> . = ALIGN(4);> } > ram>> bleibt pcInterruptPriorityRegisters auch im mapfile an addr> 0x0000000020002b74 und .text wird nur 8 Byte größer (11128).
das ist durchaus spannend. Ist das eine saubere Lösung?
Tobias P. schrieb:> Jörg W. schrieb:>> Was ja auch Sinn hat, denn keiner braucht sie.>> doch, wenn aus einer ISR heraus auf einen Semaphor oder Mutex> zugegriffen wird, dann wird diese vPortValidateInterruptPriority> aufgerufen, und dann findet auch ein Zugriff auf diese Variable statt.
Nein, findet nicht, siehe obige Erklärung. Das ist keine "Variable",
alles da drin ist konstant und die entsprechende Adresse kann zur
Compilezeit ermittelt werden (und wird auch).
>> Sorry, auf die Idee, dass jemand ohne Optimierungen compiliert, war ich>> gar nicht erst gekommen. ;-)>> ohne Optimierungen compilieren will man doch, wenn man noch debuggen> will?!
Also ob man das will, weiß ich nicht :), ich will das nie.
Man debuggt ja dann etwas komplett anderes als das, was man später
laufen lassen möchte. Im Endeffekt debuggt man daher zweimal. Da kann
ich auch auf das erste Mal gleich verzichten.
Ja, optimierter Code lässt sich natürlich schwieriger debuggen, gar
keine Frage, und man muss hie und da auch mal in die Trickkiste greifen,
um das eine oder andere weg optimierte Detail dann doch noch im Debugger
sehen zu können. Mein Standardspruch dafür: Wenn es so einfach wäre,
dass es jeder (aus dem Stegreif) könnte, würde mein Chef mich nicht
dafür bezahlen. ;-) (Das heißt aber natürlich nicht, dass man das nicht
lernen kann oder sollte.)
> das ist durchaus spannend. Ist das eine saubere Lösung?
Nein.
Du hast bislang noch gar kein Problem zeigen können, außer dass es dich
stört, dass eine absolut nichtsnutzige Variable nicht mehr in der
Symboltabelle auftaucht.
Die Stelle, an der die entsprechende Berechnung völlig korrekt jedoch
ohne diese Variable ausgeführt wird, habe ich dir ein Posting über
deinem gezeigt.
Tobias P. schrieb:> und dann findet auch ein Zugriff auf diese Variable statt.
Nein.
Es wurde ja schon mehrfach erklärt, dass der Zugriff wegoptimiert wird
und durch einen äquivalenten Direktzugriff auf den Speicherbereich
ersetzt wird.
Die Variable wird also nicht mehr gebraucht und damit fliegt sie raus.
Der Code funktioniert trotzdem. Die Variable wird durch eine Konstante
in .text ersetzt.
Sieh es vielleicht mal so: Der ganze "Fehler" ist, dass diese Variable
im Flash statt im RAM angelegt wird. Dadurch sind die Bytes in rodata
wirklich überflüssig.
Tobias P. schrieb:> Jörg W. schrieb:>> Was ja auch Sinn hat, denn keiner braucht sie.>> doch, wenn aus einer ISR heraus auf einen Semaphor oder Mutex> zugegriffen wird, dann wird diese vPortValidateInterruptPriority> aufgerufen, und dann findet auch ein Zugriff auf diese Variable statt.\
der Zugriff steht auf 2000290e:
> Diese Variable sollte eigentlich ein Pointer auf die Tabelle des NVIC> sein, wo die Interruptprioritäten eingetragen werden.
Ist es ja auch, auf 2000296c steht e000e3f0. Ja, die Tabelle fängt
genaugenommen erst auf e000e3400 an, aber in der stehen nur die normalen
Interrupts und "mrs r3, IPSR" liefert die um 16 größere Vektornummer.
Tobias P. schrieb:> Jörg W. schrieb:>> Im Mapfile siehst du davon gar nichts, weil sie statisch ist. Die taucht>> nie als globales Symbol auf.>> doch, klar findet man diese Variable im Mapfile. Ich habe sie ja schon> mal gefunden.> Ich bin grad am falschen PC und habe die Projektfiles grade nicht zur> Hand, aber wollen wir eine Wette abschliessen? ;-)
Ihr habt beide Recht, die taucht zwar auf, aber im Abschnitt "Discarded
input sections", Nomen est Omen oder so.
Tobias P. schrieb:> Und wenn das bei dieser Variable passiert, was denn sonst ist> auch noch weg optimiert worden, von dem ich nichts weiss?
Wenn du es wirklich wissen willst: alles, was unter "Discarded input
sections" auftaucht. Aber das sollte dich nicht beunruhigen.
Bauform B. schrieb:> Der ganze "Fehler" ist, dass diese Variable im Flash statt im RAM> angelegt wird.
Hmm, naja. Das Ablegen größerer Werte, die nicht als Direktoperanden
einbettbar sind, hinter den jeweiligen Codeblöcken ist bei ARM gängige
Praxis. Das würde ich jetzt noch nicht als "Anlegen einer Variablen"
bezeichnen. ;-)
Hintergrund: bei RISC hat man nur begrenzt Platz im Befehl selbst zu
Ablegen von Direktoperanden. Erste RISC-Architekturen haben dann oft
low- und high-Teil eines Operanden in zwei Schritten geladen (2
32-Bit-Befehle mit je 16-Bit Operanden), bei ARM dagegen werden die
Operanden in den Code eingebettet und dann PC-relativ adressiert, weil
man dafür nur relativ wenige Bits im Opcode für den PC-Offset braucht.
Jörg W. schrieb:> Bauform B. schrieb:>> Der ganze "Fehler" ist, dass diese Variable im Flash statt im RAM>> angelegt wird.>> Hmm, naja. Das Ablegen größerer Werte, die nicht als Direktoperanden> einbettbar sind, hinter den jeweiligen Codeblöcken ist bei ARM gängige> Praxis. Das würde ich jetzt noch nicht als "Anlegen einer Variablen"> bezeichnen. ;-)
Irgendwie muss man doch die Realität mit Tobias' Wünschen(?) in Einklang
bringen können...
Der Begriff "Variable" ist in diesem Faden nicht so eng zu sehen.
Hallo zusammen
OK ich habe mich da vom Debugger täuschen lassen!
Ich verwende niemals Eclipse, sondern OZONE von Segger. Das funktioniert
eigentlich sehr sehr gut.
Wenn man sich die Disassembly (oben rechts) genau anschaut, sieht man,
dass in der Tat der Zugriff auf die korrekte Adresse erfolgt -
lustigerweise ist aber im "Watch Window" unten rechts der Wert der
pcInterruptPriorityRegisters offenbar 0. Das ist nicht sehr logisch.
Ich dachte dann erst, dass mein Code nicht lief, weil diese Variable
(oder meinetwegen Konstante...) den falschen Wert hat, der wahre Grund
war aber, dass die Interruptprioritäten tatsächlich falsch gesetzt waren
- die Funktion vPortValidateInterruptPriority ist im assert (Zeile 764)
stecken geblieben.
Ich bin noch nicht 100% sicher, ob der Code jetzt richtig funktioniert,
aber zumindest hat diese Variable in der Tat den falschen Wert, der
Zugriff funktioniert aber trotzdem. Sehr mysteriös.
Tobias P. schrieb:> aber zumindest hat diese Variable in der Tat den falschen Wert
Naja, die Variable gibt es halt nicht, da der Compiler sie ja nie
benutzt hat. Dass er dir mit -O0 dennoch ein Symbol hinterlässt, nun ja,
Segger schnallt dann wohl nicht, dass das Symbol gar keinen Wert hat und
gibt stattdessen 0 aus.
Eigentlich noch ein Punkt mehr, nicht ohne eingeschaltete Optimierung zu
debuggen. ;-)
Jörg W. schrieb:> Naja, die Variable gibt es halt nicht, da der Compiler sie ja nie> benutzt hat. Dass er dir mit -O0 dennoch ein Symbol hinterlässt, nun ja,> Segger schnallt dann wohl nicht, dass das Symbol gar keinen Wert hat und> gibt stattdessen 0 aus.
naja ich verstehe noch nicht ganz, wie dann der Zugriff auf dieses
Interruptregister funktioniert.
Spasseshalber werde ich jetzt mal mit -O3 debuggen. Ich mach das aber
immer nicht so gern, weil da der Debugger zum Teil wild zwischen den
Zeilen herumspringt, weil eben gewisse Optimierungen vorgenommen werden.
Tobias P. schrieb:> naja ich verstehe noch nicht ganz, wie dann der Zugriff auf dieses> Interruptregister funktioniert
Das Disassembler-Listing habe ich dir doch oben gezeigt.
Deine "Variable" ist letztlich weiter nichts als ein Hilfskonstrukt, um
aus der puren Adresse (portNVIC_IP_REGISTERS_OFFSET_16) via Typecast
einen für dich sinnvoll benutzbaren C-Konstrukt zu machen. Aber den
brauchst du nur, damit du mit den Mitteln der Hochsprache angenehm drauf
zugreifen kannst. In der Realität ist da drunter ja trotzdem noch
einfach nur ein bisschen Adressrechnung und ein Buszugriff (auf ein
MMIO-Register).
Edit: von -O3 würde ich dir stark abraten. Das ist eine so aggressive
Laufzeitoptimierung, dass dabei u.a. Schleifen entrollt werden, was das
Zeug hält. Der Code wird halt größer aber schneller. Such dir irgendwas
zwischen -O1, -Os oder -Og aus.
Der Compiler wird hier für die 5. Zeile nicht Code generieren, der x
einliest, 3 dazu adiiert und das Ergebnis nach a schreibt, sondern Code,
der einfach direkt 8 nach a schreibt. Er weiß ja, dass das das Ergebnis
der Berechnung sein wird, also kann er sich diese Addition zur Laufzeit
sparen. Dann wird aber x gar nicht mehr benutzt, denn es war ja nur der
Input für die nicht mehr statt findende Addition. Damit kann es also
auch weg.
Genau das gleiche passiert bei dir auch, nur mit einen Pointer statt
einem int.