Como remover símbolos C / C ++ não usados ​​com GCC e ld?

110

Eu preciso otimizar o tamanho do meu executável severamente ( ARMdesenvolvimento) e notei que no meu esquema de compilação atual ( gcc+ ld) os símbolos não usados ​​não estão sendo removidos.

O uso de arm-strip --strip-unneededpara os executáveis ​​/ bibliotecas resultantes não altera o tamanho de saída do executável (não tenho ideia do motivo, talvez simplesmente não possa) .

Qual seria a maneira (se houver) de modificar meu pipeline de construção, de modo que os símbolos não utilizados sejam retirados do arquivo resultante?


Eu nem sequer penso nisso, mas meu ambiente incorporado atual não é muito "forte" e economizando ainda 500Kfora de 2Mresultados em um impulso muito agradável desempenho de carregamento.

Atualizar:

Infelizmente, a gccversão atual que uso não tem a -dead-stripopção e o -ffunction-sections... + --gc-sectionsfor ldnão dá nenhuma diferença significativa para a saída resultante.

Estou chocado que isso se tornou um problema, porque eu tinha certeza que gcc + lddeveria remover automaticamente os símbolos não utilizados (por que eles ainda têm que mantê-los?).

Yippie-Ki-Yay
fonte
Como você sabe que os símbolos não são usados?
zvrba
Não referenciado em qualquer lugar => não sendo usado na aplicação final. Presumo que construir um gráfico de chamadas enquanto compila / vincula não deve ser muito difícil.
Yippie-Ki-Yay
1
Você está tentando reduzir o tamanho do arquivo .o removendo símbolos mortos ou está tentando reduzir o tamanho da área de cobertura do código real, uma vez carregada na memória executável? O fato de você dizer "embutido" sugere o último; a pergunta que você faz parece estar focada no primeiro.
Ira Baxter
@Ira Estou tentando reduzir o tamanho do executável de saída, porque (por exemplo) se eu tentar portar alguns aplicativos existentes, que usam boostbibliotecas, o .exearquivo resultante contém muitos arquivos objetos não usados ​​e devido às especificações do meu tempo de execução incorporado atual , iniciar um 10mbaplicativo demora muito mais do que, por exemplo, iniciar um 500kaplicativo.
Yippie-Ki-Yay
8
@Yippie: Você deseja se livrar do código para minimizar o tempo de carregamento; o código do qual você deseja se livrar são métodos não utilizados / etc. de bibliotecas. Sim, você precisa construir um gráfico de chamadas para fazer isso. Não é tão fácil; tem que ser um gráfico de chamadas global, tem que ser conservador (não pode remover algo que possa ser usado) e tem que ser preciso (então você tem o mais próximo de um gráfico de chamada ideal, então você realmente sabe o que não é usava). O grande problema é fazer um gráfico de chamadas global e preciso. Não conheço muitos compiladores que fazem isso, muito menos linkers.
Ira Baxter

Respostas:

131

Para o GCC, isso é realizado em duas etapas:

Primeiro compile os dados, mas diga ao compilador para separar o código em seções separadas dentro da unidade de tradução. Isso será feito para funções, classes e variáveis ​​externas usando os dois sinalizadores de compilador a seguir:

-fdata-sections -ffunction-sections

Vincule as unidades de tradução usando o sinalizador de otimização do vinculador (isso faz com que o vinculador descarte as seções não referenciadas):

-Wl,--gc-sections

Portanto, se você tivesse um arquivo chamado test.cpp que tivesse duas funções declaradas, mas uma delas não fosse usada, você poderia omitir o não usado com o seguinte comando para gcc (g ++):

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

(Observe que -Os é um sinalizador de compilador adicional que diz ao GCC para otimizar o tamanho)

JT
fonte
3
Observe que isso tornará o executável mais lento conforme as descrições das opções do GCC (eu testei).
metamorfose
1
Com mingwisso não funciona ao vincular estaticamente estaticamente libstdc ++ e libgcc com o sinalizador -static. A opção do vinculador -strip-allajuda um pouco, mas ainda assim o executável gerado (ou dll) é cerca de 4 vezes maior do que o que o Visual Studio geraria. O que quero dizer é que não tenho controle sobre como libstdc++foi compilado. Deve haver uma ldúnica opção.
Fábio
34

Para acreditar nessa thread , você precisa fornecer o -ffunction-sectionse-fdata-sections ao gcc, que colocará cada função e objeto de dados em sua própria seção. Então você dá e --gc-sectionsao GNU ld para remover as seções não utilizadas.

