Forum: Compiler & IDEs GCC static lib; Konstruktoren gloabler Objekte werden nicht aufgerufen


von DerAlbi (Gast)


Lesenswert?

Hallo,
ich schreibe ein etwas großeres Programm für den AVR32. Der compiliert 
mit dem GCC für diese Architektur.
Ich habe vor kurzem das Projekt geteilt, sodass das Compilieren nicht 
mehr so ewig dauert. Fertige Module habe ich in statische Libs 
ausgelagert.

Nun habe ich folgendes Problem:
In den statischen Libs befinden sich globale Klassen. Auf wundersame 
wiese werden deren Konstruktoren vor der Main nicht mehr aufgerufen.

Woran kann sowas liegen?

Ich habe mal eine globale Klasse die mit "extern" im Header deklariert 
ist, in die Main.cpp reinkopiert sodass nur noch die -extern-Deklaration 
in der ausgelagerten Headerdatei ist. Die Deklaration ohne extern ist in 
der Main.cpp.

Wird die globale Klasse nun im Hauptprogram "definiert", anstatt in 
einer Datei aus einer static Lib, dann wird der Konstruktor normal 
aufgerufen.

Was genau haut mir das auslagern kaput?

Ich hoffe auf fachkundige Nicht-Spam-Antworten :-)

MFG

von Stephan (Gast)


Lesenswert?

Hi,

hab mal etwas gegrübelt....
Normaler weise wird im Startup-Code des MC die Daten von Klassen ins RAM 
kopiert. Danach erfolgt der Aufruf der Konstruktoren.
1
/* Call constructors */
2
  ldr r0, =__ctors_start__  <<-- diese Sectionen sind wichtig
3
  ldr r1, =__ctors_end__    <<-- diese Sectionen sind wichtig
4
ctor_loop:
5
  cmp r0, r1
6
  beq ctor_end
7
  ldr r2, [r0], #+4
8
  stmfd sp!, {r0-r1}
9
  mov lr, pc
10
#ifdef __ARM_ARCH_3__
11
  mov pc, r2
12
#else    
13
  bx r2
14
#endif
15
  ldmfd sp!, {r0-r1}
16
  b ctor_loop
17
ctor_end:

Kann es nun bei dir möglich sein, dass die Daten von der Lib in einer 
anderen Section gelandet sind und so nicht mehr ausgeführt werden???

Ich kann dir leider nicht sagen, wo man da schauen könnte. :-(
(Aufbau der Lib (MAP), Linker .....)

ein Versuch fällt mir ein:
Programm einmal mit Lib und einmal ohne Lib erstellen und dann die 
Grösse von der Section berechnen, sollte sie gleich sein, sind deine 
Daten von der Lib woanders gelandet! (_ctors_end__ - __ctors_start_ = 
size)

Vielleicht bringt dich das auf eine Idee oder ein anderer hier im Forum 
kann noch mehr dazu sagen.

Stephan

von Andreas B. (Gast)


Lesenswert?

Zwei Sachen fallen mir da ein:

Wird mit -nostdlib oder Linkerscript gelinkt, muss meistens trotzdem die 
libgcc.a mit -lgcc reingelinkt werden. Die kümmert sich auch um 
Konstruktoren.

Wir wissen ja nicht um welche Klassen es sich handelt, aber wenn es um 
so etwas wie als globale Variablen deklarierte Objekte geht, die sich 
durch den Konstruktor selber irgendwo einklinken aber sonst nicht direkt 
referenziert werden, dann erklärt sich das auch. Aus einer statischen 
Library werden nur die Objektdateien reingelinkt, die auch tatsächlich 
Referenzen auflösen.

von DerAlbi (Gast)


Lesenswert?

Danke erstmal für euren Einsatz.
Die Sections sind ok. Soweit hab ich am Linkerfile nichts geändert und 
außerdem sollte dann auch das Hauptprogramm keine Konstruktoren 
aufgerufen bekommen.

Soweit ich sehe, hat Andreas B. recht. Es ist das leidige Problem, dass
>Aus einer statischen Library werden nur die Objektdateien reingelinkt, die >auch 
tatsächlich Referenzen auflösen.

Da Konstruktoren nie wirklich aufgerufen werden (aus dem Code), werden 
sie nicht mit in die Lib reingenommen. Das nervt.
Es gibt das Linker flag --whole-archive, aber dann kommen wilde fehler, 
da ja jede Static lib für sich selbst diverse Grundfunktionen 
implementiert. Irgendwie ist das komisch.

Fehlermeldungen sind alle wie:

In function `__emutls_register_common': multiple definition of 
`__emutls_register_common'
n function `__emutls_get_address': multiple definition of 
`__emutls_get_address'

blahh usw.

ICh verstehe den Grund der Fehlermeldungen. Aber ich sehe dann keinen 
Ausweg mehr, wie ich das Konstruktorproblem lößen soll.
Es muss dafür ja eine schöne Lösung geben.

von Andreas B. (Gast)


Lesenswert?

> Da Konstruktoren nie wirklich aufgerufen werden (aus dem Code), werden
> sie nicht mit in die Lib reingenommen. Das nervt.

Nur um es noch mal zu präzisieren: Wenn die Objektdatei aus einer 
statischen Lib irgendeine Referenz auflöst wird sie reingelinkt und wenn 
sie gelinkt wird, werden auch sonstige Konstruktoren in derselben Datei 
aufgerufen.

Aber insgesamt ist wohl das Problem, dass du statische Libs überhaupt 
verwendest. Libs sind dazu da, um Code für andere Programme zur 
Verfügung zu stellen, aus diesem Grund wird auch das ausgelassen was 
nicht referenziert wird.

Die Compile-Zeit bei größeren Projekten zu reduzieren ist die Aufgabe 
von make, der nur die Dateien übersetzt, bei denen es nötig ist. Wenn 
das nicht funktioniert, sollte das Makefile korrigiert werden anstatt 
auf Libs auszuweichen.

von DerAlbi (Gast)


Lesenswert?

Du hast schon recht, die CompileZeit ist bei geringen veränderungen 
nicht lang. Aber ändere mal die Optimierungsstufe um zu tersten dass 
alles bei =O0, O1, O2, O3 und Os geauso funktioniert. Da sitzte dann da 
und sagst dir: Ahhhja. Schade dass ich kein Fernsehr habe ;-)

