Come rimuovere i simboli C / C ++ inutilizzati con GCC e ld?


110

Ho bisogno di ottimizzare la dimensione del mio eseguibile in modo serio ( ARMsviluppo) e ho notato che nel mio attuale schema di compilazione ( gcc+ ld) i simboli inutilizzati non vengono rimossi.

L'uso di arm-strip --strip-unneededper gli eseguibili / librerie risultanti non cambia la dimensione di output dell'eseguibile (non ho idea del perché, forse semplicemente non può) .

Quale sarebbe il modo (se esiste) per modificare la pipeline di costruzione, in modo che i simboli inutilizzati vengano rimossi dal file risultante?


Non ci penserei nemmeno, ma il mio attuale ambiente embedded non è molto "potente" e il risparmio anche 500Kdei 2Mrisultati in un bel miglioramento delle prestazioni di caricamento.

Aggiornare:

Sfortunatamente la gccversione corrente che uso non ha l' -dead-stripopzione e il -ffunction-sections... + --gc-sectionsfor ldnon dà alcuna differenza significativa per l'output risultante.

Sono scioccato dal fatto che questo sia diventato persino un problema, perché ero sicuro che gcc + ldavrebbe dovuto rimuovere automaticamente i simboli inutilizzati (perché devono anche tenerli?).


Come fai a sapere che i simboli non vengono utilizzati?
zvrba

Non referenziato da nessuna parte => non utilizzato nell'applicazione finale. Presumo che la creazione di un grafo delle chiamate durante il comipling / collegamento non dovrebbe essere molto difficile.
Yippie-Ki-Yay

1
Stai tentando di ridurre la dimensione del file .o rimuovendo i simboli morti o stai cercando di ridurre la dimensione del footprint di codice effettivo una volta caricato nella memoria eseguibile? Il fatto che tu dica "incorporato" allude a quest'ultimo; la domanda che fai sembra incentrata sulla prima.
Ira Baxter

@Ira sto cercando di ridurre la dimensione dell'eseguibile di output, perché (ad esempio) se tento di portare alcune applicazioni esistenti, che utilizzano boostlibrerie, il .exefile risultante contiene molti file oggetto inutilizzati e a causa delle specifiche del mio attuale runtime incorporato , l'avvio di 10mbun'applicazione richiede molto più tempo rispetto, ad esempio, all'avvio di 500kun'applicazione.
Yippie-Ki-Yay

8
@Yippie: vuoi sbarazzarti del codice per ridurre al minimo il tempo di caricamento; il codice di cui vuoi sbarazzarti sono metodi inutilizzati / ecc. dalle biblioteche. Sì, è necessario creare un grafico delle chiamate per farlo. Non è così facile; deve essere un grafico delle chiamate globale, deve essere conservativo (non può rimuovere qualcosa che potrebbe essere utilizzato) e deve essere accurato (quindi hai il più vicino possibile a un grafico delle chiamate ideale, quindi sai davvero cosa non lo è Usato). Il grosso problema è creare un grafico delle chiamate globale e accurato. Non conosco molti compilatori che lo fanno, figuriamoci i linker.
Ira Baxter

Risposte:


131

Per GCC, ciò si ottiene in due fasi:

Per prima cosa compila i dati ma chiedi al compilatore di separare il codice in sezioni separate all'interno dell'unità di traduzione. Questo verrà fatto per funzioni, classi e variabili esterne utilizzando i seguenti due flag del compilatore:

-fdata-sections -ffunction-sections

Collega le unità di traduzione insieme utilizzando il flag di ottimizzazione del linker (questo fa sì che il linker scarti le sezioni non referenziate):

-Wl,--gc-sections

Quindi, se avevi un file chiamato test.cpp che aveva due funzioni dichiarate in esso, ma una di esse era inutilizzata, potresti omettere quella inutilizzata con il seguente comando a gcc (g ++):

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

(Nota che -Os è un flag aggiuntivo del compilatore che dice a GCC di ottimizzare per le dimensioni)


3
Si prega di notare che questo rallenterà l'eseguibile secondo le descrizioni delle opzioni di GCC (ho testato).
metamorfosi

1
Con mingwquesto non funziona quando si collega staticamente staticamente libstdc ++ e libgcc con il flag -static. L'opzione linker -strip-allaiuta un po ', ma l'eseguibile generato (o dll) è circa 4 volte più grande di quello che Visual Studio genererebbe. Il punto è che non ho alcun controllo su come è libstdc++stato compilato. Dovrebbe esserci ldun'unica opzione.
Fabio

34

Se si vuole credere a questo thread , è necessario fornire -ffunction-sectionse-fdata-sections a gcc, che inserirà ogni funzione e oggetto dati nella propria sezione. Quindi date e --gc-sectionsa GNU ld per rimuovere le sezioni inutilizzate.


