gcc编译器的编译链接

2021-11-21  本文已影响0人  Wannay

1.了解在编译过程中链接的作用

链接是将各种代码和数据收集并组合成为一个文件的过程,最终得到的文件可以被加载到内存执行。在早期的计算机系统中,链接是手动完成的,在现代计算机系统中,链接是由链接器自动完成的。

在大型的应用程序开发过程中,不可能将所有功能实现全部都写在main.c中,而是把它拆分成为很多个更容易进行管理的模块f1.cf2.c等。当我们修改其中一个模块时,只需要重新编译该模块,别的模块不用进行重新编译。对于初学者,代码规模一般都比较小,链接一般都是由链接器默默去进行处理的,也不会觉得链接很重要,一般不需要我们自己去进行链接。那么链接有什么作用呢?

2.执行编译的步骤

比如有下面的文件sum.cmain.c

下面是sum.c

int sum(int *a, int n)
{
    int i = 0, s = 0;
    for (; i < n; i++)
    {
        s += a[i];
    }
    return s;
}

下面是文件main.c

int sum(int *a, int n);

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

int main()
{
    int val = sum(array, 2);
    return val;
}

在Linux系统中可以使用gcc -Og -o prog main.c sum.c去编译得到可执行程序prog。其中-Og表示代码优化级别为debug级别,用来告诉编译器生成的机器代码要符合原始C代码的结构目的是方便调试。在真正的程序中一般使用-O1或者是-O2优化等级,可以提高程序的性能。

编译系统一般包括如下步骤:

3.可重定位目标文件

编写如下的测试代码main.c

#include <stdio.h>

int count = 10;
int value;

void func(int sum)
{
    printf("sum is:%d\n", sum);
}

int main()
{
    static int a = 1;
    static int b = 0;
    int x = 1;
    func(x + a + b);
    return 0;
}

可重定位目标文件(.o)包括三个部分组成:ELF Header、Sections、Section Headers。

image.png

3.1 Elf Header

查看Elf Header使用的命令是readelf -h main.o

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:                              REL (Relocatable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x0
  Start of program headers:          0 (bytes into file)
  Start of section headers:          1064 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           0 (bytes)
  Number of program headers:         0
  Size of section headers:           64 (bytes)
  Number of section headers:         13
  Section header string table index: 12

我们首先来看Elf Header的第一行Magic:7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00

接着来看剩下的内容:

通过Elf Header,我们可以得到如下该文件的结构如下图所示

image.png

3.2 Section Header Table

There are 13 section headers, starting at offset 0x428:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .text             PROGBITS         0000000000000000  00000040
       0000000000000054  0000000000000000  AX       0     0     1
  [ 2] .rela.text        RELA             0000000000000000  00000318
       0000000000000078  0000000000000018   I      10     1     8
  [ 3] .data             PROGBITS         0000000000000000  00000094
       0000000000000008  0000000000000000  WA       0     0     4
  [ 4] .bss              NOBITS           0000000000000000  0000009c
       0000000000000004  0000000000000000  WA       0     0     4
  [ 5] .rodata           PROGBITS         0000000000000000  0000009c
       000000000000000b  0000000000000000   A       0     0     1
  [ 6] .comment          PROGBITS         0000000000000000  000000a7
       000000000000002a  0000000000000001  MS       0     0     1
  [ 7] .note.GNU-stack   PROGBITS         0000000000000000  000000d1
       0000000000000000  0000000000000000           0     0     1
  [ 8] .eh_frame         PROGBITS         0000000000000000  000000d8
       0000000000000058  0000000000000000   A       0     0     8
  [ 9] .rela.eh_frame    RELA             0000000000000000  00000390
       0000000000000030  0000000000000018   I      10     8     8
  [10] .symtab           SYMTAB           0000000000000000  00000130
       0000000000000198  0000000000000018          11    11     8
  [11] .strtab           STRTAB           0000000000000000  000002c8
       0000000000000049  0000000000000000           0     0     1
  [12] .shstrtab         STRTAB           0000000000000000  000003c0
       0000000000000061  0000000000000000           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),
  l (large), p (processor specific)

从这个文件中我们可以发现:

对各个section进行说明:

最终就是如下结构:

image.png

3.3 Section

使用到的命令为objdump -s -d main.o

main.o:     file format elf64-x86-64

Contents of section .text:
 0000 554889e5 4883ec10 897dfc8b 45fc89c6  UH..H....}..E...
 0010 488d3d00 000000b8 00000000 e8000000  H.=.............
 0020 0090c9c3 554889e5 4883ec10 c745fc01  ....UH..H....E..
 0030 0000008b 15000000 008b45fc 01c28b05  ..........E.....
 0040 00000000 01d089c7 e8000000 00b80000  ................
 0050 0000c9c3                             ....            
