Princípio de vinculação do programa

Este artigo apresenta brevemente o princípio de vinculação do programa. Aprender o princípio da ligação ajuda os programadores a compreender a natureza do programa e também pode estabelecer uma base sólida para o futuro desenvolvimento de código de software em grande escala. Compreender o princípio da ligação nos ajuda a resolver alguns problemas inexplicáveis ​​no desenvolvimento diário.

Simplificando, vinculação é o processo de coletar vários códigos e dados parciais em um projeto e combiná-los em um único arquivo executável . O arquivo combinado pode ser carregado na memória para execução.

A vinculação pode ocorrer em três situações:

1. Tempo de compilação: quando o código-fonte é traduzido em código de máquina

2. Ao carregar: Quando o programa é carregado na memória e executado

3. Tempo de execução: quando o aplicativo é executado

1. Vinculação estática

1.1 Processo de compilação do programa

//示例程序1

/* /code/link/main.c */
void swap();

int buf[2] = {1, 2};

int main()
{
    swap();
    return 0;
}

/* /code/link/swap.c */
extern int buf[];

int *bufp0 = &buf[0];
int *bufp1;

void swap()
{
    int temp;
    
    bufp1 = &buf[1];
    temp = *bufp0;
    *bufp0 = *bufp1;
    *bufp1 = temp;
}

O acima é um programa simples de troca de dois números. O processo de geração de um arquivo de destino executável é o seguinte:

Pré-processador de linguagem C (cpp): traduz o programa fonte da linguagem C *.c em um arquivo intermediário de código ASCII *.i

Compilador c (ccl): traduz *.i em um arquivo de linguagem assembly de código ASCII *.s

Assembler (as): Traduza *.s em um arquivo de objeto relocável*.o

Finalmente, o programa vinculador ld combina todos os arquivos *.o e alguns arquivos de sistema necessários para criar um arquivo objeto executável.

 

 

1.2 Tarefas do vinculador

O vinculador vincula vários arquivos de objeto em um arquivo de objeto completo, carregável e executável. Sua entrada é um conjunto de arquivos de destino relocáveis. As duas tarefas principais do link são as seguintes:

1. Resolução de símbolos : vincule referências e definições de símbolos no arquivo de destino. Cada função e cada variável podem ser consideradas como um símbolo, e cada símbolo no arquivo objeto está associado à definição do símbolo.

2. Relocação : O vinculador associa a definição de cada símbolo a um local específico de memória (RAM) e, em seguida, modifica todas as referências a esses símbolos para que todos apontem para esse local de memória.

1.3 Arquivo de destino

​Três formas de arquivos de destino:

1. Arquivos de destino relocáveis

     Este tipo de arquivo contém código binário e dados que foram compilados e convertidos em código e dados de instrução de máquina, mas não podem ser executados diretamente. Como essas instruções e dados geralmente fazem referência a símbolos em outros módulos (arquivos de objetos), os símbolos desses outros módulos são desconhecidos para este módulo. A resolução desses símbolos requer que o vinculador vincule todos os módulos. Esta operação é chamada de relocação, portanto esse arquivo de destino é chamado de "arquivo de destino relocável" e o sufixo geralmente é *.o
 

 2. Arquivo de destino executável

 Esses arquivos também contêm código binário e dados. A diferença é que este arquivo foi vinculado e está vinculado a todos os módulos (arquivos objeto). O vinculador concatena todos os arquivos de objetos relocáveis ​​necessários em um arquivo de objeto executável. Neste ponto, os símbolos em cada arquivo-objeto que fazem referência a outros arquivos-objeto foram resolvidos e realocados. Portanto, cada símbolo é conhecido e o arquivo pode ser executado diretamente pela máquina.
 

3.  Compartilhe arquivos de destino

  Este é um arquivo objeto localizável especial que pode ser carregado dinamicamente na memória e executado quando o programa que precisa dele for executado ou carregado. O sufixo para esses arquivos geralmente é *.so. Arquivos de objetos compartilhados são frequentemente chamados de arquivos de “biblioteca dinâmica” ou arquivos de “biblioteca compartilhada”.

1.4 Arquivos de objetos relocáveis

Um arquivo de destino relocável típico e um arquivo executável em um ambiente Linux geralmente estão no formato ELF (Excutable Linkable File).A estrutura típica de um arquivo ELF é a seguinte:

O arquivo de destino consiste principalmente em duas partes: o cabeçalho do arquivo ELF e o segmento do arquivo de destino. Os primeiros 16 bytes do cabeçalho do arquivo ELF constituem uma ordem de bytes, descrevendo o comprimento da palavra e a ordem dos bytes do sistema de arquivos gerado. A parte restante inclui algumas outras informações sobre o arquivo ELF, incluindo o tamanho do cabeçalho do arquivo ELF, o tipo do arquivo de destino, o tipo da máquina de destino, a posição de deslocamento do arquivo da tabela de cabeçalho do segmento no arquivo de destino, etc. . Esta informação é importante ao vincular e carregar programas no formato ELF. 

/*ELF文件头*/
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x4003e0
  Start of program headers:          64 (bytes into file)
  Start of section headers:          6736 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         9
  Size of section headers:           64 (bytes)
  Number of section headers:         31
  Section header string table index: 28

Além do cabeçalho do arquivo ELF, o restante consiste em segmentos do arquivo objeto. Essas seções são a parte central do arquivo ELF. Consiste nas seguintes seções:

●.texto : segmento de código , instruções de máquina binárias armazenadas, que podem ser executadas diretamente pela máquina.

.rodata : segmento de dados somente leitura , que armazena constantes complexas usadas em programas, como strings, etc.

.data : Segmento de dados , que armazena dados globais que foram explicitamente inicializados no programa. Incluindo variáveis ​​globais e variáveis ​​estáticas em linguagem C. Se esses dados globais forem inicializados em 0, eles não serão armazenados no segmento de dados, mas no segmento de armazenamento em bloco. Variáveis ​​locais da linguagem C são armazenadas na pilha e não aparecem no segmento de dados.

.bss : segmento de armazenamento em bloco , que armazena dados globais que não foram inicializados explicitamente. Esta seção não ocupa espaço real no arquivo de destino, mas é apenas um espaço reservado para informar que o espaço para dados globais deve ser reservado no local especificado. A razão pela qual existem segmentos de armazenamento em bloco é para melhorar a utilização do espaço de armazenamento no disco.

.symtab : Tabela de símbolos , que armazena funções e variáveis ​​globais definidas e referenciadas. Deve haver uma dessas tabelas em cada arquivo de objeto relocável. Nesta tabela, todos os símbolos globais referenciados (incluindo funções e variáveis ​​globais) neste módulo e símbolos globais em outros módulos (arquivos objeto) terão um registro. A operação de relocação no link serve para determinar a localização desses símbolos globais referenciados.

.rel.text : Informação de que o segmento de código precisa ser realocado (relocate) e armazena um resumo dos símbolos que precisam ser modificados pelas operações de realocação. Esses símbolos estão no segmento de código e geralmente são um nome e um rótulo de função.

.rel.data : Informações sobre segmentos de dados que precisam ser realocados, armazenando um resumo dos símbolos que precisam ser modificados pelas operações de realocação. Esses símbolos estão no segmento de dados e são variáveis ​​globais.

.debug : informações de depuração, armazenando uma tabela de símbolos para depuração. Usar a opção -g do compilador gcc ao compilar um programa irá gerar esta seção. Esta tabela inclui as referências e definições de todos os símbolos no programa de origem. Com esta seção, você pode imprimir e observá-la ao usar o depurador gdb para depurar o programa. O valor da variável.

.line : O mapeamento do número de linha do programa de origem, que armazena o número de linha de cada instrução no programa de origem. Ao compilar um programa, usar a opção -g do compilador gcc irá gerar esta seção.Esta seção é muito útil ao depurar o programa usando o depurador gdb.

.strtab : Tabela de strings, que armazena os nomes dos símbolos na tabela de símbolos .symtab e na tabela de símbolos .debug.Esses nomes são strings e terminam com '\0'.

1.5 Símbolos e tabelas de símbolos em arquivos objeto

A resolução de símbolos é uma das principais tarefas da vinculação. Somente depois que o símbolo for analisado corretamente a localização do símbolo referenciado poderá ser alterada, completando assim a realocação e gerando um arquivo de destino executável que pode ser carregado e executado diretamente pela máquina. Cada arquivo de objeto relocável possui uma tabela de símbolos, que armazena símbolos.Esses símbolos são divididos em 3 categorias:

1. Símbolos globais definidos neste módulo

2. Símbolos globais definidos por outros módulos referenciados neste módulo

3. Símbolos locais definidos e referenciados neste módulo

