Comment supprimer les symboles C / C ++ inutilisés avec GCC et LD?

110

J'ai besoin d'optimiser sévèrement la taille de mon exécutable ( ARMdéveloppement) et j'ai remarqué que dans mon schéma de construction actuel ( gcc+ ld) les symboles inutilisés ne sont pas supprimés.

L'utilisation de arm-strip --strip-unneededpour les exécutables / bibliothèques résultants ne change pas la taille de sortie de l'exécutable (je ne sais pas pourquoi, peut-être que cela ne peut tout simplement pas) .

Quelle serait la façon (si elle existe) de modifier mon pipeline de construction, de sorte que les symboles inutilisés soient supprimés du fichier résultant?


Je n'y penserais même pas, mais mon environnement embarqué actuel n'est pas très "puissant" et économise même 500Kdes 2Mrésultats dans une très belle amélioration des performances de chargement.

Mettre à jour:

Malheureusement, la gccversion actuelle que j'utilise n'a pas l' -dead-stripoption et le -ffunction-sections... + --gc-sectionsfor ldne donne aucune différence significative pour la sortie résultante.

Je suis choqué que cela soit même devenu un problème, car j'étais sûr que cela gcc + lddevrait automatiquement supprimer les symboles inutilisés (pourquoi doivent-ils même les conserver?).

Yippie-Ki-Yay
la source
Comment savez-vous que les symboles ne sont pas utilisés?
zvrba
Non référencé nulle part => non utilisé dans l'application finale. Je suppose que la création d'un graphe d'appel lors de la compilation / liaison ne devrait pas être très difficile.
Yippie-Ki-Yay
1
Essayez-vous de réduire la taille du fichier .o en supprimant les symboles morts , ou vous essayez de réduire la taille de l'empreinte de code réelle une fois chargée dans la mémoire exécutable? Le fait que vous disiez «intégré» fait allusion à ce dernier; la question que vous posez semble centrée sur la première.
Ira Baxter le
@Ira J'essaie de réduire la taille de l'exécutable de sortie, car (à titre d'exemple) si j'essaie de porter certaines applications existantes, qui utilisent des boostbibliothèques, le .exefichier résultant contient de nombreux fichiers objets inutilisés et en raison des spécifications de mon runtime intégré actuel , le démarrage d'une 10mbapplication prend beaucoup plus de temps que, par exemple, le démarrage d'une 500kapplication.
Yippie-Ki-Yay
8
@Yippie: Vous voulez vous débarrasser du code pour minimiser le temps de chargement; le code dont vous voulez vous débarrasser sont des méthodes inutilisées / etc. des bibliothèques. Oui, vous devez créer un graphe d'appel pour ce faire. Ce n'est pas si simple; il doit s'agir d'un graphe d'appel global, il doit être conservateur (ne peut pas supprimer quelque chose qui pourrait être utilisé) et doit être précis (donc vous avez le plus proche d'un graphe d'appel idéal, donc vous savez vraiment ce qui n'est pas utilisé). Le gros problème est de créer un graphe d'appel global et précis. Je ne connais pas de nombreux compilateurs qui font cela, sans parler des éditeurs de liens.
Ira Baxter le

Réponses:

131

Pour GCC, cela se fait en deux étapes:

Commencez par compiler les données, mais dites au compilateur de séparer le code en sections distinctes au sein de l'unité de traduction. Cela sera fait pour les fonctions, les classes et les variables externes en utilisant les deux indicateurs de compilateur suivants:

-fdata-sections -ffunction-sections