Und dazu kommt auch noch die Linkzeit, die aber JEDES mal fällig ist. 
und für 120 .o Dateien rattert mein Rechner schon ganz schön. Das geht 
mit den Libs schon wesentlich schneller.

Die einzelnen .o dateien in den Libs.. hmmh.
Problem: die Klassen werden praktisch über ihren Konstruktor an das GUI 
system angemeldet, dass dann nur noch Funktionspointer und Namen (zur 
Laufzeit) kennt.
Wenn eine GUI Komponente einen Handler hat, wird dann in der Liste der 
registrierten Funktionen gesucht und der Funktionspointer zugewiesen. 
Nur so werden die Klassenfunktionen zum schluss aufgerufen. Die 
Anmeldung der Klassenfunktionen passiert relativ automatisiert über 
Vererbungsmechanismen und einem kleinen Macro.

So. Nun wird der anmeldende Konstruktor aber nicht ausgeführt, die 
Funktionen werden nicht registriert. Dadurch wiederrum ist es sinnlos 
für den Linker die .o noch einzubinden.
Teufelskreis. So ein Dreck. Das is ja schon fast Bug-Haftes Verhalten 
des Linkers.

Sinnlose funktionen zu schreiben, nur um jede Datei mit sicherheit 
einzubinden ist einfach keine sinnvolle Lösung.
Es kann doch nicht sein, dass aufgetrennter Code sich anders verhält als 
Code aus einer einzelnen Compilierung.

von Andreas B. (Gast)


Lesenswert?

> Und dazu kommt auch noch die Linkzeit, die aber JEDES mal fällig ist.
> und für 120 .o Dateien rattert mein Rechner schon ganz schön. Das geht
> mit den Libs schon wesentlich schneller.

Vielleicht weil das Dateisystem fragmentiert ist? Eine statische Lib mit 
den GNU binutils ist ja nichts anderes als alle Objekt-Dateien in eine 
Archiv-Datei gepackt. Rein von der Linkerei (Anzahl der .o) bleibt sichs 
also gleich.

> Sinnlose funktionen zu schreiben, nur um jede Datei mit sicherheit
> einzubinden ist einfach keine sinnvolle Lösung.

Nö. Um eine Datei mit Sicherheit einzubinden, muss man sie nur beim 
Linken angeben.

> Es kann doch nicht sein, dass aufgetrennter Code sich anders verhält als
> Code aus einer einzelnen Compilierung.

Tut er auch nicht. Libraries sind aber was anderes als aufgetrennter 
Code.


Aber es gibt noch eine Möglichkeit: Man kann mehrere .o zu einer 
größeren .o zusammenlinken, vielleicht hilft das. Option -r beim GNU ld.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

