Opção GCC -fPIC

Respostas:

525

Código independente da posição significa que o código da máquina gerado não depende de estar localizado em um endereço específico para funcionar.

Por exemplo, saltos seriam gerados como relativos e não absolutos.

Pseudo-montagem:

PIC: Isso funcionaria se o código estivesse no endereço 100 ou 1000

100: COMPARE REG1, REG2
101: JUMP_IF_EQUAL CURRENT+10
...
111: NOP

Não PIC: isso funcionará apenas se o código estiver no endereço 100

100: COMPARE REG1, REG2
101: JUMP_IF_EQUAL 111
...
111: NOP

EDIT: Em resposta ao comentário.

Se o seu código for compilado com -fPIC, é adequado para inclusão em uma biblioteca - a biblioteca deve poder ser realocada do local preferido na memória para outro endereço, pode haver outra biblioteca já carregada no endereço que sua biblioteca preferir.

Erik
fonte
36
Este exemplo é claro, mas como usuário, qual será a diferença se eu criar um arquivo labrary compartilhado (.so) sem a opção? Existem alguns casos em que, sem -fPIC, minha lib será inválida?
Narek
16
Sim, criar uma biblioteca compartilhada que não seja PIC pode ser um erro.
John Zwinck
92
Para ser mais específico, a biblioteca compartilhada deve ser compartilhada entre processos, mas nem sempre é possível carregar a biblioteca no mesmo endereço nos dois. Se o código não fosse independente de posição, cada processo exigiria sua própria cópia.
Simon Richter
19
@Narek: o erro ocorre se um processo quiser carregar mais de uma biblioteca compartilhada no mesmo endereço virtual. Como as bibliotecas não podem prever quais outras bibliotecas podem ser carregadas, esse problema é inevitável com o conceito tradicional de biblioteca compartilhada. O espaço de endereço virtual não ajuda aqui.
Philipp
6
Você pode omitir -fPICao compilar um programa ou uma biblioteca estática, porque apenas um programa principal existirá em um processo, portanto, nenhuma realocação de tempo de execução é necessária. Em alguns sistemas, os programas ainda se tornam independentes de posição para aumentar a segurança.
Simon Richter
61

Vou tentar explicar o que já foi dito de uma maneira mais simples.

Sempre que uma lib compartilhada é carregada, o carregador (o código no sistema operacional que carrega qualquer programa que você executa) altera alguns endereços no código, dependendo de onde o objeto foi carregado.

No exemplo acima, o "111" no código não PIC é gravado pelo carregador na primeira vez em que foi carregado.

Para objetos não compartilhados, convém que seja assim, porque o compilador pode fazer algumas otimizações nesse código.

Para objeto compartilhado, se outro processo desejar "vincular" esse código, ele deverá lê-lo nos mesmos endereços virtuais ou o "111" não fará sentido. mas esse espaço virtual já pode estar em uso no segundo processo.

Roee Gavirel
fonte
Whenever a shared lib is loaded, the loader changes some addresses in the code depending on where the object was loaded to.Eu acho que isso não está correto se compilado com -fpic e a razão pela qual o -fpic existe, ou seja, por motivos de desempenho ou porque você tem um carregador que não é capaz de se realocar ou porque precisa de várias cópias em locais diferentes ou por muitas outras razões.
robsn 15/04
Por que nem sempre use -fpic?
Jay
1
@ Jay - porque será necessário mais um cálculo (o endereço da função) para cada chamada de função. Portanto, quanto ao desempenho, se não for necessário, é melhor não usá-lo.
Roee Gavirel 21/04
45

O código incorporado às bibliotecas compartilhadas normalmente deve ser um código independente da posição, para que a biblioteca compartilhada possa ser carregada prontamente em (mais ou menos) qualquer endereço na memória. A -fPICopção garante que o GCC produz esse código.

Jonathan Leffler
fonte
Por que uma biblioteca compartilhada não seria carregada em nenhum endereço da memória sem ter o -fPICsinalizador ativado? não está vinculado ao programa? Quando o programa está em execução, o sistema operacional faz o upload para a memória. Estou esquecendo de algo?
Tony Tannous
1
O -fPICsinalizador é usado para garantir que essa lib possa ser carregada em qualquer endereço virtual no processo que está vinculando-o? desculpe pelos comentários duplos 5 minutos decorridos não podem editar o anterior.
Tony Tannous
1
Distinguir entre criar a biblioteca compartilhada (criar libwotnot.so) e vincular a ela ( -lwotnot). Ao vincular, você não precisa se preocupar -fPIC. Antigamente, ao criar a biblioteca compartilhada, -fPICera necessário garantir que todos os arquivos de objetos fossem construídos na biblioteca compartilhada. As regras podem ter sido alteradas porque os compiladores são construídos com código PIC por padrão hoje em dia. Então, o que era crítico há 20 anos, e pode ter sido importante há 7 anos, é menos importante atualmente, acredito. Endereços fora do kernel o / s são 'sempre' endereços virtuais '.
Jonathan Leffler
Então, anteriormente você tinha que adicionar o -fPIC. Sem passar esse sinalizador, o código gerado ao criar o .so precisa ser carregado em endereços virtuais específicos que possam estar em uso?
Tony Tannous
1
Sim, porque se você não usou o sinalizador PIC, o código não foi realocável. Coisas como ASLR (randomização do layout do espaço de endereço) não são possíveis se o código não for PIC (ou, pelo menos, for tão difícil de conseguir que é efetivamente impossível).
Jonathan Leffler
21

Adicionando mais ...

Todo processo tem o mesmo espaço de endereço virtual (se a randomização do endereço virtual for interrompida usando um sinalizador no SO Linux) (Para obter mais detalhes, desative e reative a randomização do layout do espaço de endereço apenas para mim )