Nemo
fonte
6
@MSalters: Não é o padrão, porque viola os padrões C e C ++. De repente, a inicialização global não acontece, o que resulta em alguns programadores muito surpresos.
Ben Voigt de
1
@MSalters: Somente se você passar as opções de quebra de comportamento não padrão, que você propôs para tornar o comportamento padrão.
Ben Voigt
1
@MSalters: Se você puder fazer um patch que rode inicializadores estáticos se e somente se os efeitos colaterais forem necessários para o funcionamento correto do programa, isso seria incrível. Infelizmente, acho que fazer isso com perfeição muitas vezes requer a resolução do problema da parada, então você provavelmente terá que errar ao incluir alguns símbolos extras às vezes. O que basicamente é o que Ira diz em seus comentários à pergunta. (A propósito: "não é necessário para a operação correta do programa" é uma definição diferente de "não utilizado" do que o termo usado nos padrões)
Ben Voigt
2
@BenVoigt em C, a inicialização global não pode ter efeitos colaterais (os inicializadores devem ser expressões constantes)
MM
2
@Matt: Mas isso não é verdade em C ++ ... e eles compartilham o mesmo vinculador.
Ben Voigt
25

Você vai querer verificar seus documentos para sua versão do gcc & ld:

No entanto, para mim (OS X gcc 4.0.1), acho isso para ld

-dead_strip

Remova funções e dados que não podem ser alcançados pelo ponto de entrada ou pelos símbolos exportados.

-dead_strip_dylibs

Remova dylibs que não podem ser alcançados pelo ponto de entrada ou pelos símbolos exportados. Ou seja, suprime a geração de comandos de comando de carregamento para dylibs que não forneceram símbolos durante o link. Esta opção não deve ser usada ao vincular a um dylib que é necessário em tempo de execução por alguma razão indireta, como o dylib ter um inicializador importante.

E esta opção útil

-why_live symbol_name

Registra uma cadeia de referências a symbol_name. Aplicável apenas com -dead_strip. Isso pode ajudar a depurar por que algo que você acha que deveria ser removido da tira morta não é removido.

Também há uma observação no gcc / g ++ man que certos tipos de eliminação de código morto são executados apenas se a otimização estiver habilitada durante a compilação.

Embora essas opções / condições possam não se aplicar ao seu compilador, sugiro que você procure algo semelhante em seus documentos.

Michael Anderson
fonte
Isso parece não fazer nada com mingw.
Fabio
-dead_stripnão é uma gccopção.
ar2015
20

Os hábitos de programação também podem ajudar; por exemplo, adicionar staticfunções que não são acessadas fora de um arquivo específico; use nomes mais curtos para os símbolos (pode ajudar um pouco, provavelmente não muito); use sempre const char x[]que possível; ... este artigo , embora fale sobre objetos compartilhados dinâmicos, pode conter sugestões que, se seguidas, podem ajudar a diminuir o tamanho da saída binária final (se seu destino for ELF).

ShinTakezou
fonte
4
Como ajuda escolher nomes mais curtos para os símbolos?
fuz
1
se os símbolos não forem eliminados, ça va sans dire - mas parece que isso precisava ser dito agora.
ShinTakezou
@fuz O artigo está falando sobre objetos compartilhados dinâmicos (por exemplo, .sono Linux), portanto, os nomes dos símbolos devem ser retidos para que APIs como o ctypesmódulo FFI do Python possam usá-los para procurar símbolos por nome em tempo de execução.
ssokolow
18

A resposta é -flto. Você deve passá-lo para as etapas de compilação e link, caso contrário, ele não fará nada.

Na verdade, funciona muito bem - reduziu o tamanho de um programa de microcontrolador que escrevi para menos de 50% do tamanho anterior!

Infelizmente, parecia um pouco bugado - tive casos de coisas que não foram construídas corretamente. Pode ter sido devido ao sistema de compilação que estou usando (QBS; é muito novo), mas em qualquer caso, eu recomendo que você apenas habilite-o para sua compilação final, se possível, e teste-a completamente.

Timmmm
fonte
1
"-Wl, - gc-seções" não funciona no MinGW-W64, "-flto" funciona para mim. Obrigado
rhbc73
A montagem da saída é muito estranha com -fltoeu não entendo o que ela faz nos bastidores.
ar2015
Eu acredito que com -fltoele não compila cada arquivo para o assembly, ele os compila para o LLVM IR, e então o link final os compila como se estivessem todos em uma unidade de compilação. Isso significa que pode eliminar funções não utilizadas e não umas em linha statice provavelmente outras coisas também. Consulte llvm.org/docs/LinkTimeOptimization.html
Timmmm
13

