Wie entferne ich nicht verwendete C / C ++ - Symbole mit GCC und ld?

110

Ich muss die Größe meiner ausführbaren Datei stark optimieren ( ARMEntwicklung) und habe festgestellt, dass in meinem aktuellen Build-Schema ( gcc+ ld) nicht verwendete Symbole nicht entfernt werden.

Die Verwendung von arm-strip --strip-unneededfür die resultierenden ausführbaren Dateien / Bibliotheken ändert nichts an der Ausgabegröße der ausführbaren Datei (ich habe keine Ahnung warum, vielleicht kann es einfach nicht) .

Wie kann ich meine Gebäude-Pipeline so ändern (falls vorhanden) , dass die nicht verwendeten Symbole aus der resultierenden Datei entfernt werden?


Ich würde nicht einmal daran denken, aber meine derzeitige eingebettete Umgebung ist nicht sehr "leistungsfähig" und das Speichern selbst 500Kvon 2MErgebnissen führt zu einer sehr schönen Steigerung der Ladeleistung.

Aktualisieren:

Leider hat die aktuelle gccVersion, die ich verwende, keine -dead-stripOption und das -ffunction-sections... + --gc-sectionsfor ldgibt keinen signifikanten Unterschied für die resultierende Ausgabe.

Ich bin schockiert, dass dies sogar zu einem Problem wurde, weil ich mir sicher war, dass gcc + ldnicht verwendete Symbole automatisch entfernt werden sollten (warum müssen sie sie überhaupt behalten?).

Yippie-Ki-Yay
quelle
Woher wissen Sie, dass Symbole nicht verwendet werden?
Zvrba
Nirgendwo referenziert => wird in der endgültigen Anwendung nicht verwendet. Ich gehe davon aus, dass das Erstellen eines Anrufdiagramms beim Komprimieren / Verknüpfen nicht sehr schwierig sein sollte.
Yippie-Ki-Yay
1
Versuchen Sie, die Größe der .o-Datei durch Entfernen toter Symbole zu verringern , oder versuchen Sie, die Größe des tatsächlichen Code-Footprints zu verringern, sobald dieser in den ausführbaren Speicher geladen wurde? Die Tatsache, dass Sie "eingebettet" sagen, deutet auf Letzteres hin; Die Frage, die Sie stellen, scheint sich auf die erstere zu konzentrieren.
Ira Baxter
@Ira Ich versuche, die Größe der ausführbaren Ausgabedatei zu reduzieren, da (als Beispiel) wenn ich versuche, einige vorhandene Anwendungen zu portieren, die boostBibliotheken verwenden, die resultierende .exeDatei viele nicht verwendete Objektdateien enthält und aufgrund der Spezifikationen meiner aktuellen eingebetteten Laufzeit Das Starten einer 10mbAnwendung dauert viel länger als beispielsweise das Starten einer 500kAnwendung.
Yippie-Ki-Yay
8
@ Yippie: Sie möchten Code entfernen, um die Ladezeit zu minimieren. Der Code, den Sie entfernen möchten, sind nicht verwendete Methoden / etc. aus Bibliotheken. Ja, Sie müssen dazu ein Anrufdiagramm erstellen. Es ist nicht so einfach; Es muss ein globales Anrufdiagramm sein, es muss konservativ sein (kann etwas nicht entfernen, das verwendet werden könnte) und es muss genau sein (damit Sie einem idealen Anrufdiagramm so nahe kommen, dass Sie wirklich wissen, was nicht gebraucht). Das große Problem besteht darin, ein globales, genaues Anrufdiagramm zu erstellen. Ich kenne nicht viele Compiler, die dies tun, geschweige denn Linker.
Ira Baxter

Antworten:

131

Für GCC erfolgt dies in zwei Schritten:

Kompilieren Sie zuerst die Daten, aber weisen Sie den Compiler an, den Code in separate Abschnitte innerhalb der Übersetzungseinheit zu unterteilen. Dies erfolgt für Funktionen, Klassen und externe Variablen mithilfe der folgenden zwei Compiler-Flags:

-fdata-sections -ffunction-sections

Verknüpfen Sie die Übersetzungseinheiten mithilfe des Linker-Optimierungsflags (dies führt dazu, dass der Linker nicht referenzierte Abschnitte verwirft):

-Wl,--gc-sections

