Considere os dois arquivos a seguir em um sistema Linux:
use_message.cpp
#include <iostream>
extern const char* message;
void print_message();
int main() {
std::cout << message << '\n';
print_message();
}
libmessage.cpp
#include <iostream>
const char* message = "Meow!";
void print_message() {
std::cout << message << '\n';
}
Podemos compilar use_message.cpp em um arquivo objeto, compilar libmessage.cpp em uma biblioteca compartilhada e vinculá-los, assim:
$ g++ use_message.cpp -c -pie -o use_message.o
$ g++ libmessage.cpp -fPIC -shared -o libmessage.so
$ g++ use_message.o libmessage.so -o use_message
A definição de message
originalmente reside em libmessage.so . Quando use_message
é executado, o vinculador dinâmico executa realocações que:
- Atualize a
message
definição dentro de libmessage.so com o endereço de carregamento dos dados da string - Copie a definição
message
de libmessage.so para a seção use_message.bss
- Atualize a tabela de deslocamento global em libmessage.so para apontar para a nova versão de
message
dentro de use_message
As realocações relevantes, conforme descartadas por readelf
, são:
usar_mensagem
Offset Info Type Sym. Value Sym. Name + Addend
000000004150 000c00000005 R_X86_64_COPY 0000000000004150 message + 0
Esta é a realocação número 2 na lista que escrevi antes.
libmessage.so
Offset Info Type Sym. Value Sym. Name + Addend
000000004040 000000000008 R_X86_64_RELATIVE 2000
000000003fd8 000b00000006 R_X86_64_GLOB_DAT 0000000000004040 message + 0
Estes são os números de realocação 1 e 3, respectivamente.
Há uma dependência entre os números de realocação 1 e 2: a atualização para a definição de libmessage.somessage
deve acontecer antes que esse valor seja copiado para use_message , caso contrário, use_message não apontará para o local correto.
Minha pergunta é: como é especificada a ordem de aplicação das remanejamentos? Existe algo codificado nos arquivos ELF que especifica isso? Ou na ABI? Ou espera-se apenas que o vinculador dinâmico resolva as dependências entre as realocações e garanta que todas as realocações que gravam em um determinado endereço de memória sejam executadas antes de qualquer realocação que seja lida no mesmo local? O vinculador estático gera apenas realocações de modo que as do executável sempre possam ser processadas após as da biblioteca compartilhada?