Nota: Variáveis ​​locais e símbolos locais não são a mesma coisa. Variáveis ​​locais são armazenadas na pilha e são um conceito que só aparece na memória; símbolos locais incluem variáveis ​​estáticas e rótulos locais, que também podem aparecer em arquivos de disco.

Estrutura da tabela de símbolos

typedef struct{
    int name;			//目标符号的名字
    int value;			//符号的地址。对于可重定位模块:该值是距定义目标节的起始位置的偏移;
    					//			对于可执行目标文件:该值是一个绝对运行时地址。
    int size;			//目标符号的大小(字节为单位)
    char type:4;		//目标符号的类型
    char binding:4;		//目标符号是本地的还是全局的
    char reserved;		//保留
    char section;		//表示目标符号和目标文件的某个节关联(符号表中的Ndx字段)
}Elf_Symbol;

 Tabela de símbolos no programa de exemplo 1main.c

Symbol table '.symtab' contains 11 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS main.c
     2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1 
     3: 0000000000000000     0 SECTION LOCAL  DEFAULT    3 
     4: 0000000000000000     0 SECTION LOCAL  DEFAULT    4 
     5: 0000000000000000     0 SECTION LOCAL  DEFAULT    6 
     6: 0000000000000000     0 SECTION LOCAL  DEFAULT    7 
     7: 0000000000000000     0 SECTION LOCAL  DEFAULT    5 
     8: 0000000000000000     8 OBJECT  GLOBAL DEFAULT    3 buf
     9: 0000000000000000    21 FUNC    GLOBAL DEFAULT    1 main
    10: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND swap

Explicação do significado da tabela de símbolos:

**buf:** Um destino de 8 bytes localizado no deslocamento 0 (valor) na seção .data, um símbolo global

**main:** Uma função de 21 bytes localizada no deslocamento 0 na seção .text, uma função global

**troca:**Referência de troca de símbolo externo, símbolo externo

Números inteiros são usados ​​na tabela de símbolos para identificar cada seção diferente: Ndx=1 representa a seção .text; Ndx=3 representa a seção .data; ABS representa símbolos que não devem ser realocados; UNDEF representa símbolos indefinidos, ou seja, neste Símbolos referenciados no módulo de destino e definidos em outro lugar; COMMON representa um destino de dados não inicializado que ainda não recebeu um local, ou seja, uma variável estática global ou local não inicializada. LOCAL representa símbolos locais e GLOBAL representa símbolos globais.

1.6 Análise de símbolos

O vinculador resolve referências de símbolos associando cada referência a uma definição de símbolo definida na tabela de símbolos do arquivo de objeto relocável que ele insere.

1. Resolução de símbolo local

 A resolução de símbolos é muito simples para aquelas referências a símbolos locais definidos no mesmo módulo. O compilador permite apenas uma definição de cada símbolo local em cada arquivo de objeto local. Obviamente, para variáveis ​​estáticas locais, elas receberão um símbolo de vinculador local pelo compilador e terão um nome exclusivo.
 

2. Resolução global de símbolos 

 Ao resolver símbolos globais, quando o compilador encontra um símbolo (variável ou função) que não está definido no módulo atual, ele assumirá que o símbolo está definido em algum outro módulo, gerará uma tabela de entrada de símbolos do vinculador e deixará isso para o vinculador. Durante o processo subsequente de realocação do link, se o vinculador não conseguir encontrar a definição do símbolo referenciado em nenhum de seus módulos de entrada, a compilação reportará um erro.
 

 3. Regras de análise do compilador para o mesmo símbolo global definido em vários arquivos de objeto

 Regra 1: Vários símbolos fortes não são permitidos
 Regra 2: Se houver um símbolo forte e vários símbolos fracos, escolha o símbolo forte
 Regra 3: Se houver vários símbolos fracos, escolha qualquer símbolo forte destes símbolos fracos
 : Símbolo global inicializado
 Símbolo fraco: símbolo global não inicializado

 

1.7 Realocação

Quando a análise do símbolo é concluída, a posição de definição e o tamanho de cada símbolo são conhecidos. A operação de relocação requer apenas a ligação desses símbolos. Nesta etapa, o vinculador precisa mesclar todos os arquivos-objeto participantes do link e atribuir a cada símbolo um endereço de tempo de execução para armazenar o conteúdo. A realocação é realizada em duas etapas:

1. Seção de realocação e definições de símbolos