Wenn Sie also eine Datei namens test.cpp hatten, in der zwei Funktionen deklariert waren, von denen jedoch eine nicht verwendet wurde, können Sie die nicht verwendete mit dem folgenden Befehl an gcc (g ++) weglassen:

gcc -Os -fdata-sections -ffunction-sections test.cpp -o test -Wl,--gc-sections

(Beachten Sie, dass -Os ein zusätzliches Compiler-Flag ist, das GCC anweist, die Größe zu optimieren.)

JT
quelle
3
Bitte beachten Sie, dass dies die ausführbare Datei gemäß den Optionsbeschreibungen von GCC verlangsamt (ich habe getestet).
Metamorphose
1
Mit mingwdiesem nicht funktioniert , wenn mit der Flagge statisch statisch libstdc ++ und libgcc verbindet -static. Die Linker-Option -strip-allhilft einiges, aber die generierte ausführbare Datei (oder DLL) ist immer noch ungefähr viermal größer als das, was Visual Studio generieren würde. Punkt ist, ich habe keine Kontrolle darüber, wie libstdc++kompiliert wurde. Es sollte eine ldeinzige Option geben.
Fabio
34

Wenn dieser Thread angenommen werden soll, müssen Sie das -ffunction-sectionsund-fdata-sections an gcc angeben, wodurch jedes Funktions- und Datenobjekt in einem eigenen Abschnitt platziert wird. Dann geben Sie und --gc-sectionsan GNU ld, um die nicht verwendeten Abschnitte zu entfernen.

Nemo
quelle
6
@MSalters: Dies ist nicht die Standardeinstellung, da sie gegen die C- und C ++ - Standards verstößt. Plötzlich findet keine globale Initialisierung mehr statt, was zu einigen sehr überraschten Programmierern führt.
Ben Voigt
1
@MSalters: Nur wenn Sie die nicht standardmäßigen Verhaltensunterbrechungsoptionen übergeben, die Sie vorgeschlagen haben, um das Standardverhalten festzulegen.
Ben Voigt
1
@MSalters: Wenn Sie einen Patch erstellen können, der genau dann statische Initialisierer ausführt, wenn die Nebenwirkungen für den korrekten Betrieb des Programms erforderlich sind, wäre das fantastisch. Leider denke ich, dass es für das perfekte Anhalten häufig erforderlich ist, das Problem des Anhaltens zu lösen, sodass Sie wahrscheinlich manchmal auf der Seite einiger zusätzlicher Symbole irren müssen. Welches ist im Grunde, was Ira in seinen Kommentaren zu der Frage sagt. (Übrigens: "nicht notwendig für den korrekten Betrieb des Programms" ist eine andere Definition von "unbenutzt" als die Verwendung dieses Begriffs in den Standards)
Ben Voigt
2
@ BenVoigt in C kann die globale Initialisierung keine Nebenwirkungen haben (Initialisierer müssen konstante Ausdrücke sein)
MM
2
@Matt: Aber das stimmt nicht in C ++ ... und sie teilen den gleichen Linker.
Ben Voigt
25

Sie sollten Ihre Dokumente auf Ihre Version von gcc & ld überprüfen:

Für mich (OS X gcc 4.0.1) finde ich diese jedoch für ld

-dead_strip

Entfernen Sie Funktionen und Daten, die vom Einstiegspunkt oder von exportierten Symbolen nicht erreicht werden können.

-dead_strip_dylibs

Entfernen Sie Dylibs, die vom Einstiegspunkt oder von exportierten Symbolen nicht erreicht werden können. Das heißt, unterdrückt die Erzeugung von Ladebefehlsbefehlen für Dylibs, die während der Verknüpfung keine Symbole geliefert haben. Diese Option sollte nicht verwendet werden, wenn eine Verknüpfung mit einer Dylib hergestellt wird, die zur Laufzeit aus indirekten Gründen erforderlich ist, z. B. wenn die Dylib einen wichtigen Initialisierer hat.

Und diese hilfreiche Option

-why_live symbol_name

Protokolliert eine Kette von Verweisen auf symbol_name. Gilt nur für -dead_strip. Es kann beim Debuggen helfen, warum etwas, von dem Sie denken, dass es entfernt werden sollte, nicht entfernt wird.

Es gibt auch einen Hinweis im gcc / g ++ - Mann, dass bestimmte Arten der Beseitigung von totem Code nur durchgeführt werden, wenn die Optimierung beim Kompilieren aktiviert ist.

