Explicação detalhada do processo de compilação e vinculação da linguagem C

Explicação detalhada do processo de compilação e vinculação da linguagem C

Arquivo fonte

principal.c
#include <stdio.h>

extern int data;
extern int add(int a,int b);

int a1;
int a2 = 0;
int a3 = 10;

static int b1;
static int b2 = 0;
static int b3 = 20;

int main()
{
    
    
	int c1;
	int c2 = 0;
	int c3 = 30;

	static int d1;
	static int d2 = 0;
	static int d3 = 40;

	c1 = data;
	c2 = add(a1,a2);

	while(1);

	return 0;
}
adicionar.c
int data = 3;
int add(int a,int b)
{
    
    
	return a+b;
}

Dois processos principais: compilação e vinculação

1. Processo de compilação:


  1. Pré-processamento (.i)

    • Processe instruções de pré-processamento começando com #: #include #define #ifndef #if #else, etc.

    • Remova comentários, adicione números de linha, gere índices de arquivos, etc.

    Comando: gcc -E main.c -o main.i, gera arquivo .i

  2. compilar (.s)

    Compile o arquivo .i para gerar um arquivo assembly .s

    Comando: gcc -S main.i gera arquivo .s

  3. Montagem(.o)

    Traduzir arquivos assembly em arquivos relocáveis ​​de dois processos, ou seja, arquivos .o

    Comando: gcc -c main.s gera arquivo .o

PS: O comando gcc é apenas um wrapper para alguns programas em segundo plano. Ele chama outros programas de acordo com parâmetros diferentes:

  • A pré-compilação e a compilação são combinadas em uma única etapa, usando o programa cc1 , ou você pode gerar um arquivo .s através do seguinte comando

    cc1 olá.c

    Equivalente a gcc -S olá.c -o olá.s

  • montador como

  • linkerld

Analise arquivos binários relocáveis

arquivo main.c

#include <stdio.h>

int a1;
int a2 = 0;
int a3 = 10;

static int b1;
static int b2 = 0;
static int b3 = 20;

int main(void)
{
    
    
	int c1;
	int c2 = 0;
	int c3 = 30;

	static int d1;
	static int d2 = 0;
	static int d3 = 40;

	return 0;
}

Comando de compilação: compilar arquivos .o de 32 bits em uma máquina de 64 bits

* gcc -m32 -fno-PIC -c .c

-m32 especifica a compilação para gerar arquivos de 32 bits; -fno-PIC remove segmentos que são independentes da posição (deixando apenas .text.data.bss.comment, etc.)

Insira a descrição da imagem aqui

1. Leia o cabeçalho do arquivo elf
$ readelf -h main.o                                                           
ELF 头:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
  类别:                              ELF32
  数据:                              2 补码,小端序 (little endian)
  版本:                              1 (current)
  OS/ABI:                            UNIX - System V
  ABI 版本:                          0
  类型:                              REL (可重定位文件)
  系统架构:                          ARM
  版本:                              0x1
  入口点地址:               0x0
  程序头起点:          0 (bytes into file)
  Start of section headers:          268 (bytes into file)
  标志:             0x5000000, Version5 EABI
  本头的大小:       52 (字节)
  程序头大小:       0 (字节)
  Number of program headers:         0
  节头大小:         40 (字节)
  节头数量:         10
  字符串表索引节头: 7

(1) Número mágico

Magia: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00

Insira a descrição da imagem aqui

(2) REL (arquivo relocável)

(3) Endereço do ponto de entrada: 0x0

(4) Início dos cabeçalhos da seção: 268 (bytes no arquivo)

(5) Tamanho do cabeçalho: 52 (bytes)

2. Obtenha as informações dos cabeçalhos da seção do arquivo elf (para uso do link)
$ readelf -S main.o
There are 12 section headers, starting at offset 0x2ec:

节头:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .text             PROGBITS        00000000 000034 000044 00  AX  0   0  1
  [ 2] .rel.text         REL             00000000 00026c 000020 08   I  9   1  4
  [ 3] .data             PROGBITS        00000000 000078 00000c 00  WA  0   0  4
  [ 4] .bss              NOBITS          00000000 000084 000014 00  WA  0   0  4
  [ 5] .comment          PROGBITS        00000000 000084 00002a 01  MS  0   0  1
  [ 6] .note.GNU-stack   PROGBITS        00000000 0000ae 000000 00      0   0  1
  [ 7] .eh_frame         PROGBITS        00000000 0000b0 00003c 00   A  0   0  4
  [ 8] .rel.eh_frame     REL             00000000 00028c 000008 08   I  9   7  4
  [ 9] .symtab           SYMTAB          00000000 0000ec 000140 10     10  14  4
  [10] .strtab           STRTAB          00000000 00022c 000040 00      0   0  1
  [11] .shstrtab         STRTAB          00000000 000294 000057 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  p (processor specific)

Existem 12 cabeçalhos de segmento e o deslocamento inicial do cabeçalho do segmento é 0x2ec

Você pode ver o deslocamento e o tamanho de cada segmento

3. Imprima o conteúdo do segmento
~ $ objdump -s main.o

main.o:     文件格式 elf32-i386

Contents of section .text:
 0000 8d4c2404 83e4f0ff 71fc5589 e55183ec  .L$.....q.U..Q..
 0010 14c745ec 00000000 c745f01e 000000a1  ..E......E......
 0020 00000000 8945f48b 15000000 00a10000  .....E..........
 0030 000083ec 085250e8 fcffffff 83c41089  .....RP.........
 0040 45ecebfe                             E...            