Portanto, se for um exe sem vinculação compartilhada (cenário hipotético), sempre podemos atribuir o mesmo endereço virtual à mesma instrução ASM sem nenhum dano.

Mas quando queremos vincular o objeto compartilhado ao exe, não temos certeza do endereço inicial atribuído ao objeto compartilhado, pois isso dependerá da ordem em que os objetos compartilhados foram vinculados. Dito isto, asm instrução dentro .so sempre terá endereço virtual diferente, dependendo do processo ao qual está vinculado.

Portanto, um processo pode fornecer o endereço inicial para .so, como 0x45678910 em seu próprio espaço virtual, e outro processo ao mesmo tempo pode fornecer o endereço inicial de 0x12131415 e, se eles não usarem o endereço relativo, .so não funcionará.

Portanto, eles sempre precisam usar o modo de endereçamento relativo e, portanto, a opção fpic.

Ritesh
fonte
1
Obrigado pela explicação do endereço virtual.
Hot.PxL
2
Alguém pode explicar como isso não é um problema com uma biblioteca estática, por que você não precisa usar -fPIC em uma biblioteca estática? Entendo que a vinculação é feita em tempo de compilação (ou logo após, na verdade), mas se você tiver duas bibliotecas estáticas com código dependente da posição, como elas serão vinculadas?
Michael P
3
O arquivo de objeto @MichaelP possui uma tabela de rótulos dependentes da posição e, quando um arquivo obj específico está vinculado, todos os rótulos são atualizados de acordo. Isso não pode ser feito para a biblioteca compartilhada.
Slava
16

O link para uma função em uma biblioteca dinâmica é resolvido quando a biblioteca é carregada ou em tempo de execução. Portanto, o arquivo executável e a biblioteca dinâmica são carregados na memória quando o programa é executado. O endereço de memória no qual uma biblioteca dinâmica é carregada não pode ser determinado com antecedência, porque um endereço fixo pode entrar em conflito com outra biblioteca dinâmica que requer o mesmo endereço.


Existem dois métodos comumente usados ​​para lidar com esse problema:

1.Relocação. Todos os ponteiros e endereços no código são modificados, se necessário, para se ajustarem ao endereço de carregamento real. A realocação é feita pelo vinculador e pelo carregador.

2. Código independente da posição. Todos os endereços no código são relativos à posição atual. Objetos compartilhados em sistemas tipo Unix usam código independente de posição por padrão. Isso é menos eficiente que a realocação se o programa for executado por um longo período de tempo, especialmente no modo de 32 bits.


O nome " código independente da posição " realmente implica o seguinte:

  • A seção de código não contém endereços absolutos que precisam de realocação, mas apenas endereços auto-relativos. Portanto, a seção de código pode ser carregada em um endereço de memória arbitrário e compartilhada entre vários processos.

  • A seção de dados não é compartilhada entre vários processos porque geralmente contém dados graváveis. Portanto, a seção de dados pode conter ponteiros ou endereços que precisam de realocação.

  • Todas as funções públicas e dados públicos podem ser substituídos no Linux. Se uma função no executável principal tiver o mesmo nome que uma função em um objeto compartilhado, a versão no main terá precedência, não apenas quando chamada do main, mas também quando chamada do objeto compartilhado. Da mesma forma, quando uma variável global em main tiver o mesmo nome que uma variável global no objeto compartilhado, a instância em main será usada, mesmo quando acessada a partir do objeto compartilhado.


Essa chamada interposição de símbolos visa imitar o comportamento de bibliotecas estáticas.

Um objeto compartilhado possui uma tabela de ponteiros para suas funções, chamada de tabela de ligação de procedimento (PLT) e uma tabela de ponteiros para suas variáveis ​​denominadas tabela de deslocamento global (GOT), a fim de implementar esse recurso de "substituição". Todos os acessos a funções e variáveis ​​públicas passam por essas tabelas.

ps Onde a vinculação dinâmica não pode ser evitada, há várias maneiras de evitar os recursos de consumo de tempo do código independente de posição.

Você pode ler mais sobre este artigo: http://www.agner.org/optimize/optimizing_cpp.pdf

bruziuz
fonte
9

Uma pequena adição às respostas já postadas: arquivos de objetos não compilados para serem independentes de posição são realocáveis; eles contêm entradas da tabela de realocação.

Essas entradas permitem que o carregador (aquele bit de código que carrega um programa na memória) reescreva os endereços absolutos para ajustar o endereço de carga real no espaço de endereço virtual.

Um sistema operacional tentará compartilhar uma única cópia de uma "biblioteca de objetos compartilhados" carregada na memória com todos os programas vinculados à mesma biblioteca de objetos compartilhados.

Como o espaço de endereço do código (diferente das seções do espaço de dados) não precisa ser contíguo, e como a maioria dos programas vinculados a uma biblioteca específica tem uma árvore de dependência de biblioteca bastante fixa, isso ocorre com êxito na maioria das vezes. Nos casos raros em que há discrepância, sim, pode ser necessário ter duas ou mais cópias de uma biblioteca de objetos compartilhada na memória.

Obviamente, qualquer tentativa de randomizar o endereço de carregamento de uma biblioteca entre programas e / ou instâncias de programa (para reduzir a possibilidade de criar um padrão explorável) tornará esses casos comuns, não raros, portanto, quando um sistema tiver ativado esse recurso, deve-se fazer todos os esforços para compilar todas as bibliotecas de objetos compartilhados para serem independentes de posição.

Como as chamadas para essas bibliotecas do corpo do programa principal também serão tornadas realocáveis, isso torna muito menos provável que uma biblioteca compartilhada precise ser copiada.

user1016759
fonte