Uma biblioteca compartilhada é composta por várias seções (confira comreadelf -a libMySharedLib.so). Daquelas que são carregadas na memória destacamos duas: a seção.texte a seção.data. Para simplificar a explicação, considere que só estas duas são alocadas no espaço virtual do processo.
Nos algoritmos apresentados na seção 8.1, quatro variáveis globais foram declaradas:gA_ext,gA_int,gB_extegB_int. Como o endereço onde a biblioteca é carregada não é fixo, os endereços das variáveis globais variam de uma execução para outra. Isto implica dizer que os modos de endereçamento tradicionais (veja apêndice B.1 não são aplicáveis. A solução para este dilema é muito esperta1e utiliza o endereçamento indireto relativo ao%rip2. A idéia geral é descrita a seguir.
Quando está gerando o arquivo objeto, o ligador pode calcular uma série de informações, dentre as quais destacamos: 1. o tamanho (em bytes) da seção.text;
2. que a seção.datainicia logo após a seção.text. 1 que normalmente é sinônimo de complicada
8.2. Variáveis globais de bibliotecas compartilhadas 137
Isto significa que, se o ligador calcular que a seção.texttem0x0100bytes, então, quando for carregada a seção.data, ela iniciará0x0100bytes após o início da seção.text. Em outras palavras: sabe-se o endereço de início da seção.datarelativo ao endereço de início da seção.text.
Melhor do que isto: sabe-se o endereço de início da seção.datarelativo ao endereço de qualquer instrução da seção.text, mais especificamente, relativo ao%rip.
Isto não é muito fácil de entender de primeira, por isto vamos ilustrar esta ideia num exemplo onde: • a seção.texttem0x0100bytes e que foi carregada para o endereço0x0400.
• a variável globalvarGlobal1é a primeira da seção.data. Logo, ela está localizada no endereço0x0500. • a primeira instrução ocupa 4 bytes, e carrega a constante44paravarGlobal1.
Vamos ilustrar esta ideia. Em bibliotecas estáticas, a instrução em assembly para acessar a variável seria:
movq $44, varGlobal1
Porém, em bibliotecas compartilhadas, o endereço devarGlobal1é desconhecido quando o código assembly é gerado. Supondo que a instruçãomovq $44, varGlobal1ocupa 4 bytes, então sabe-se que%ripestá distante0x96bytes do valor atual de%rip(lembre-se que%ripjá aponta para a próxima instrução), o que sugere a instrução a seguir:
movq $44, $0x96(%rip)
Observe que se a próxima instrução também acessarvarGlobal1, então%ripserá diferente, e por isso o valor será menor do que0x96. Como regra geral, considere que a instrução será sempre algo como
movq <origem>, $<desloc>(%rip)
8.2.1
Locais à biblioteca compartilhada
O mecanismo apresentado na seção anterior funciona bem para acesso às variáveis globais internas à biblioteca porque a distân- cia entre cada instrução e a variável é conhecido em tempo de ligação.
Porém, para variáveis globais externas3, a distância entre a instrução e a seção.dataé desconhecido até o carregamento. Uma solução seria criar uma tabela defixupse atualizar os$<desloc>ao carregá-la. Há um custo para fazer estas atualizações que, dependendo do número de variáveis envolvidas, pode ser alto.
Por esta razão, uma solução mais eficiente foi implementada. Nesta solução, o “alvo” ($<desloc>(%rip)) não contém a variável, mas sim o endereço da variável.
Esta ideia está representada esquematicamente na figura 28, onde o lado direito representa a organização de uma biblioteca em memória. Vamos analisá-la em detalhes:
• A seção de código (existe uma por biblioteca) contém a instrução
movq $<desloc>(%rip), %rax
• Esta instrução coloca em%raxo endereço de uma posição fixa na biblioteca.
• Esta posição fixa não guarda o conteúdo da variável, mas sim o endereço da variável. Esta região intermediária chamare- mos de área de apontamentos4. Ela reserva um espaço por variável global. Assim, se houverem 21 variáveis globais, então esta área pode ser vista como um vetor contendo 21 endereços.
• Neste momento, o registrador%raxcontém o endereço da variável na região de dados da biblioteca. Para acessar a variá- vel, utiliza-se o endereçamento indireto, ou seja:
movq $44, (%rax)
Do ponto de vista do programador, esta abordagem é complicada. Porém, considere o ponto de vista do carregador. Ao carregar a biblioteca, ele aloca espaço para a área de apontamentos e para a área de dados. Em seguida, ele escreve - em cada po- sição da área de apontamentos - o endereço daquela variável global interna na área de dados. E após isto, o serviço do carregador acaba.
Não é preciso muito para verificar que esta solução é (normalmente) mais eficiente do que a solução de relocação usando fixups (veja exercício 8.3).
3 aquelas que estão declaradas em outro arquivo.
0x0000000000000000 0x0000080000000000 0xffffffffffffffff Segmentos do Processo Reservado ao Sistema S e g ment os D inâ mi c os
@
@
@
@
@
@
@
@
@
@
@
@
R
1
Text Data Área de Apontamentosmovq <desloc>%(rip), %rax movq 0x44, (%rax)
<desloc>
0x44 •
Figura 28 – Biblioteca compartilhada: acesso às variáveis globais internas.
8.2.2
Exportadas (GOT)
A solução apresentada na seção 8.2.1 é também aplicada para as variáveis globais externas. A diferença é que o local alocado para a variável está próximo à seção.datado segmento de processo, mais especificamente na GOT (global offset table), indicado pela seção.got.
A GOT é o local que contém as variáveis globais externas às bibliotecas, e que fica localizada na seção.datado segmento de processo. Isto pode ser visto na parte superior da figura 29, que mostra também a seção.texte.datado segmento de processo. A figura mostra, dentro da GOT, as duas variáveis globais exportadas no exemplo da seção 8.1,GA_exteGB_ext. A forma de acessar estas variáveis depende de onde se dá o acesso:
• a partir da seção.text: Esta situação está indicada em1 . Utiliza-se o deslocamento a partir de%rip(veja exercício 8.4). • a partir da biblioteca: Indicado em 2 , que encontra o endereço deGA_extem a , que por sua vez aponta paraGA_extna
GOT.
A figura também mostra como é o acesso à variávelGA_int(que só pode ser feito de dentro da biblioteca): a instrução indicada em 3 encontra o endereço deGA_intem b , que aponta para a região de dados da biblioteca.
Observe que o custo para o carregador executar os ajustes de endereço são mínimos: após alocar as variáveis na GOT e na região de dados da biblioteca, só precisa atualizar os endereços contidos na área de apontamentos.