6
@MSalters: non è l'impostazione predefinita, perché viola gli standard C e C ++. Improvvisamente l'inizializzazione globale non avviene, il che si traduce in alcuni programmatori molto sorpresi.
Ben Voigt

1
@MSalters: solo se passi le opzioni di violazione del comportamento non standard, che hai proposto di rendere il comportamento predefinito.
Ben Voigt

1
@MSalters: Se puoi creare una patch che esegue inizializzatori statici se e solo se gli effetti collaterali sono necessari per il corretto funzionamento del programma, sarebbe fantastico. Sfortunatamente penso che farlo perfettamente spesso richieda la risoluzione del problema dell'arresto, quindi probabilmente dovrai sbagliare sul lato di includere alcuni simboli extra a volte. Che in fondo è quello che dice Ira nei suoi commenti alla domanda. (A proposito: "non necessario per il corretto funzionamento del programma" è una definizione diversa di "non utilizzato" rispetto a come il termine viene utilizzato negli standard)
Ben Voigt

2
@BenVoigt in C, l'inizializzazione globale non può avere effetti collaterali (gli inizializzatori devono essere espressioni costanti)
MM

2
@ Matt: Ma non è vero in C ++ ... e condividono lo stesso linker.
Ben Voigt

25

Ti consigliamo di controllare i tuoi documenti per la tua versione di gcc & ld:

Tuttavia per me (OS X gcc 4.0.1) li trovo per ld

-dead_strip

Rimuovere funzioni e dati che non sono raggiungibili dal punto di ingresso o dai simboli esportati.

-dead_strip_dylibs

Rimuovi i dylibs non raggiungibili dal punto di ingresso o dai simboli esportati. Cioè, sopprime la generazione di comandi di comando di caricamento per dylibs che non hanno fornito simboli durante il collegamento. Questa opzione non dovrebbe essere usata quando ci si collega a un dylib che è richiesto in fase di esecuzione per qualche motivo indiretto, come il dylib ha un inizializzatore importante.

E questa opzione utile

-why_live symbol_name

Registra una catena di riferimenti a symbol_name. Applicabile solo con -dead_strip. Può aiutare a eseguire il debug del motivo per cui qualcosa che pensi dovrebbe essere rimosso dalla striscia morta non viene rimosso.

C'è anche una nota in gcc / g ++ man che certi tipi di eliminazione di codice inattivo vengono eseguiti solo se l'ottimizzazione è abilitata durante la compilazione.

Sebbene queste opzioni / condizioni potrebbero non essere valide per il tuo compilatore, ti suggerisco di cercare qualcosa di simile nei tuoi documenti.


Questo sembra non avere nulla a che fare con mingw.
Fabio

-dead_stripnon è gccun'opzione.
ar2015

20