Diese Optionen / Bedingungen gelten möglicherweise nicht für Ihren Compiler. Ich empfehle jedoch, dass Sie in Ihren Dokumenten nach etwas Ähnlichem suchen.

Michael Anderson
quelle
Das scheint nichts damit zu tun zu haben mingw.
Fabio
-dead_stripist keine gccOption.
ar2015
20

Programmiergewohnheiten könnten ebenfalls helfen; zB staticFunktionen hinzufügen, auf die außerhalb einer bestimmten Datei nicht zugegriffen wird; Verwenden Sie kürzere Namen für Symbole (kann ein bisschen helfen, wahrscheinlich nicht zu viel). verwenden , const char x[]sofern möglich; ... dieses Papier , obwohl es sich um dynamische gemeinsam genutzte Objekte handelt, kann Vorschläge enthalten, die, wenn sie befolgt werden, dazu beitragen können, Ihre endgültige binäre Ausgabegröße zu verkleinern (wenn Ihr Ziel ELF ist).

ShinTakezou
quelle
4
Wie hilft es, kürzere Namen für Symbole zu wählen?
Fuz
1
Wenn Symbole nicht entfernt werden, ça va sans dire - aber es scheint, dass es jetzt gesagt werden muss.
ShinTakezou
@fuz In diesem Artikel geht es um dynamische gemeinsam genutzte Objekte (z. B. .sounter Linux) . Daher müssen die Symbolnamen beibehalten werden, damit APIs wie das ctypesFFI-Modul von Python sie zur Laufzeit nach Namen suchen können.
ssokolow
18

Die Antwort lautet -flto. Sie müssen es sowohl an Ihre Kompilierungs- als auch an Ihre Linkschritte übergeben, sonst macht es nichts.

Es funktioniert tatsächlich sehr gut - die Größe eines Mikrocontroller-Programms, das ich geschrieben habe, wurde auf weniger als 50% seiner vorherigen Größe reduziert!

Leider schien es ein bisschen fehlerhaft zu sein - ich hatte Fälle, in denen Dinge nicht richtig gebaut wurden. Möglicherweise lag es an dem von mir verwendeten Build-System (QBS; es ist sehr neu), aber ich würde auf jeden Fall empfehlen, es nur für Ihren endgültigen Build zu aktivieren, wenn möglich, und diesen Build gründlich zu testen.

Timmmm
quelle
1
"-Wl, - gc-Abschnitte" funktioniert bei MinGW-W64 nicht, "-flto" funktioniert bei mir. Danke
rhbc73
Die Ausgabebaugruppe ist sehr seltsam, da -fltoich nicht verstehe, was sie hinter den Kulissen tut.
ar2015
Ich glaube, -fltodass damit nicht jede Datei zu Assembly kompiliert wird, sondern sie zu LLVM IR kompiliert werden und der letzte Link sie dann kompiliert, als ob sie alle in einer Kompilierungseinheit wären. Dies bedeutet, dass nicht verwendete Funktionen und Inline-Funktionen staticund wahrscheinlich auch andere Dinge eliminiert werden können . Siehe llvm.org/docs/LinkTimeOptimization.html
Timmmm
13

Wenn es um Größe geht, kompilieren Sie immer mit -Osund -sFlags. -Osoptimiert den resultierenden Code für die minimale Größe -sder ausführbaren Datei und entfernt die Symboltabelle und die Verschiebungsinformationen aus der ausführbaren Datei.

Manchmal - wenn eine kleine Größe gewünscht wird - kann das Herumspielen mit verschiedenen Optimierungsflags von Bedeutung sein oder auch nicht. Zum Beispiel können Sie durch Umschalten -ffast-mathund / oder -fomit-frame-pointermanchmal sogar Dutzende von Bytes sparen.

zxcdw
quelle
Die meisten Optimierungsänderungen liefern immer noch korrekten Code, solange Sie den Sprachstandard einhalten, aber ich habe -ffast-mathChaos in vollständig standardkonformem C ++ - Code angerichtet, daher würde ich ihn niemals empfehlen.
Raptor007
11

Es scheint mir, dass die Antwort von Nemo die richtige ist. Wenn diese Anweisungen nicht funktionieren, hängt das Problem möglicherweise mit der von Ihnen verwendeten Version von gcc / ld zusammen. Als Übung habe ich ein Beispielprogramm mit den hier beschriebenen Anweisungen zusammengestellt

#include <stdio.h>
void deadcode() { printf("This is d dead codez\n"); }
int main(void) { printf("This is main\n"); return 0 ; }

