¿Cómo eliminar los símbolos C / C ++ no utilizados con GCC y ld?

110

Necesito optimizar el tamaño de mi ejecutable severamente ( ARMdesarrollo) y noté que en mi esquema de compilación actual ( gcc+ ld) los símbolos no utilizados no se eliminan.

El uso de arm-strip --strip-unneededpara los ejecutables / bibliotecas resultantes no cambia el tamaño de salida del ejecutable (no tengo idea de por qué, tal vez simplemente no pueda) .

¿Cuál sería la forma (si existe) de modificar la tubería de mi edificio, de modo que los símbolos no utilizados se eliminen del archivo resultante?


Ni siquiera pensaría en esto, pero mi entorno embebido actual no es muy "poderoso" y ahorrar incluso 500Klos 2Mresultados en un aumento de rendimiento de carga muy bueno.

Actualizar:

Por desgracia, la actual gccutilización versión que no tiene la -dead-stripopción y el -ffunction-sections... + --gc-sectionsde ldno dar ninguna diferencia significativa para los productos resultantes.

Me sorprende que esto incluso se convierta en un problema, porque estaba seguro de que gcc + lddebería eliminar automáticamente los símbolos no utilizados (¿por qué tienen que conservarlos?).

Yippie-Ki-Yay
fuente
¿Cómo sabes que no se utilizan símbolos?
zvrba
No se hace referencia en ninguna parte => no se utiliza en la aplicación final. Supongo que construir un gráfico de llamadas mientras se compila / enlaza no debería ser muy difícil.
Yippie-Ki-Yay
1
¿Está tratando de reducir el tamaño del archivo .o eliminando los símbolos muertos , o está tratando de reducir el tamaño del código real una vez cargado en la memoria ejecutable? El hecho de que diga "incrustado" sugiere lo último; la pregunta que hace parece centrada en la primera.
Ira Baxter
@Ira Estoy tratando de reducir el tamaño del ejecutable de salida, porque (como ejemplo) si intento migrar algunas aplicaciones existentes, que usan boostbibliotecas, el .exearchivo resultante contiene muchos archivos de objetos no utilizados y debido a las especificaciones de mi tiempo de ejecución incrustado actual , iniciar una 10mbaplicación lleva mucho más tiempo que, por ejemplo, iniciar una 500kaplicación.
Yippie-Ki-Yay
8
@Yippie: Quiere deshacerse del código para minimizar el tiempo de carga; el código del que desea deshacerse son métodos no utilizados / etc. de bibliotecas. Sí, necesita crear un gráfico de llamadas para hacer esto. No es tan fácil; Tiene que ser un gráfico de llamadas global, tiene que ser conservador (no se puede eliminar algo que podría usarse) y tiene que ser preciso (para que tenga lo más cercano a un gráfico de llamadas ideal, para que realmente sepa lo que no es usado). El gran problema es hacer un gráfico de llamadas preciso y global. No conozco muchos compiladores que hagan esto, y mucho menos enlazadores.
Ira Baxter

Respuestas:

131

Para GCC, esto se logra en dos etapas:

Primero compile los datos pero dígale al compilador que separe el código en secciones separadas dentro de la unidad de traducción. Esto se hará para funciones, clases y variables externas mediante el uso de los siguientes dos indicadores del compilador:

-fdata-sections -ffunction-sections

Vincule las unidades de traducción juntas usando la marca de optimización del vinculador (esto hace que el vinculador descarte las secciones sin referencia):

-Wl,--gc-sections

Entonces, si tiene un archivo llamado test.cpp que tiene dos funciones declaradas en él, pero una de ellas no está en uso, puede omitir la que no se usa con el siguiente comando para gcc (g ++):

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

(Tenga en cuenta que -Os es una marca de compilador adicional que le dice a GCC que optimice el tamaño)

JT
fuente
3
Tenga en cuenta que esto ralentizará el ejecutable según las descripciones de las opciones de GCC (probé).
metamorfosis
1
Con mingwesto no funciona al vincular estáticamente estáticamente libstdc ++ y libgcc con la bandera -static. La opción del vinculador -strip-allayuda bastante, pero aún así el ejecutable generado (o dll) es aproximadamente 4 veces más grande de lo que generaría Visual Studio. El punto es que no tengo control sobre cómo libstdc++se compiló. Debería haber una ldúnica opción.
Fabio
34

Si hay que creer en este hilo , debe proporcionar -ffunction-sectionsy-fdata-sections a gcc, que colocará cada función y objeto de datos en su propia sección. Luego le da y --gc-sectionsa GNU ld para eliminar las secciones no utilizadas.