Liez les unités de traduction ensemble à l'aide de l'indicateur d'optimisation de l'éditeur de liens (cela oblige l'éditeur de liens à supprimer les sections non référencées):

-Wl,--gc-sections

Donc, si vous aviez un fichier appelé test.cpp qui contenait deux fonctions déclarées, mais que l'une d'elles n'était pas utilisée, vous pouvez omettre le fichier inutilisé avec la commande suivante à gcc (g ++):

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

(Notez que -Os est un indicateur de compilateur supplémentaire qui indique à GCC d'optimiser la taille)

JT
la source
3
Veuillez noter que cela ralentira l'exécutable selon les descriptions des options de GCC (j'ai testé).
métamorphose
1
Avec mingwcela ne fonctionne pas lors de la liaison statiquement statique de libstdc ++ et libgcc avec l'indicateur -static. L'option de l'éditeur de liens -strip-allaide un peu, mais l'exécutable généré (ou dll) est environ 4 fois plus grand que ce que Visual Studio générerait. Le fait est que je n'ai aucun contrôle sur la façon dont a libstdc++été compilé. Il devrait y avoir une ldseule option.
Fabio
34

Si l' on en croit ce thread , vous devez fournir le -ffunction-sectionset-fdata-sections à gcc, qui placera chaque fonction et objet de données dans sa propre section. Ensuite, vous donnez et --gc-sectionsà GNU ld pour supprimer les sections inutilisées.

Nemo
la source
6
@MSalters: Ce n'est pas la valeur par défaut, car elle enfreint les normes C et C ++. L'initialisation globale ne se produit soudainement pas, ce qui rend certains programmeurs très surpris.
Ben Voigt
1
@MSalters: Uniquement si vous passez les options non standard de rupture de comportement, que vous avez proposé de définir comme comportement par défaut.
Ben Voigt
1
@MSalters: Si vous pouvez créer un correctif qui exécute des initialiseurs statiques si et seulement si les effets secondaires sont nécessaires au bon fonctionnement du programme, ce serait génial. Malheureusement, je pense que le faire parfaitement nécessite souvent de résoudre le problème d'arrêt, vous devrez donc probablement vous tromper en ajoutant parfois des symboles supplémentaires. C'est essentiellement ce que dit Ira dans ses commentaires sur la question. (BTW: "pas nécessaire au bon fonctionnement du programme" est une définition différente de "inutilisé" que la façon dont ce terme est utilisé dans les normes)
Ben Voigt
2
@BenVoigt en C, l'initialisation globale ne peut pas avoir d'effets secondaires (les initialiseurs doivent être des expressions constantes)
MM
2
@Matt: Mais ce n'est pas vrai en C ++ ... et ils partagent le même éditeur de liens.
Ben Voigt
25

Vous voudrez vérifier vos documents pour votre version de gcc & ld:

Cependant pour moi (OS X gcc 4.0.1) je trouve ceux-ci pour ld

-dead_strip

Supprimez les fonctions et les données inaccessibles par le point d'entrée ou les symboles exportés.

-dead_strip_dylibs

Supprimez les dylibs inaccessibles par le point d'entrée ou les symboles exportés. Autrement dit, supprime la génération de commandes de commande de chargement pour les dylibs qui n'ont fourni aucun symbole pendant la liaison. Cette option ne doit pas être utilisée lors de la liaison avec un dylib qui est requis au moment de l'exécution pour une raison indirecte telle que le dylib a un initialiseur important.

Et cette option utile

-why_live symbol_name

Enregistre une chaîne de références à symbol_name. Uniquement applicable avec -dead_strip. Cela peut aider à déboguer pourquoi quelque chose qui, selon vous, devrait être supprimé n'est pas supprimé.

Il y a aussi une note dans le manuel gcc / g ++ que certains types d'élimination de code mort ne sont effectués que si l'optimisation est activée lors de la compilation.

Bien que ces options / conditions ne soient pas valables pour votre compilateur, je vous suggère de rechercher quelque chose de similaire dans vos documents.

Michael Anderson
la source
Cela semble ne rien faire avec mingw.
Fabio
-dead_stripn'est pas une gccoption.
ar2015
20

Les habitudes de programmation pourraient également aider; par exemple, ajouter staticaux fonctions qui ne sont pas accessibles en dehors d'un fichier spécifique; utiliser des noms plus courts pour les symboles (peut aider un peu, probablement pas trop); utiliser const char x[]dans la mesure du possible; ... cet article , bien qu'il parle d'objets partagés dynamiques, peut contenir des suggestions qui, si elles sont suivies, peuvent aider à réduire la taille de votre sortie binaire finale (si votre cible est ELF).

ShinTakezou
la source
4
Comment cela aide-t-il à choisir des noms plus courts pour les symboles?
fuz le
1
si les symboles ne sont pas supprimés, ça va sans dire - mais il semble qu'il fallait le dire maintenant.
ShinTakezou
@fuz Le papier parle d'objets partagés dynamiques (par exemple .sosous Linux), donc les noms de symboles doivent être conservés afin que les API comme le ctypesmodule FFI de Python puissent les utiliser pour rechercher des symboles par nom au moment de l'exécution.
ssokolow
18

La réponse est -flto. Vous devez le transmettre à la fois à vos étapes de compilation et de lien, sinon cela ne fait rien.

Cela fonctionne très bien - réduit la taille d'un programme de microcontrôleur que j'ai écrit à moins de 50% de sa taille précédente!

Malheureusement, cela semblait un peu bogué - j'ai eu des exemples de choses qui n'étaient pas construites correctement. Cela peut être dû au système de construction que j'utilise (QBS; c'est très nouveau), mais dans tous les cas, je vous recommande de ne l'activer que pour votre version finale si possible, et de tester cette construction à fond.

Timmmm
la source
1
"-Wl, - gc-sections" ne fonctionne pas sur MinGW-W64, "-flto" fonctionne pour moi. Merci
rhbc73
Le montage de sortie est très bizarre avec -fltoje ne comprends pas ce qu'il fait en coulisse.
ar2015
Je crois que -fltocela ne compile pas chaque fichier en assemblage, il les compile en LLVM IR, puis le lien final les compile comme s'ils étaient tous dans une seule unité de compilation. Cela signifie qu'il peut éliminer les fonctions inutilisées et les non- staticuns inline , et probablement d'autres choses aussi. Voir llvm.org/docs/LinkTimeOptimization.html
Timmmm
13