Nesta etapa, o vinculador mescla todas as seções do mesmo tipo em uma nova seção. Por exemplo, todas as seções .data no módulo de objeto de entrada serão mescladas em seções .data no arquivo de objeto executável e, em seguida, o vinculador atribuirá o endereço de memória de tempo de execução à nova seção .data. O processo das outras seções é o mesmo: quando esta etapa é concluída, cada instrução e variável global do programa possui um endereço de memória de tempo de execução exclusivo.

2. Referências de símbolos nas seções de realocação

Nesta etapa, o vinculador modifica as referências a cada símbolo nas seções de código e dados para que apontem para o endereço de memória de tempo de execução correto.

Quando o compilador gera um arquivo objeto, ele não conhece o local final de armazenamento do código e das variáveis, nem conhece os símbolos externos definidos em outros arquivos. Portanto, sempre que o montador encontra uma referência alvo cuja localização final é desconhecida, o compilador gera uma entrada de relocação que armazena informações sobre cada símbolo. Esta entrada informa ao vinculador como modificar as referências de símbolos em cada arquivo de objeto ao mesclar os arquivos de objeto. Esta entrada de realocação é armazenada no segmento **.rel.text** e no segmento .rel.data . Esta entrada pode ser entendida como uma estrutura que armazena as informações de relocação de cada símbolo.

typedef struct {
    int offset;/*偏移值*/
    int symbol;/*所代表的符号*/
    int type;/*符号的类型*/  
}symbol_rel;
/*
offset表示该符号在存储的段中的偏移值。symbol代表该符号的名称,字符串实际存储在.strtab段中,这里存储的是该字符串首地址的下标。type表示重定位类型,链接器只关心两种类型,一种是与PC相关的重定位引用,另一种是绝对地址引用。
*/

A referência de realocação relacionada ao PC significa adicionar o valor atual do PC (esse valor geralmente é o local de armazenamento da próxima instrução de salto) mais o valor de deslocamento do símbolo. Referência de endereço absoluta significa que a referência de endereço especificada na instrução atual é usada diretamente como endereço de salto sem qualquer modificação.

Com esta informação, o linker pode adicionar o valor de deslocamento do símbolo no segmento de armazenamento ao novo endereço do segmento após a relocação, obtendo assim um novo endereço de referência, e este endereço de referência é o endereço final do símbolo. Da mesma forma, todas as partes do programa que fazem referência a este endereço devem ser modificadas para usar este novo endereço absoluto em vez do antigo endereço de deslocamento. Quando o novo endereço do símbolo é modificado, o trabalho do vinculador termina.

1.8 Arquivos de objetos executáveis

​O formato de um arquivo de objeto executável (ELF):

 

O cabeçalho ELF descreve o formato geral do arquivo, que é semelhante ao formato de um arquivo de objeto relocável, mas inclui o ponto de entrada do programa.

Tabela de cabeçalho de segmento: descreve quais segmentos contíguos de memória são mapeados para fatias contíguas do arquivo executável.

.init define uma função: _init, que o código de inicialização do programa chamará.

.text, .rodata e .data são semelhantes às seções anteriores no arquivo de objeto relocável, mas essas seções foram realocadas para seu endereço de memória de tempo de execução final.

​ Exemplo de tabela de cabeçalho de segmento:

 

off: deslocamento do arquivo; vaddr: endereço virtual; paddr: endereço físico; align: alinhamento do segmento;

filesz: tamanho do segmento no arquivo de destino; memsz: tamanho do segmento na memória; flags: permissões de operação

explicar:

As linhas 1 e 2 nos dizem que o primeiro segmento (segmento de código) está alinhado a um limite de 4 KB, tem permissões de leitura/execução, começa no endereço de memória 0x08048000, o tamanho total da memória é 0x448 bytes e é inicializado São os primeiros 0x448 bytes do arquivo objeto executável, incluindo o cabeçalho ELF, a tabela de cabeçalho de segmento e as seções .init, .text e .rodata.

As linhas 3 e 4 nos dizem que o segundo segmento (segmento de dados) está alinhado a um limite de 4 KB, tem permissões de leitura/gravação, começa no endereço de memória 0x08049448, tem um tamanho total de memória de 0x104 bytes e usa os bytes 0xe8 que são inicializados a partir de no deslocamento do arquivo 0x448, que neste caso é o início da seção .data. Os bytes restantes nesta seção correspondem aos dados .bss que serão inicializados com zero em tempo de execução.

Acho que você gosta

Origin blog.csdn.net/qq_40648827/article/details/128021316
Recomendado
Clasificación