Hi,
ich habe gerade ein Problem zu lösen, das sich reichlich kniffelig gibt:
Gegeben sein ein C-Code, wie er bei kleinen µC recht häufig auftritt:
1
intmain(void){
2
// Init-Anweisungen
3
...
4
5
// Idle-Loop
6
while(1){
7
// eventuell Anweisungen in der Idle-Loop
8
...
9
}
10
// kommt niemals hier her
11
}
Aufgabe ist nun, die Adresse einer bestimmten Codestelle innerhalb der
Idle-Loop zu ermitteln. Das soll aber - weil in einem automatisierten
Test genutzt - nicht von Hand ermittelt werden, sondern im ELF-File als
Label auslesbar sein. Eine erste Idee war, das "Label as value"-Feature
des GCC zu verwenden, einen Pointer damit zu beschreiben und diesen dann
während des Programmablaufs im z.B. Simulator auszulesen:
1
void*ptrLabel;
2
3
intmain(void){
4
// Label-Adresse in Pointer eintragen
5
ptrLabel=&&LABEL;
6
// Init-Anweisungen
7
...
8
9
// Idle-Loop
10
while(1){
11
// gesuchte Zieladresse
12
LABEL:
13
// weitere Anweisungen in der Idle-Loop
14
...
15
}
16
// kommt niemals hier her
17
}
Problem: _das funktioniert nicht_! Es wird dem Pointer eine Adresse
zugewiesen, allerdings die Startadresse von main! Nicht die innerhalb
der Idle-Loop. (nachweisbar im Assemblercode!)
Also ist nun eine Idee gesucht, diese Nuss doch noch zu knacken: wie
kann ich den C-Code (!) gestalten, das ich die Adresse der gewünschten
Code-Stelle auslesen kann? Bedingungen:
- der Programmfluss _innerhalb_ der Idle-Loop soll nicht verändert
werden, also z.B. kein Call an der entsprechenden Stelle auf eine
spezielle Funktion, deren Adresse man aus dem ELF-File ja auslesen kann.
- nach Möglichkeit sollte die Adresse direkt auslesbar sein, alternativ,
wie oben ja schon genutzt, z.B. durch eine (Pointer-)Variable.
- wenn möglich ein Konstrukt, das nicht nur speziell für den GCC
verwendbar ist.
Irgendjemand eine Idee?
Schönen Tag noch,
Thomas
Wenn ich mir so vorstelle, was erzeugte Assemblercode noch mit dem
C-Code zu tun hat, nachdem der Optimierer seinen Arbeit getan hat, bin
ich mir nicht so sicher, ob das, was du vorhast, wirklich das ist, was
du willst.
Was willst du denn mit dem Test nachprüfen?
Oliver
Hugo schrieb:> Immer das gleiche, wenn man PC-Programmierer auf Mikrocontroller los> lässt ... Oh je...
Das soll wohl ein Witz sein? Auf sowas kommen nur
Ex-Assembler-Programmierer.
0x75 ist die Wordadresse = Byteadresse 0xEA von LABEL.
Also von der while-Schleife, da der sinnlose Zähler wegoptimiert wird.
main hat die Adresse 0x6F(0xDE).
Grundsätzlich tut es, was es tun soll.
Was willst du eigentlich damit bezwecken? Die Adresse steht doch ohnehin
nicht unter ptrLabel im *.elf, da sie erst zur Laufzeit in den Pointer
geschrieben wird.
mfg.
Thomas Eckmann schrieb:> Die Adresse steht doch ohnehin> nicht unter ptrLabel im *.elf, da sie erst zur Laufzeit in den Pointer> geschrieben wird.
Eigentlich nicht. Das ist eine Konstante, die wird schon vom Compiler
(bzw. Linker) eingetragen.
Oliver
Nein, ptrLabel ist global und wird mit 0 initialisiert.
Das Setzen von ptrLabel geschieht erst zur Laufzeit, und dazu muß die
Funktion, die das Label enthält, aufgerufen werden.
Oliver schrieb:> Eigentlich nicht. Das ist eine Konstante, die wird schon vom Compiler> (bzw. Linker) eingetragen.
Der Inhalt von ptrLabel ist erst zur Laufzeit bekannt. Das ist zwar im
Code eine Konstante, aber davon kann sich Thomas nichts kaufen.
A. K. schrieb:> Der Inhalt von ptrLabel ist erst zur Laufzeit bekannt.
der Compieler kennt doch wohl die Adresse, damit damit kann er es doch
wegoptimieren. Man müsste schon ptrLabel volatile machen.
Peter II schrieb:> der Compieler kennt doch wohl die Adresse, damit damit kann er es doch> wegoptimieren.
Dazu müsste er wissen, dass vor main() niemand auf ptrLabel zugreift.
Denn vor der Zuweisung muss der Inhalt von ptrLabel 0 sein.
A. K. schrieb:> Denn vor der Zuweisung muss der Inhalt von ptrLabel 0 sein.
globale variabeln die nicht genutzt werden, werden auch wegoptimiert und
sind nicht 0.
Stephan B. schrieb:> __asm__("mein_einzigartiger_Labelname_als_Symbol");
Die böse Überraschung wartet bereits:
- falls das Label in einer Scheife ist, die aufgerollt wird
- falls das Label in einer Funktion ist, die (mehrfach)
evtl. nur partiell geinlinet wird
- falls das Label in einem Block steht, der durch Optimizer
kopiert wird, um teure Sprünge zu sparen
- etc.
Johann L. schrieb:> Die böse Überraschung wartet bereits
Deine genannten Moeglichkeiten sollten in einem Compilerfehler
resultieren?
(Da fixes Label) Eine Ueberraschung im Sinne von inkonsistenten Code
duerfte es nicht geben?
Oder?
MfG
Stephan B. schrieb:> Johann L. schrieb:>> Die böse Überraschung wartet bereits>> Deine genannten Moeglichkeiten sollten in einem Compilerfehler> resultieren?
Nein, beschweren wird sich erst der Assembler, weil das Label dann
mehrfach definiert wird. Für den Compiler ist Inline-Asm ne Blackbox.
Zudem definiert der Ansazu lediglich ein Label, aber den Wert (die
Adresse) hat man dadurch noch nicht.
Peter II schrieb:> globale variabeln die nicht genutzt werden, werden auch wegoptimiert und> sind nicht 0.
Die wird hier aber benutzt. Nämlich um in main() die Adresse vom Label
rein zu schreiben.
Der Compiler weiss nicht, dass die Variable nirgends sonst genutzt wird.
Der Linker wiederum sieht nur, dass die Variable verwendet wird.
A. K. schrieb:> Die wird hier aber benutzt. Nämlich um in main() die Adresse vom Label> rein zu schreiben.
da sie aber nie wieder ausgelesen wird, muss der compiler den wert dort
nicht reinschreiben. (sonst wird es das volatile problem mit einer ISR
gar nicht geben).
Da es die funktion vom Programm nicht ändert, darf er sie wegoptimieren.
Ob er es macht ist eine andere Frage.
Peter II schrieb:> da sie aber nie wieder ausgelesen wird, muss der compiler den wert dort> nicht reinschreiben.
Dazu müsste er das aber wissen. Da es sich um eine globale Variable
handelt weiss er es jedoch nicht, solange klassisch File für File
compiliert wird. Er kann es nur wissen, wenn das gesamte Programm
geschlossen übersetzt wird (z.B. -flto oder -fwhole-program).
A. K. schrieb:> Peter II schrieb:>> der Compieler kennt doch wohl die Adresse, damit damit kann er es doch>> wegoptimieren.>> Dazu müsste er wissen, dass vor main() niemand auf ptrLabel zugreift.> Denn vor der Zuweisung muss der Inhalt von ptrLabel 0 sein.
Das wäre in ISO C ja kein Problem, weil man da gar keinen Code schreiben
kann, der vor main() ausgeführt wird.
Rolf Magnus schrieb:> Das wäre in ISO C ja kein Problem, weil man da gar keinen Code schreiben> kann, der vor main() ausgeführt wird.
In C nicht, in C++ schon. Und auf dieser Ebene wird der Compiler
möglicherweise keinen Unterschied mehr dazwischen machen.
Wobei mindestens manche Startup-Szenarien von C Compilern für
Microcontroller sehr wohl C Code vor main() ausführen. Da müsste man in
dem Fall eine Extrawurst braten, um das dem Compiler beizubringen.
A. K. schrieb:> Er kann es nur wissen, wenn das gesamte Programm> geschlossen übersetzt wird (z.B. -flto oder -fwhole-program).
und? Wir wissen nicht wie es übersetzt wird.
Peter II schrieb:> Wir wissen nicht wie es übersetzt wird.
Hier im Forum, bei Testbeispielen? ;-)
Aber unsere Diskussion ist ohnehin Haarspalterei, denn wenn der Compiler
die Variable wegoptimiert, dann erfährt Thomas weder vom Linker noch zur
Laufzeit, wo sein wegoptimiertes Label liegt.
Wobei mir die Frage weiterhin sehr schräg erscheint.
Matthias Lipinsky schrieb:> Das alles erklärt aber noch nicht die Frage, was damit bezweckt werden> soll...
Man kann soetwas gebrauchen, wenn man z.B. innerhalb von einem
aligned-Codeblock virtuell verschiedene Funktionen haben moechte (ohne
dabei jede Funktion aus Speicherplatzgruenden alignen zu muessen).
Z.B. um ein Muster mit harting Timings zu interpretieren, wie es im
Beitrag "Re: AVR ASM ws2811 / ws2812 Ansteuerung mit FastPWM"
(burstsequence_8mhz.h und ws2812.*) getan wurde.
MfG
Hi @all,
wow, da geht ja echt die Post ab. :-) Ok, hier die Auflösung des Rätsels
... wobei, das ist gar nicht so rätselhaft. Und mit etwas Phantasie
sollte man sogar selbst drauf kommen können. Also ...
Ad 1) Die Lösung von Stephan B. funktioniert! Damit wird ja nichts
anderes getan, als im vom Compiler erzeugten Assembler-Code ein
bestimmtes Label einzuführen. Und das taucht auch, wie per readelf ganz
einfach nachweisbar, eben auch im ELF-File auf, von wo ich es für den
Test auslesen kann. Die Lösung hat 3 Vorteile: das sollte analog auch
mit anderen Compilern als dem GCC funktionieren, es wird kein
zusätzlicher Code eingeführt, vor allem, wie schon geschrieben, eben
nicht in der Idle-Loop, was essentiell ist. Und drittens, die gesuchte
Adresse an der bestimmten Code-Stelle steht sofort zur Verfügung und
nicht, wie die Label-as-value-Variante, erst wenn der definierte Pointer
geschrieben ist.
Ad 2) nachdem das Rätseln gross ist, wozu das Ganze: Es geht um
automatisierte Tests, speziell um Performance-Tests. In "grossen"
Controllern, wie z.B. bei ARM-Cortex-Prozessoren, wenn ich per PLL
Taktraten von 100MHz und mehr erreiche, kann man den Code für einen
Performance-Test entsprechend ein-codieren, der Performance-Einbruch ist
vernachlässigbar. Aber was ist mit Controllern, wie z.B. einem ATTiny?
Wenig RAM und Flash, keine freien Com-Schnittstellen. Code zur
Performance-Messung einbauen führt das Ganze ad absurdum.
Die Lösung ist eben, in der üblichen Idle-Loop einen Trigger-Punkt zu
haben, der immer in der Loop ausgeführt wird, unabhängig, was noch an
Funktionalität in der Idle-Loop ausgeführt wird. Den man per Debugger
oder Simulator automatisiert (!) einstellen und messen kann, um so Zahl
der Loops/Zeiteinheit und die jeweilige Zeit einer Loop messen zu
können.
Ziel: Nachweis, das ich meine Funktionen rechtzeitig abarbeiten kann,
das die entsprechenden Zeit-Forderungen eingehalten werden können, die
Möglichkeit, Code-Optimierungen bewerten zu können. Auch nachweisen, das
der eingesetzte Controller richtig skaliert und nicht zu klein
(Möglichkeit zur Fehlfunktion) oder zu gross (Kostenaspekt) ist. Oder
eben auch nachweisen, das unter worst-case-Bedingungen der Code noch
funktioniert und nicht zu Fehlfunktionen führt.
Vielen Dank an Stephan für die richtige Idee!
Schönen Tag noch, Thomas
Thomas K. schrieb:> Ziel: Nachweis, das ich meine Funktionen rechtzeitig abarbeiten kann,> das die entsprechenden Zeit-Forderungen eingehalten werden können,
Autsch.
Mit Laufzeittests bekommst du lediglich Abschätzungen der Laufzeit nach
unten, was aber benötigt wird sind Abschätzungen nach oben. Da es
i.d.R. nicht machbar ist, Zeitmessungen für jede mögliche Eingabe
durchzuführen, führt an einer statischen Analyse nichts vorbei.
Hi Johann,
Johann L. schrieb:> Mit Laufzeittests bekommst du lediglich Abschätzungen der Laufzeit nach> unten, was aber benötigt wird sind Abschätzungen nach oben. Da es> i.d.R. nicht machbar ist, Zeitmessungen für jede mögliche Eingabe> durchzuführen, führt an einer statischen Analyse nichts vorbei.
Schon richtig. Eine Messung der Laufzeit im Komplett-Image kann
definitiv nicht alle worst-case-Szenarien der Einzelfunktionen abdecken.
Aber es ist eben ein Baustein von mehreren.
Wenn Du z.B. weist, resp. herausbekommst, das Du jenseits der 90%
Prozessorauslastung bist, solltest Du zumindest eine Ahnung haben, was
passieren könnte, wenn der Kunde mit merkwürdigen Fehlerbeschreibungen
kommt, die keiner nachvollziehen kann. (Ok ich weiss, der Kunde kommt
IMMER mit merkwürdigen Beschreibungen ... :-) )
Und wenn Du z.B. bei der Ausmessung der Loop-Zeit einer Idle-Loop
entsprechend hohe Ausreisser nach oben bekommst, die jenseits dessen
liegen, was als maximale Loop-Zeit definiert oder berechnet wurde, dann
ist das auch schon ein Indiz dafür, das da noch Arbeit lauert.
Richtig testen ist eben auch eine Kunst für sich. :-) Schönen Tag noch,
Thomas
Johann L. schrieb:> Nein, ptrLabel ist global und wird mit 0 initialisiert.>> Das Setzen von ptrLabel geschieht erst zur Laufzeit, und dazu muß die> Funktion, die das Label enthält, aufgerufen werden.
Da hast du natürlich Recht, für einen Zuweisung schon zur zur
Compilezeit müsste die Initialisierung bei der Definition gemacht
werden.
Oliver