Bien que ce ne soit pas strictement sur les symboles, si vous choisissez la taille, compilez toujours avec les indicateurs -Oset -s. -Osoptimise le code résultant pour la taille minimale de l'exécutable et -ssupprime la table de symboles et les informations de déplacement de l'exécutable.

Parfois - si une petite taille est souhaitée - jouer avec différents indicateurs d'optimisation peut - ou non - avoir une signification. Par exemple, basculer -ffast-mathet / ou -fomit-frame-pointerpeut parfois vous faire économiser même des dizaines d'octets.

zxcdw
la source
La plupart des ajustements d'optimisation produiront toujours un code correct tant que vous respectez la norme du langage, mais j'ai fait -ffast-mathdes ravages dans un code C ++ entièrement conforme aux normes, donc je ne le recommanderais jamais.
Raptor007
11

Il me semble que la réponse fournie par Nemo est la bonne. Si ces instructions ne fonctionnent pas, le problème peut être lié à la version de gcc / ld que vous utilisez, comme exercice, j'ai compilé un exemple de programme en utilisant les instructions détaillées ici

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

Ensuite, j'ai compilé le code en utilisant des commutateurs de suppression de code mort de plus en plus agressifs:

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

Ces paramètres de compilation et de liaison ont produit des exécutables de taille 8457, 8164 et 6160 octets, respectivement, la contribution la plus importante provenant de la déclaration «strip-all». Si vous ne pouvez pas produire de réductions similaires sur votre plate-forme, alors peut-être que votre version de gcc ne prend pas en charge cette fonctionnalité. J'utilise gcc (4.5.2-8ubuntu4), ld (2.21.0.20110327) sur Linux Mint 2.6.38-8-generic x86_64

Gearoid Murphy
la source
8

strip --strip-unneededne fonctionne que sur la table des symboles de votre exécutable. Il ne supprime en fait aucun code exécutable.

Les bibliothèques standard obtiennent le résultat que vous recherchez en divisant toutes leurs fonctions en fichiers objets séparés, qui sont combinés à l'aide de ar. Si vous liez ensuite l'archive résultante en tant que bibliothèque (c'est-à-dire donnez l'option -l your_libraryà ld) alors ld n'inclura que les fichiers objets, et donc les symboles, qui sont réellement utilisés.

Vous pouvez également trouver certaines des réponses à cette question d'utilisation similaire .

Andrew Edgecombe
la source
2
Les fichiers objets séparés dans la bibliothèque ne sont pertinents que lors de la création d'un lien statique. Avec les bibliothèques partagées, toute la bibliothèque est chargée, mais pas incluse dans l'exécutable, bien sûr.
Jonathan Leffler
4

Je ne sais pas si cela vous aidera dans votre situation actuelle car il s'agit d'une fonctionnalité récente, mais vous pouvez spécifier la visibilité des symboles de manière globale. Passer -fvisibility=hidden -fvisibility-inlines-hiddenà la compilation peut aider l'éditeur de liens à se débarrasser plus tard des symboles inutiles. Si vous produisez un exécutable (par opposition à une bibliothèque partagée), il n'y a plus rien à faire.

Plus d'informations (et une approche plus fine pour les bibliothèques par exemple) sont disponibles sur le wiki GCC .

Luc Danton
la source
4

Extrait du manuel GCC 4.2.1, section -fwhole-program :

Supposons que l'unité de compilation actuelle représente l'ensemble du programme en cours de compilation. Toutes les fonctions et variables publiques à l'exception de mainet celles fusionnées par attribut externally_visibledeviennent des fonctions statiques et dans un affect sont optimisées de manière plus agressive par les optimiseurs interprocéduraux. Bien que cette option équivaut à une utilisation correcte du staticmot - clé pour les programmes composés d'un seul fichier, en combinaison avec l'option, --combinecet indicateur peut être utilisé pour compiler la plupart des programmes C à plus petite échelle puisque les fonctions et les variables deviennent locales pour l'ensemble de l'unité de compilation combinée, pas pour le fichier source unique lui-même.

awiebe
la source
Ouais, mais cela ne fonctionne probablement avec aucun type de compilation incrémentielle et sera probablement un peu lent.
Timmmm
@Timmmm: Je suppose que vous pensez -flto.
Ben Voigt
Oui! J'ai découvert par la suite que (pourquoi n'est-ce pas l'une des réponses?). Malheureusement, cela semblait un peu bogué, donc je ne le recommanderais que pour la version finale, puis je testais beaucoup cette construction!
Timmmm
-1

Vous pouvez utiliser strip binary sur un fichier objet (ex. Exécutable) pour en retirer tous les symboles.

Remarque: il change le fichier lui-même et ne crée pas de copie.

ton4eg
la source