Anche le abitudini di programmazione potrebbero aiutare; es. aggiungi statica funzioni a cui non si accede al di fuori di un file specifico; usa nomi più brevi per i simboli (può aiutare un po ', probabilmente non troppo); utilizzare const char x[]dove possibile; ... questo documento , sebbene parli di oggetti dinamici condivisi, può contenere suggerimenti che, se seguiti, possono aiutare a ridurre la dimensione dell'output binario finale (se l'obiettivo è ELF).


4
Come aiuta a scegliere nomi più brevi per i simboli?
fuz

1
se i simboli non vengono rimossi, ça va sans dire - ma sembra che sia necessario dirlo ora.
ShinTakezou

@fuz Il documento parla di oggetti condivisi dinamici (es. .sosu Linux), quindi i nomi dei simboli devono essere conservati in modo che API come il ctypesmodulo FFI di Python possano usarli per cercare simboli per nome in fase di esecuzione.
ssokolow

18

La risposta è -flto. Devi passarlo sia alla fase di compilazione che a quella di collegamento, altrimenti non fa nulla.

In realtà funziona molto bene: ha ridotto le dimensioni di un programma di microcontrollore che ho scritto a meno del 50% delle dimensioni precedenti!

Sfortunatamente sembrava un po 'buggato - ho avuto casi di cose che non venivano costruite correttamente. Potrebbe essere stato dovuto al sistema di compilazione che sto utilizzando (QBS; è molto nuovo), ma in ogni caso ti consiglio di abilitarlo solo per la tua build finale, se possibile, e di testare a fondo quella build.


1
"-Wl, - gc-section" non funziona su MinGW-W64, "-flto" funziona per me. Grazie
rhbc73

L'output assembly è molto strano con -fltonon capisco cosa fa dietro le quinte.
ar2015

Credo che con -fltoesso non compili ogni file in assembly, li compila in LLVM IR, e quindi il collegamento finale li compila come se fossero tutti in un'unica unità di compilazione. Ciò significa che può eliminare funzioni inutilizzate e non inline static, e probabilmente anche altre cose. Vedi llvm.org/docs/LinkTimeOptimization.html
Timmmm

13

Anche se non strettamente sui simboli, se stai cercando la dimensione, compila sempre con -Ose -sflag. -Osottimizza il codice risultante per la dimensione minima eseguibile e -srimuove la tabella dei simboli e le informazioni di riposizionamento dall'eseguibile.

A volte, se si desidera una dimensione ridotta, giocare con diversi flag di ottimizzazione può o non può avere un significato. Ad esempio, la commutazione -ffast-mathe / o -fomit-frame-pointerpuò a volte farti risparmiare anche dozzine di byte.


La maggior parte delle modifiche all'ottimizzazione produrrà comunque il codice corretto fintanto che sarai conforme allo standard del linguaggio, ma ho -ffast-mathdevastato il codice C ++ completamente conforme agli standard, quindi non lo consiglierei mai.
Raptor007

11

Mi sembra che la risposta fornita da Nemo sia quella corretta. Se queste istruzioni non funzionano, il problema potrebbe essere correlato alla versione di gcc / ld che stai utilizzando, come esercizio ho compilato un programma di esempio utilizzando le istruzioni dettagliate qui

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

Quindi ho compilato il codice utilizzando interruttori di rimozione del codice morto progressivamente più aggressivi:

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

Questi parametri di compilazione e collegamento hanno prodotto eseguibili di dimensione rispettivamente 8457, 8164 e 6160 byte, il contributo più sostanziale proveniente dalla dichiarazione "strip-all". Se non puoi produrre riduzioni simili sulla tua piattaforma, forse la tua versione di gcc non supporta questa funzionalità. Sto usando gcc (4.5.2-8ubuntu4), ld (2.21.0.20110327) su Linux Mint 2.6.38-8-generic x86_64


8

strip --strip-unneededopera solo sulla tabella dei simboli del tuo eseguibile. In realtà non rimuove alcun codice eseguibile.

Le librerie standard ottengono il risultato che cerchi suddividendo tutte le loro funzioni in file oggetto separati, che vengono combinati utilizzando ar. Se poi colleghi l'archivio risultante come una libreria (es. Dai l'opzione -l your_librarya ld) allora ld includerà solo i file oggetto, e quindi i simboli, che sono effettivamente usati.

Potresti anche trovare alcune delle risposte a questa domanda simile di utilizzo.


2
I file oggetto separati nella libreria sono rilevanti solo quando si esegue un collegamento statico. Con le librerie condivise, l'intera libreria viene caricata, ma non inclusa nell'eseguibile, ovviamente.
Jonathan Leffler

4

Non so se questo ti aiuterà con la tua situazione attuale poiché si tratta di una funzionalità recente, ma puoi specificare la visibilità dei simboli in modo globale. Passare -fvisibility=hidden -fvisibility-inlines-hiddenalla compilazione può aiutare il linker a sbarazzarsi successivamente dei simboli non necessari. Se stai producendo un eseguibile (al contrario di una libreria condivisa) non c'è più niente da fare.

Ulteriori informazioni (e un approccio a grana fine per es. Le librerie) sono disponibili sul wiki di GCC .


4

Dal manuale GCC 4.2.1, sezione -fwhole-program:

Si supponga che l'unità di compilazione corrente rappresenti l'intero programma in fase di compilazione. Tutte le funzioni e variabili pubbliche, ad eccezione di maine quelle unite per attributo, externally_visiblediventano funzioni statiche e in un affetto vengono ottimizzate in modo più aggressivo dagli ottimizzatori interprocedurali. Sebbene questa opzione sia equivalente all'uso corretto della staticparola chiave per programmi costituiti da un singolo file, in combinazione con l'opzione --combinequesto flag può essere utilizzato per compilare la maggior parte dei programmi C su scala ridotta poiché le funzioni e le variabili diventano locali per l'intera unità di compilazione combinata, non per il singolo file sorgente stesso.


Sì, ma presumibilmente non funziona con nessun tipo di compilazione incrementale e probabilmente sarà un po 'lento.
Timmmm

@ Timmmm: sospetto che tu stia pensando -flto.
Ben Voigt

Sì! Successivamente ho scoperto che (perché non è nessuna delle risposte?). Sfortunatamente sembrava un po 'buggato, quindi lo consiglierei solo per la build finale e poi testarla molto!
Timmmm

-1

Puoi usare lo strip binary sul file oggetto (es. Eseguibile) per rimuovere tutti i simboli da esso.

Nota: cambia il file stesso e non crea una copia.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.