Eu li sobre as Opções do GCC para convenções de geração de código , mas não conseguia entender o que "Gerar código independente de posição (PIC)" faz. Por favor, dê um exemplo para me explicar o que isso significa.
Eu li sobre as Opções do GCC para convenções de geração de código , mas não conseguia entender o que "Gerar código independente de posição (PIC)" faz. Por favor, dê um exemplo para me explicar o que isso significa.
Respostas:
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.
-fPIC
ao 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.
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.
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.
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 -fPIC
opção garante que o GCC produz esse código.
-fPIC
sinalizador 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?
-fPIC
sinalizador é 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.
libwotnot.so
) e vincular a ela ( -lwotnot
). Ao vincular, você não precisa se preocupar -fPIC
. Antigamente, ao criar a biblioteca compartilhada, -fPIC
era 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 '.
-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?
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.
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
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.