1
ld --help | grep -i constr
2
  -Ur                         Build global constructor/destructor tables
3
  --warn-constructors         Warn if global constructors/destructors are seen

Das sind binutils-2.19

Was die Build-Zeit angeht, hilft evtl. ne Übersetzung mit gcc -pipe ...
Ebenso make -j<n>

Zudem muss nach einem build für O0, O1, ... kein clean gemacht werden, 
wenn mehrer Projekte angelegt sind die die gleiche Quelle verwenden und 
in unterschiedliche Verzeichnisse generieren --> build parallelisierbar, 
overhead durch clean fällt weg, besser diff'bar

von Andreas B. (Gast)


Lesenswert?

Johann L. schrieb:
> ld --help | grep -i constr
>   -Ur                         Build global constructor/destructor tables
>   --warn-constructors         Warn if global constructors/destructors are seen

Vor globalen Konstruktoren mit --warn-constructors zu warnen wird nicht 
helfen, wenn er globale Konstruktoren mit Absicht einsetzt.

Aber trotzdem gut, dass du das geschrieben hast. Um aus anderen Objekten 
wieder ein linkbares Objekt zu linken, muss man die Option -Ur statt -r 
für C++ angeben. Zumindest, wenn man nicht in mehr als zwei Stufen 
linkt:
1
`-Ur'
2
     For anything other than C++ programs, this option is equivalent to
3
     `-r': it generates relocatable output--i.e., an output file that
4
     can in turn serve as input to `ld'.  When linking C++ programs,
5
     `-Ur' _does_ resolve references to constructors, unlike `-r'.  It
6
     does not work to use `-Ur' on files that were themselves linked
7
     with `-Ur'; once the constructor table has been built, it cannot
8
     be added to.  Use `-Ur' only for the last partial link, and `-r'
9
     for the others.

von DerAlbi (Gast)


Lesenswert?

Blöde Frage aber:
wo setze ich diese Linkeroption ein?
a) in den LinkerCommand des Hauptprogrammes
b) in den LinkerCommand der Libs (AVR32 Studio nennt das AVR32 Archiver 
- der wird mit -r gefüttert zZ)

Im Hauptprogramm hat es zu misteriösen fehlern geführt, deren Wortlaut 
ich gerade nicht mehr wiedergeben kann.

Btw: Linkerspeed ist wirklich vn der Anzahl der Dateien abhängig. Mit 
Fragmentierung hat das nichts zu tun. Die Dateien müssen alle einzeln 
geöffnet werden und das ist das was Zeit frist. (Wäre auch auf meinem 
Mp3-Player so ;-) ) Eine Einzelne datei zu öffnen ist hingegen rasend 
schnell.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Ich kannte die Option bislang auch nicht, hab nur ein grep auf ld --help 
gemacht.

Auch weiß ich nicht, inwieweit das nur relokatierbaren Code betrifft und 
ob du relokatierbaren Code brauchst und auch dafür compilierst (zB 
-fpic, -fPIC, -mpic oder so).

In den seltensten Fällen ist es git, ld direkt aufzurufen. Stattdessen 
rugt man gcc auf und passt dio Opt an ld per gcc -Wl,Ur

Wissenswert für dich ist bestimmt auch

http://gcc.gnu.org/onlinedocs/gccint/Collect2.html

von Andreas B. (Gast)


Lesenswert?

Um die Option einzusetzen muss man das Makefile anpassen. Es geht ja 
darum, in zwei Stufen zu linken.

Normalerweise sieht der letzte Schritt ja so ähnlich aus:
1
output.elf: a.o b.o c.o d.o
2
        $(LD) $(LDFLAGS) -o $@ $^

Das könnte man dann beispielsweise so machen:
1
output.elf: ab.o cd.o
2
        $(LD) $(LDFLAGS) -o $@ $^
3
4
ab.o: a.o b.o
5
        $(LD) $(LDFLAGS) -Ur -o $@ $^
6
7
cd.o: c.o d.o
8
        $(LD) $(LDFLAGS) -Ur -o $@ $^

Wenn man in diesem Fall schon einmal komplett übersetzt hat und dann was 
an b.c ändert, wird es zu b.o übersetzt, zu ab.o gelinkt und dann ab.o 
und (die schon existierende) cd.o gelinkt. Was jetzt noch keine so große 
Ersparnis ist, aber bei vielen Objekten und guter Aufteilung sicher was 
bringt.

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.