Contents of section .data:
 0000 0a000000 01000000                    ........        
Contents of section .rodata:
 0000 73756d20 69733a25 640a00             sum is:%d..     
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 01781001  .........zR..x..
 0010 1b0c0708 90010000 1c000000 1c000000  ................
 0020 00000000 24000000 00410e10 8602430d  ....$....A....C.
 0030 065f0c07 08000000 1c000000 3c000000  ._..........<...
 0040 00000000 30000000 00410e10 8602430d  ....0....A....C.
 0050 066b0c07 08000000                    .k......        

Disassembly of section .text:

0000000000000000 <func>:
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   48 83 ec 10             sub    $0x10,%rsp
   8:   89 7d fc                mov    %edi,-0x4(%rbp)
   b:   8b 45 fc                mov    -0x4(%rbp),%eax
   e:   89 c6                   mov    %eax,%esi
  10:   48 8d 3d 00 00 00 00    lea    0x0(%rip),%rdi        # 17 <func+0x17>
  17:   b8 00 00 00 00          mov    $0x0,%eax
  1c:   e8 00 00 00 00          callq  21 <func+0x21>
  21:   90                      nop
  22:   c9                      leaveq 
  23:   c3                      retq   

0000000000000024 <main>:
  24:   55                      push   %rbp
  25:   48 89 e5                mov    %rsp,%rbp
  28:   48 83 ec 10             sub    $0x10,%rsp
  2c:   c7 45 fc 01 00 00 00    movl   $0x1,-0x4(%rbp)
  33:   8b 15 00 00 00 00       mov    0x0(%rip),%edx        # 39 <main+0x15>
  39:   8b 45 fc                mov    -0x4(%rbp),%eax
  3c:   01 c2                   add    %eax,%edx
  3e:   8b 05 00 00 00 00       mov    0x0(%rip),%eax        # 44 <main+0x20>
  44:   01 d0                   add    %edx,%eax
  46:   89 c7                   mov    %eax,%edi
  48:   e8 00 00 00 00          callq  4d <main+0x29>
  4d:   b8 00 00 00 00          mov    $0x0,%eax
  52:   c9                      leaveq 
  53:   c3                      retq  

我们对重要的部分进行一下分析

3.4 符号和符号表

3.4.1 符号表

符号表是众多section中的一个(.symtab),Linux查看符号表使用到的命令是readelf -s main.o,最终得到的结果如下:

Symbol table '.symtab' contains 17 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    5 
     6: 0000000000000004     4 OBJECT  LOCAL  DEFAULT    3 a.2254
     7: 0000000000000000     4 OBJECT  LOCAL  DEFAULT    4 b.2255
     8: 0000000000000000     0 SECTION LOCAL  DEFAULT    7 
     9: 0000000000000000     0 SECTION LOCAL  DEFAULT    8 
    10: 0000000000000000     0 SECTION LOCAL  DEFAULT    6 
    11: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    3 count
    12: 0000000000000004     4 OBJECT  GLOBAL DEFAULT  COM value
    13: 0000000000000000    36 FUNC    GLOBAL DEFAULT    1 func
    14: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _GLOBAL_OFFSET_TABLE_
    15: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND printf
    16: 0000000000000024    48 FUNC    GLOBAL DEFAULT    1 main

各个字段的说明:

我们可以看到:

COMMON.bss区别:

3.4.2 符号

在链接器的上下文中存在三种不同的符号,它们分别是

4.符号解析和静态库的链接

4.1 符号解析错误-无法找到符号引用定义

#include <stdio.h>
int add(int x, int y);

int main()
{
    int x = 1, y = 2;
    int result = add(x, y);
    printf("the result is: %d", result);
    return 0;
}

当我们使用如上的代码使用gcc -c test_link_error.c进行编译和汇编时,不会有任何的错误。我们使用readelf -s test_link_error.o去查看它的符号表

Symbol table '.symtab' contains 13 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS test_link_error.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    5 
     6: 0000000000000000     0 SECTION LOCAL  DEFAULT    7 
     7: 0000000000000000     0 SECTION LOCAL  DEFAULT    8 
     8: 0000000000000000     0 SECTION LOCAL  DEFAULT    6 
     9: 0000000000000000    69 FUNC    GLOBAL DEFAULT    1 main
    10: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _GLOBAL_OFFSET_TABLE_
    11: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND add
    12: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND printf