Embora não seja estritamente sobre símbolos, se for pelo tamanho - sempre compilar com os sinalizadores -Ose -s. -Osotimiza o código resultante para o tamanho mínimo do executável e -sremove a tabela de símbolos e as informações de realocação do executável.

Às vezes - se o tamanho pequeno for desejado - brincar com diferentes sinalizadores de otimização pode - ou não - ter significado. Por exemplo, alternar -ffast-mathe / ou -fomit-frame-pointerpode, às vezes, economizar até dezenas de bytes.

zxcdw
fonte
A maioria dos ajustes de otimização ainda produzirá código correto, contanto que você cumpra o padrão de linguagem, mas eu tive uma -ffast-mathconfusão em código C ++ totalmente compatível com os padrões, então eu nunca recomendaria.
Raptor007
11

Parece-me que a resposta fornecida por Nemo é a correta. Se essas instruções não funcionarem, o problema pode estar relacionado à versão do gcc / ld que você está usando, como um exercício, compilei um programa de exemplo usando as instruções detalhadas aqui

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

Em seguida, compilei o código usando interruptores de remoção de código morto progressivamente mais agressivos:

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

Esses parâmetros de compilação e vinculação produziram executáveis ​​de tamanho 8457, 8164 e 6160 bytes, respectivamente, a contribuição mais substancial proveniente da declaração 'strip-all'. Se você não puder produzir reduções semelhantes em sua plataforma, talvez sua versão do gcc não suporte esta funcionalidade. Estou usando gcc (4.5.2-8ubuntu4), ld (2.21.0.20110327) no Linux Mint 2.6.38-8-generic x86_64

Gearoid Murphy
fonte
8

strip --strip-unneededopera apenas na tabela de símbolos do seu executável. Na verdade, ele não remove nenhum código executável.

As bibliotecas padrão alcançam o resultado desejado, dividindo todas as suas funções em arquivos de objetos separados, que são combinados usando ar. Se você então vincular o arquivo resultante como uma biblioteca (ou seja, dar a opção -l your_libraryde ld), então ld incluirá apenas os arquivos objeto e, portanto, os símbolos que são realmente usados.

Você também pode encontrar algumas das respostas a esta questão semelhante de uso.

Andrew Edgecombe
fonte
2
Os arquivos de objeto separados na biblioteca são relevantes apenas ao fazer um link estático. Com as bibliotecas compartilhadas, toda a biblioteca é carregada, mas não incluída no executável, é claro.
Jonathan Leffler
4

Não sei se isso ajudará em sua situação atual, pois este é um recurso recente, mas você pode especificar a visibilidade dos símbolos de uma maneira global. Passar -fvisibility=hidden -fvisibility-inlines-hiddenna compilação pode ajudar o vinculador a se livrar de símbolos desnecessários posteriormente. Se você estiver produzindo um executável (em oposição a uma biblioteca compartilhada), não há mais nada a fazer.

Mais informações (e uma abordagem detalhada para, por exemplo, bibliotecas) estão disponíveis no wiki do GCC .

Luc Danton
fonte
4

Do manual GCC 4.2.1, seção -fwhole-program:

Suponha que a unidade de compilação atual represente o programa inteiro sendo compilado. Todas as funções e variáveis ​​públicas, com exceção de maine aquelas mescladas por atributo, externally_visibletornam-se funções estáticas e, em um efeito, são otimizadas de forma mais agressiva por otimizadores interprocedurais. Embora esta opção seja equivalente ao uso adequado de staticpalavra-chave para programas que consistem em um único arquivo, em combinação com a opção, --combineeste sinalizador pode ser usado para compilar a maioria dos programas C de escala menor, uma vez que as funções e variáveis ​​tornam-se locais para toda a unidade de compilação combinada, não para o próprio arquivo de origem único.

awiebe
fonte
Sim, mas isso provavelmente não funciona com nenhum tipo de compilação incremental e provavelmente será um pouco lento.
Timmmm
@Timmmm: Suspeito que você esteja pensando -flto.
Ben Voigt
Sim! Posteriormente, descobri isso (por que não é nenhuma das respostas?). Infelizmente parecia um pouco bugado, então eu só o recomendaria para a compilação final e depois testaria muito essa compilação!
Timmmm
-1

Você pode usar strip binário no arquivo de objeto (por exemplo, executável) para retirar todos os símbolos dele.

Nota: ele muda o próprio arquivo e não cria uma cópia.

ton4eg
fonte