Nemo
fuente
6
@MSalters: no es el predeterminado, porque viola los estándares C y C ++. De repente, la inicialización global no ocurre, lo que resulta en algunos programadores muy sorprendidos.
Ben Voigt
1
@MSalters: solo si pasa las opciones de ruptura de comportamiento no estándar, que propuso para hacer el comportamiento predeterminado.
Ben Voigt
1
@MSalters: Si puede hacer un parche que ejecute inicializadores estáticos si y solo si los efectos secundarios son necesarios para el correcto funcionamiento del programa, sería fantástico. Desafortunadamente, creo que hacerlo perfectamente a menudo requiere resolver el problema de la detención, por lo que probablemente deba equivocarse al incluir algunos símbolos adicionales en ocasiones. Que es básicamente lo que dice Ira en sus comentarios a la pregunta. (Por cierto: "no es necesario para el funcionamiento correcto del programa" es una definición diferente de "no utilizado" a la forma en que se utiliza ese término en los estándares)
Ben Voigt
2
@BenVoigt en C, la inicialización global no puede tener efectos secundarios (los inicializadores deben ser expresiones constantes)
MM
2
@Matt: Pero eso no es cierto en C ++ ... y comparten el mismo enlazador.
Ben Voigt
25

Querrá verificar sus documentos para su versión de gcc & ld:

Sin embargo, para mí (OS X gcc 4.0.1) encuentro estos para ld

-dead_strip

Elimine las funciones y los datos a los que no se puede acceder mediante el punto de entrada o los símbolos exportados.

-dead_strip_dylibs

Elimine dylibs a los que no se pueda acceder mediante el punto de entrada o los símbolos exportados. Es decir, suprime la generación de comandos de comando de carga para dylibs que no proporcionaron símbolos durante el enlace. Esta opción no debe usarse cuando se vincula contra un dylib que se requiere en tiempo de ejecución por alguna razón indirecta, como que el dylib tiene un inicializador importante.

Y esta útil opción

-why_live symbol_name

Registra una cadena de referencias a symbol_name. Solo aplicable con -dead_strip. Puede ayudar a depurar por qué no se elimina algo que usted cree que debería eliminarse.

También hay una nota en el gcc / g ++ man de que ciertos tipos de eliminación de código muerto solo se realizan si la optimización está habilitada al compilar.

Si bien estas opciones / condiciones pueden no ser válidas para su compilador, le sugiero que busque algo similar en sus documentos.

Michael Anderson
fuente
Esto parece no hacer nada con mingw.
Fabio
-dead_stripno es una gccopción.
ar2015
20

Los hábitos de programación también podrían ayudar; por ejemplo, agregar staticfunciones a las que no se accede fuera de un archivo específico; use nombres más cortos para los símbolos (puede ayudar un poco, probablemente no demasiado); utilizar const char x[]siempre que sea posible; ... este documento , aunque habla de objetos dinámicos compartidos, puede contener sugerencias que, si se siguen, pueden ayudar a reducir el tamaño de salida binaria final (si su objetivo es ELF).

ShinTakezou
fuente
4
¿Cómo ayuda elegir nombres más cortos para los símbolos?
fuz
1
si los símbolos no se eliminan, ça va sans dire, pero parece que hay que decirlo ahora.
ShinTakezou
@fuz El artículo habla de objetos compartidos dinámicos (por ejemplo, .soen Linux), por lo que los nombres de los símbolos deben conservarse para que las API como el ctypesmódulo FFI de Python puedan usarlos para buscar símbolos por nombre en tiempo de ejecución.
ssokolow
18

La respuesta es -flto. Tienes que pasarlo a los pasos de compilación y enlace, de lo contrario no hace nada.

De hecho, funciona muy bien: ¡redujo el tamaño de un programa de microcontrolador que escribí a menos del 50% de su tamaño anterior!

Desafortunadamente, parecía un poco defectuoso: tuve casos de cosas que no se construyeron correctamente. Puede que se deba al sistema de compilación que estoy usando (QBS; es muy nuevo), pero en cualquier caso, te recomiendo que solo lo habilites para tu compilación final si es posible, y que pruebes esa compilación a fondo.

Timmmm
fuente
1
"-Wl, - gc-section" no funciona en MinGW-W64, "-flto" me funciona. Gracias
rhbc73
El ensamblaje de salida es muy extraño -flto, no entiendo lo que hace detrás de escena.
ar2015
Creo que con -fltoél no compila cada archivo en ensamblador, los compila en LLVM IR, y luego el enlace final los compila como si estuvieran todos en una unidad de compilación. Eso significa que puede eliminar las funciones no utilizadas y las que no staticson en línea , y probablemente también otras cosas. Ver llvm.org/docs/LinkTimeOptimization.html
Timmmm
13