我们发现,就算只是声明了函数add,没对它进行实现,但是它仍旧被加入了符号表中。

当我们直接使用gcc test_link_error.c -o test_link_error命令对其进行编译和链接并生成可执行文件时,却发生了引用未定义的错误。我们可以知道,这个错误发生在链接阶段,而且下面的报错也说了发生在ld(链接器)。

/tmp/ccpeaREp.o: In function `main':
test_link_error.c:(.text+0x21): undefined reference to `add'
collect2: error: ld returned 1 exit status

4.2 符号解析错误-符号被重复定义

首先我们得了解强符号和弱符号的概念:

强符号包括已经完成初始化的全局变量和函数
弱符号包括未完成初始化的全局变量

4.2.1 强符号被重复定义

#include <stdio.h>

int main()
{
    printf("hello\n");
}
#include <stdio.h>

int main()
{
    printf("world\n");
}

比如不同的文件中包含了上面的两段代码,main函数就被重复定义了多次,但是函数是强符号,因此不能正确完成链接,直接抛出错误。类似的,在不同模块中初始化同名的全局变量,也是会直接抛出错误。

4.2.2 强符号和弱符号同时出现/弱符号被重复定义

(1) example1

int x = 1234;
int main()
{
    function();
    printf("x=%d", x);
}
int x;
void function(void)
{
    x = 4567;
}

上述两段代码在不同的模块当中,其中一个定义了强符号x,另一个定义了弱符号x,则以强符号为准。但是function中对强符号进行了修改,导致main函数中打印出来的数字并不是预期的1234,而是4567,假如两个模块是不同的开发者进行开发的,就会???啥情况。

(2) example2

对于弱符号被重复定义,也会出现这种情况

int x;
int main()
{
    x = 1234;
    function();
    printf("x=%d", x);
}
int x;
void function(void)
{
    x = 4567;
}

上面就是两个弱符号的情况,也会出现这种情况。

(3) example3

这还不是最糟糕的,还有另外一种情况

int y = 3333;
int x = 1234;
int main()
{
    function();
    printf("x=%d", x);
}
double x;
void function(void)
{
    x = 0.0;
}

这种情况,double在64位系统下占用64个字节,而int占用的是4个字节,在第一个模块中y和x在内存上会存放在一起。当第二个模块中对x进行赋值0.0,不仅把x位置的内存改了,还把y位置的也给改了,造成了严重的错误。

(4) 如何避免上述的错误

为了避免这类错误,可以在编译时添加-fno-common的编译选项告诉编译器,遇到多重定义的全局符号时,触发一个错误。或者添加-Werror编译选项告诉编译器把所有的警告当做异常进行处理,编译不让通过。

4.3 静态链接库的链接

4.3.1 C中的静态链接库存放位置

了解过C语言的都知道printf是C语言提供的一个库函数,那么链接器是如何使用这一类的静态库的?

printfscanfstrcpy等函数都是定义在libc.a的文件库中。在Linux中静态库文件以archive的特殊文件格式存放在磁盘上。archive是一组可重定位目标文件(.o)的集合。

当然,也可以使用工具arlibc.a文件解压成为一个个的.o文件到当前目录,具体命令为ar -x /usr/lib/x86_64-linux-gnu/libc.a

4.3.2 如何去构建一个静态链接库

下面是vector_add.c源文件中的内容,用来实现向量的加法

void vector_add(int *x, int *y, int *z, int n)
{
    for (int i = 0; i < n; i++)
    {
        z[i] = x[i] + y[i];
    }
}

下面是vector_mul.c源文件的内容,用来实现向量的乘法

void vector_mul(int *x, int *y, int *z, int n)
{
    for (int i = 0; i < n; i++)
    {
        z[i] = x[i] * y[i];
    }
}

构建流程如下:

4.3.3 对静态链接库进行引用

创建一个vector.h文件,在里面定义vector相关的方法的声明。

void vector_mul(int *x, int *y, int *z, int n);
void vector_add(int *x, int *y, int *z, int n);

在程序main.c中去进行使用静态链接库

#include <stdio.h>
#include "vector.h"

int main()
{

    int x[2] = {1, 3};
    int y[2] = {2, 4};
    int z[2];
    vector_mul(x, y, z, 2);
    printf("z[0]=%d, z[1]=%d", z[0], z[1]);
}

使用的方式:

4.3.4 链接器的链接流程

5. 可重定位目标文件和java字节码文件对比

个人博客:http://wanna1314y.top:8090/

上一篇 下一篇

猜你喜欢

热点阅读