Contents of section .data:
 0000 0a000000 14000000 28000000           ........(...    
Contents of section .comment:
 0000 00474343 3a202855 62756e74 7520372e  .GCC: (Ubuntu 7.
 0010 352e302d 33756275 6e747531 7e31382e  5.0-3ubuntu1~18.
 0020 30342920 372e352e 3000               04) 7.5.0.      
Contents of section .eh_frame:
 0000 14000000 00000000 017a5200 017c0801  .........zR..|..
 0010 1b0c0404 88010000 20000000 1c000000  ........ .......
 0020 00000000 44000000 00440c01 00471005  ....D....D...G..
 0030 02750043 0f03757c 06000000           .u.C..u|....
4. Leia a tabela de símbolos do arquivo .o
~ $ objdump -t main.o                                                           
main.o:     文件格式 elf32-little

SYMBOL TABLE:
00000000 l    df *ABS*	00000000 main.c
00000000 l    d  .text	00000000 .text
00000000 l    d  .data	00000000 .data
00000000 l    d  .bss	00000000 .bss
00000004 l     O .bss	00000004 b1
00000008 l     O .bss	00000004 b2
00000004 l     O .data	00000004 b3
00000008 l     O .data	00000004 d3.1881
0000000c l     O .bss	00000004 d2.1880
00000010 l     O .bss	00000004 d1.1879
00000000 l    d  .note.GNU-stack	00000000 .note.GNU-stack
00000000 l    d  .eh_frame	00000000 .eh_frame
00000000 l    d  .comment	00000000 .comment
00000004       O *COM*	00000004 a1
00000000 g     O .bss	00000004 a2
00000000 g     O .data	00000004 a3
00000000 g     F .text	00000044 main
00000000         *UND*	00000000 data
00000000         *UND*	00000000 add

Ele marca em qual segmento cada símbolo está e quanta memória ele ocupa. A1 é marcado como *COM* para indicar que é um símbolo fraco (uma variável global não estática não inicializada que pode ter o mesmo nome definido em outros arquivos).

Os dois símbolos data e add são marcados como *UND*, indicando símbolos indefinidos. A definição não pode ser encontrada neste arquivo e será encontrada em outros arquivos durante a vinculação.

5. Com base nas informações dos cabeçalhos da seção, desenhe a composição do arquivo binário relocável (arquivo .o)

Insira a descrição da imagem aqui

Pode-se descobrir que a TV via satélite inicial do segmento bss e do segmento de comentários é a mesma, mas o cálculo real mostra que o segmento bss não está armazenado no arquivo .o, mas o segmento bss é registrado na tabela de símbolos.

Conclusão: A seção bss salva variáveis ​​globais que não são inicializadas/inicializadas com 0 , e variáveis ​​locais estáticas que não são inicializadas/inicializadas com 0 , portanto, seus valores padrão são todos 0, portanto, para economizar espaço no Arquivo .o, nenhum armazenamento é necessário, mas precisa ser registrado na tabela de símbolos.Depois que o arquivo executável é finalmente executado, os símbolos do segmento bss são armazenados no espaço de endereço virtual.
Insira a descrição da imagem aqui
Insira a descrição da imagem aqui

2. Processo de vinculação:


Compilando em uma máquina x86 de 64 bits - vinculando comandos que produzem arquivos de objeto e executáveis ​​de 32 bits

编译:
	gcc -m32 -fno-PIC -c *.c
手动链接:
    ld -e main -melf_i386 *.o -o run
    
生成如下文件:
    $ ls
	add.c  add.o  main.c  main.o  run

PS:

-m32 especifica compilação para gerar arquivos de 32 bits;

-fno-PIC remove segmentos independentes da posição (deixando apenas .text.data.bss.comment, etc.)

-e especifica a entrada do programa, basta seguir -e com um símbolo, ou você pode usar a função add como a entrada do programa, ou seja, -e add

-melf_i386 especifica o link para gerar um arquivo executável de arquitetura x86 de 32 bits


A essência do processo de vinculação é "colar" vários arquivos de destino. Em essência, o que é costurado são as referências aos endereços entre os arquivos de destino, ou seja, nomes de funções e variáveis ​​globais.

A tabela de símbolos é uma seção do arquivo .o, symtab , veja o comando da tabela de símbolos

readelf -s principal.o

objdump -t principal.o

nm principal.o

O que está incluído na tabela de símbolos, focando principalmente em 1 e 2 :

    1. Símbolos globais definidos neste arquivo objeto, como nomes de variáveis, nomes de funções, etc.
    1. Os símbolos referenciados em outros arquivos de destino não são definidos neste arquivo e geralmente são chamados de símbolos externos.
    1. Nome da seção, como ".text", ".data" etc.
    1. Os símbolos locais são visíveis apenas dentro da unidade de compilação. O depurador pode usar esses símbolos para analisar o programa ou o arquivo de dump principal quando ele trava. O vinculador geralmente os ignora durante o processo de vinculação.
$ objdump -t main.o

main.o:     文件格式 elf32-i386

SYMBOL TABLE:
00000000 l    df *ABS*	00000000 main.c
00000000 l    d  .text	00000000 .text
00000000 l    d  .data	00000000 .data
00000000 l    d  .bss	00000000 .bss
00000004 l     O .bss	00000004 b1
00000008 l     O .bss	00000004 b2
00000004 l     O .data	00000004 b3
00000008 l     O .data	00000004 d3.1877
0000000c l     O .bss	00000004 d2.1876
00000010 l     O .bss	00000004 d1.1875
00000000 l    d  .note.GNU-stack	00000000 .note.GNU-stack
00000000 l    d  .eh_frame	00000000 .eh_frame
00000000 l    d  .comment	00000000 .comment
00000004       O *COM*	00000004 a1
00000000 g     O .bss	00000004 a2
00000000 g     O .data	00000004 a3
00000000 g     F .text	00000016 main
1. Mesclar segmentos de todos os arquivos .o

Insira a descrição da imagem aqui

Conforme mostrado na figura acima, quando os segmentos de texto são mesclados, os segmentos de dados são mesclados e os segmentos bss são mesclados, os símbolos fracos precisam ser convertidos em símbolos fortes (ou os símbolos fracos são substituídos por símbolos fortes) e o tamanho do bss segmento aumenta.

E após descobrir o link, cada segmento do arquivo executável gerado recebe um endereço de memória (memória virtual)

2. Mesclar tabelas de símbolos , análise de símbolos e realocação

Insira a descrição da imagem aqui

  • Mesclar tabelas de símbolos

​ Pode-se ver que a tabela de símbolos de um arquivo executável é simplesmente uma combinação das tabelas de símbolos de vários arquivos .o.

  • Análise de símbolos

Converta símbolos fracos (*COM*) em símbolos fortes

Símbolos indefinidos neste arquivo (*UND*) foram encontrados em outros arquivos

  • reiniciar

Aloque um endereço de memória virtual para o símbolo. O endereço do símbolo é calculado com base no endereço do segmento mais seu próprio deslocamento.

Análise de arquivo executável

1. Veja o cabeçalho do arquivo
$ readelf -h run
ELF 头:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
  类别:                              ELF32
  数据:                              2 补码,小端序 (little endian)
  版本:                              1 (current)
  OS/ABI:                            UNIX - System V
  ABI 版本:                          0
  类型:                              EXEC (可执行文件)
  系统架构:                          Intel 80386
  版本:                              0x1
  入口点地址:               0x80480a1
  程序头起点:          52 (bytes into file)
  Start of section headers:          4676 (bytes into file)
  标志:             0x0
  本头的大小:       52 (字节)
  程序头大小:       32 (字节)
  Number of program headers:         3
  节头大小:         40 (字节)
  节头数量:         9
  字符串表索引节头: 8

Endereço do ponto de entrada: 0x80480a1.

2. Visualize informações do segmento
$ readelf -S run
There are 9 section headers, starting at offset 0x1244:

节头:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .text             PROGBITS        08048094 000094 000051 00  AX  0   0  1
  [ 2] .eh_frame         PROGBITS        080480e8 0000e8 00005c 00   A  0   0  4
  [ 3] .data             PROGBITS        0804a000 001000 000010 00  WA  0   0  4
  [ 4] .bss              NOBITS          0804a010 001010 000018 00  WA  0   0  4
  [ 5] .comment          PROGBITS        00000000 001010 000029 01  MS  0   0  1
  [ 6] .symtab           SYMTAB          00000000 00103c 000170 10      7  14  4
  [ 7] .strtab           STRTAB          00000000 0011ac 000059 00      0   0  1
  [ 8] .shstrtab         STRTAB          00000000 001205 00003f 00      0   0  1

Cada segmento recebe um endereço virtual.

3. Veja os cabeçalhos dos programas
$ readelf -l run

Elf 文件类型为 EXEC (可执行文件)
Entry point 0x80480a1
There are 3 program headers, starting at offset 52

程序头:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x000000 0x08048000 0x08048000 0x00144 0x00144 R E 0x1000
  LOAD           0x001000 0x0804a000 0x0804a000 0x00010 0x00028 RW  0x1000
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x10

 Section to Segment mapping:
  段节...
   00     .text .eh_frame 
   01     .data .bss 
   02

Arquivos binários relocáveis ​​​​têm apenas "cabeçalhos de seção" e apenas arquivos executáveis ​​​​têm "cabeçalhos de programa" . "Cabeçalhos de programa" mostram o endereço virtual e os bytes de alinhamento de cada seção (uma página tem 4K)

Mesclar de acordo com atributos de segmento , somente leitura (texto+rodata), legível e gravável (dados+bss), etc.

Use readelf -l main para visualizar o "segmento" do ELF(para uso de carregamento)

PS: Como nós mesmos vinculamos e não vinculamos a biblioteca C, o conteúdo do parágrafo é relativamente pequeno.

* Se você executar gcc main.c -o main diretamente , a biblioteca C será vinculada por padrão e haverá muito conteúdo ao visualizar cada seção do arquivo executável.

​ * Arquivos executáveis ​​são carregados no processo por execve

​ * A razão pela qual o arquivo executável pode ser executado é porque ele especifica o endereço de entrada (principal) e os cabeçalhos do programa (especifica o endereço virtual a ser carregado)

​ * A estrutura que descreve "Segmento" é chamada de "Cabeçalho do Programa" , que descreve como o arquivo ELF deve ser mapeado para o espaço virtual do processo pelo sistema operacional.

Acho que você gosta

Origin blog.csdn.net/HuangChen666/article/details/133493602
Recomendado
Clasificación