Si bien no se trata estrictamente de símbolos, si se busca tamaño, siempre compile con -Osy -sflags. -Osoptimiza el código resultante para el tamaño mínimo del ejecutable y -selimina la tabla de símbolos y la información de reubicación del ejecutable.

A veces, si se desea un tamaño pequeño, jugar con diferentes indicadores de optimización puede, o no, tener importancia. Por ejemplo, alternar -ffast-mathy / o -fomit-frame-pointera veces puede ahorrarle incluso docenas de bytes.

zxcdw
fuente
La mayoría de los ajustes de optimización seguirán produciendo el código correcto siempre que cumpla con el estándar del lenguaje, pero he -ffast-mathcausado estragos en el código C ++ completamente compatible con los estándares, por lo que nunca lo recomendaría.
Raptor007
11

Me parece que la respuesta que da Nemo es la correcta. Si esas instrucciones no funcionan, el problema puede estar relacionado con la versión de gcc / ld que estás usando. Como ejercicio, compilé un programa de ejemplo usando las instrucciones detalladas aquí.

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

Luego compilé el código usando interruptores de eliminación de código muerto progresivamente más agresivos:

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

Estos parámetros de compilación y vinculación produjeron ejecutables de tamaño 8457, 8164 y 6160 bytes, respectivamente, la contribución más sustancial proveniente de la declaración 'strip-all'. Si no puede producir reducciones similares en su plataforma, entonces tal vez su versión de gcc no admita esta funcionalidad. Estoy usando gcc (4.5.2-8ubuntu4), ld (2.21.0.20110327) en Linux Mint 2.6.38-8-generic x86_64

Gearoid Murphy
fuente
8

strip --strip-unneededsolo opera en la tabla de símbolos de su ejecutable. En realidad, no elimina ningún código ejecutable.

Las bibliotecas estándar logran el resultado que busca al dividir todas sus funciones en archivos de objetos separados, que se combinan usando ar. Si luego vincula el archivo resultante como una biblioteca (es decir, le da la opción -l your_librarya ld) entonces ld solo incluirá los archivos de objeto y, por lo tanto, los símbolos que se utilizan realmente.

También puede encontrar algunas de las respuestas a esta pregunta de uso similar .

Andrew Edgecombe
fuente
2
Los archivos de objetos separados en la biblioteca solo son relevantes cuando se hace un enlace estático. Con las bibliotecas compartidas, se carga toda la biblioteca, pero no se incluye en el ejecutable, por supuesto.
Jonathan Leffler
4

No sé si esto ayudará con su situación actual, ya que se trata de una característica reciente, pero puede especificar la visibilidad de los símbolos de manera global. Pasar la -fvisibility=hidden -fvisibility-inlines-hiddencompilación puede ayudar al enlazador a deshacerse más tarde de los símbolos innecesarios. Si está produciendo un ejecutable (a diferencia de una biblioteca compartida), no hay nada más que hacer.

Más información (y un enfoque detallado para, por ejemplo, bibliotecas) está disponible en la wiki de GCC .

Luc Danton
fuente
4

Del manual GCC 4.2.1, sección -fwhole-program:

Suponga que la unidad de compilación actual representa el programa completo que se está compilando. Todas las funciones y variables públicas, con la excepción de mainy las fusionadas por atributo, se externally_visibleconvierten en funciones estáticas y, en un afecto, se optimizan de forma más agresiva mediante optimizadores interprocedimientos. Si bien esta opción es equivalente al uso adecuado de la staticpalabra clave para programas que constan de un solo archivo, en combinación con la opción, --combineeste indicador se puede usar para compilar la mayoría de los programas en C de menor escala, ya que las funciones y variables se vuelven locales para toda la unidad de compilación combinada, no para el archivo de origen único en sí.

awiebe
fuente
Sí, pero eso presumiblemente no funciona con ningún tipo de compilación incremental y probablemente sea un poco lento.
Timmmm
@Timmmm: Sospecho que estás pensando -flto.
Ben Voigt
¡Si! Posteriormente encontré eso (¿por qué no es ninguna de las respuestas?). Desafortunadamente, parecía un poco defectuoso, por lo que solo lo recomendaría para la compilación final y luego probar esa compilación mucho.
Timmmm
-1

Puede utilizar eliminar binarios en un archivo objeto (por ejemplo, ejecutable) para eliminar todos los símbolos de él.

Nota: cambia el archivo en sí y no crea una copia.

ton4eg
fuente