Dann habe ich den Code mit zunehmend aggressiveren Schaltern zum Entfernen von totem Code kompiliert:

gcc -Os test.c -o test.elf
gcc -Os -fdata-sections -ffunction-sections test.c -o test.elf -Wl,--gc-sections
gcc -Os -fdata-sections -ffunction-sections test.c -o test.elf -Wl,--gc-sections -Wl,--strip-all

Diese Kompilierungs- und Verknüpfungsparameter ergaben ausführbare Dateien der Größe 8457, 8164 bzw. 6160 Byte, wobei der wichtigste Beitrag aus der "Strip-All" -Deklaration stammt. Wenn Sie auf Ihrer Plattform keine ähnlichen Reduzierungen erzielen können, unterstützt Ihre Version von gcc diese Funktionalität möglicherweise nicht. Ich verwende gcc (4.5.2-8ubuntu4), ld (2.21.0.20110327) unter Linux Mint 2.6.38-8-generic x86_64

Gearoid Murphy
quelle
8

strip --strip-unneededfunktioniert nur mit der Symboltabelle Ihrer ausführbaren Datei. Es wird kein ausführbarer Code entfernt.

Die Standardbibliotheken erzielen das gewünschte Ergebnis, indem sie alle ihre Funktionen in separate Objektdateien aufteilen, die mithilfe von kombiniert werden ar. Wenn Sie dann das resultierende Archiv als Bibliothek verknüpfen (dh -l your_libraryld die Option geben ), enthält ld nur die Objektdateien und damit die tatsächlich verwendeten Symbole.

Möglicherweise finden Sie auch einige Antworten auf diese ähnliche Verwendungsfrage.

Andrew Edgecombe
quelle
2
Die separaten Objektdateien in der Bibliothek sind nur relevant, wenn eine statische Verknüpfung hergestellt wird. Bei gemeinsam genutzten Bibliotheken wird die gesamte Bibliothek geladen, aber natürlich nicht in die ausführbare Datei aufgenommen.
Jonathan Leffler
4

Ich weiß nicht, ob dies bei Ihrer aktuellen Situation hilfreich sein wird, da dies eine neuere Funktion ist, aber Sie können die Sichtbarkeit von Symbolen auf globale Weise festlegen. Das Übergeben -fvisibility=hidden -fvisibility-inlines-hiddenbei der Kompilierung kann dem Linker helfen, nicht benötigte Symbole später zu entfernen. Wenn Sie eine ausführbare Datei erstellen (im Gegensatz zu einer gemeinsam genutzten Bibliothek), müssen Sie nichts weiter tun.

Weitere Informationen (und einen detaillierten Ansatz für z. B. Bibliotheken) finden Sie im GCC-Wiki .

Luc Danton
quelle
4

Aus dem GCC 4.2.1-Handbuch, Abschnitt -fwhole-program:

Angenommen, die aktuelle Kompilierungseinheit repräsentiert das gesamte zu kompilierende Programm. Alle öffentlichen Funktionen und Variablen mit Ausnahme von mainund diejenigen, die durch Attribute zusammengeführt werden, werden externally_visiblezu statischen Funktionen und werden in einem Affekt durch Interprocedural-Optimierer aggressiver optimiert. Während diese Option der richtigen Verwendung des staticSchlüsselworts für Programme entspricht, die aus einer einzelnen Datei bestehen, --combinekann dieses Flag in Kombination mit der Option zum Kompilieren der meisten kleineren C-Programme verwendet werden, da die Funktionen und Variablen für die gesamte kombinierte Kompilierungseinheit lokal werden, nicht für die einzelne Quelldatei selbst.

awiebe
quelle
Ja, aber das funktioniert vermutlich nicht mit irgendeiner inkrementellen Kompilierung und wird wahrscheinlich etwas langsam sein.
Timmmm
@Timmmm: Ich vermute du denkst an -flto.
Ben Voigt
Ja! Ich fand das später (warum ist es keine der Antworten?). Leider schien es ein bisschen fehlerhaft zu sein, daher würde ich es nur für den endgültigen Build empfehlen und dann diesen Build viel testen!
Timmmm
-1

Sie können Strip Binary für Objektdatei (z. B. ausführbare Datei) verwenden, um alle Symbole daraus zu entfernen.

Hinweis: Es ändert die Datei selbst und erstellt keine Kopie.

ton4eg
quelle