Forum: Compiler & IDEs GCC: Adresse einer bestimmten Codestelle


von Thomas K. (tomk)


Lesenswert?

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
int main(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
int main(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

von Stephan B. (matrixstorm)


Angehängte Dateien:

Lesenswert?

Probier mal an die entsprechende Stelle ein
1
  asm volatile ("  mein_einzigartiger_Labelname_als_Symbol:        \n\t");

MfG

: Bearbeitet durch User
von Oliver (Gast)


Lesenswert?

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

von Hugo (Gast)


Lesenswert?

Immer das gleiche, wenn man PC-Programmierer auf Mikrocontroller los 
lässt ... Oh je...

von Fux (Gast)


Lesenswert?

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.

von Thomas E. (thomase)


Lesenswert?

1
/*volatile*/ unsigned char nTime = 0;
2
3
void *ptrLabel;
4
5
int main(void)
6
{
7
   ptrLabel = &&LABEL;
8
9
  while(1)
10
  {
11
    LABEL: nTime++;
12
  }
13
}

Ergibt:
1
int main(void)
2
{
3
   ptrLabel = &&LABEL;
4
  de:  85 e7         ldi  r24, 0x75  ; 117
5
  e0:  90 e0         ldi  r25, 0x00  ; 0
6
  e2:  90 93 66 00   sts  0x0066, r25
7
  e6:  80 93 65 00   sts  0x0065, r24
8
  ea:  ff cf         rjmp  .-2        ; 0xea <__stack+0xb>

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.

: Bearbeitet durch User
von Oliver (Gast)


Lesenswert?

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

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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.

von (prx) A. K. (prx)


Lesenswert?

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.

von Peter II (Gast)


Lesenswert?

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.

von (prx) A. K. (prx)


Lesenswert?

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.

von Peter II (Gast)


Lesenswert?

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.

von Stephan B. (matrixstorm)


Angehängte Dateien:

Lesenswert?

Hallo, ich nocheinmal:

Definiert in "assembler volatile" die Marke.
Im Code selbst implementierst du eine externe Variable:
1
extern void* Codeadresse __asm__("mein_einzigartiger_Labelname_als_Symbol");

Nach dem Linken des Codes enthaelt
1
&Codeaddresse
 die Adresse.

MfG


Nachtrag:
Ausgabe:
1
Hallo Welt
2
Sprungmarke bei 0x000000000040059d
Dump(ausschnitt):
1
0000000000400584 <main>:
2
  400584:       55                      push   %rbp
3
  400585:       48 89 e5                mov    %rsp,%rbp
4
  400588:       48 83 ec 10             sub    $0x10,%rsp
5
  40058c:       89 7d fc                mov    %edi,-0x4(%rbp)
6
  40058f:       48 89 75 f0             mov    %rsi,-0x10(%rbp)
7
  400593:       bf ac 06 40 00          mov    $0x4006ac,%edi
8
  400598:       e8 d3 fe ff ff          callq  400470 <puts@plt>
9
10
000000000040059d <mein_einzigartiger_Labelname_als_Symbol>:
11
  40059d:       ba 9d 05 40 00          mov    $0x40059d,%edx
12
  4005a2:       b8 b7 06 40 00          mov    $0x4006b7,%eax
13
  4005a7:       48 89 d6                mov    %rdx,%rsi
14
  4005aa:       48 89 c7                mov    %rax,%rdi
15
  4005ad:       b8 00 00 00 00          mov    $0x0,%eax
16
  4005b2:       e8 c9 fe ff ff          callq  400480 <printf@plt>
17
  4005b7:       b8 00 00 00 00          mov    $0x0,%eax
18
  4005bc:       c9                      leaveq 
19
  4005bd:       c3                      retq   
20
  4005be:       66 90                   xchg   %ax,%ax

: Bearbeitet durch User
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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.

von Stephan B. (matrixstorm)


Lesenswert?

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

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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.

: Bearbeitet durch User
von (prx) A. K. (prx)


Lesenswert?

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.

: Bearbeitet durch User
von Matthias L. (Gast)


Lesenswert?

Das alles erklärt aber noch nicht die Frage, was damit bezweckt werden 
soll...

von Peter II (Gast)


Lesenswert?

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.

von (prx) A. K. (prx)


Lesenswert?

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

: Bearbeitet durch User
von Rolf Magnus (Gast)


Lesenswert?

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.

von (prx) A. K. (prx)


Lesenswert?

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.

: Bearbeitet durch User
von Peter II (Gast)


Lesenswert?

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.

von (prx) A. K. (prx)


Lesenswert?

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.

: Bearbeitet durch User
von Stephan B. (matrixstorm)


Lesenswert?

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

von Thomas K. (tomk)


Lesenswert?

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

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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.

von Thomas K. (tomk)


Lesenswert?

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

von Oliver (Gast)


Lesenswert?

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

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.