链接:链接是将各种代码和数据段收集并组合为一个单一文件的过程,这个文件可被加载到内存并执行。

静态链接

为了说明什么是链接,我们可以通过例子分析

\\main.c
void swap();

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

int main(){
    swap();
    return 0;
}
\\swap.c
extern int buf[];

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

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

符号解析

首先编译器会对程序进行语法分析,找到程序的定义和引用 我们可以通过将main.cswap.c编译成.o文件得到每个符号的引用和定义

$ gcc -c swap.c
$ gcc -c main.c

可以通过readelf来查看两个文件里的符号表

$ readelf -s main.o

Symbol table '.symtab' contains 6 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 .text
     3: 0000000000000000     8 OBJECT  GLOBAL DEFAULT    3 buf
     4: 0000000000000000    25 FUNC    GLOBAL DEFAULT    1 main
     5: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND swap

$ readelf -s swap.o

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

可以看到,main.o里的swapswap.o里的buf这两个符号的Ndx属性是UND,就是undefined的。在程序里都是从外部引用但是单独编译的话编译器是没办法找到对应的定义。

重定位

既然main中的符号在swap中,swap中的符号在main中,那把两个文件合并成一个目标文件,不就解决问题了? 这就是静态链接的目的

$ ld main.o swap.o -e main -o main

-e main指定链接后程序的入口是main函数,-o main表示链接后的文件名,就得到了可执行文件main

$ readelf -s main

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 FILE    LOCAL  DEFAULT  ABS swap.c
     3: 0000000000403008     8 OBJECT  GLOBAL DEFAULT    4 bufp0
     4: 0000000000401019    67 FUNC    GLOBAL DEFAULT    2 swap
     5: 0000000000403010     0 NOTYPE  GLOBAL DEFAULT    5 __bss_start
     6: 0000000000401000    25 FUNC    GLOBAL DEFAULT    2 main
     7: 0000000000403000     8 OBJECT  GLOBAL DEFAULT    4 buf
     8: 0000000000403010     0 NOTYPE  GLOBAL DEFAULT    4 _edata
     9: 0000000000403018     0 NOTYPE  GLOBAL DEFAULT    5 _end
    10: 0000000000403010     8 OBJECT  GLOBAL DEFAULT    5 bufp1

现在mainbufswap三个符号都有,并且不再是UND 那么链接是怎么把两个二进制文件组合到一起呢 就是对应字段的组合 链接

$ objdump -h main.o

main.o:     file format elf64-x86-64

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         00000019  0000000000000000  0000000000000000  00000040  2**0
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .data         00000008  0000000000000000  0000000000000000  00000060  2**3
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000000  0000000000000000  0000000000000000  00000068  2**0
                  ALLOC
  3 .comment      0000002c  0000000000000000  0000000000000000  00000068  2**0
                  CONTENTS, READONLY
  4 .note.GNU-stack 00000000  0000000000000000  0000000000000000  00000094  2**0
                  CONTENTS, READONLY
  5 .note.gnu.property 00000020  0000000000000000  0000000000000000  00000098  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  6 .eh_frame     00000038  0000000000000000  0000000000000000  000000b8  2**3
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
$ objdump -h swap.o

swap.o:     file format elf64-x86-64

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         00000043  0000000000000000  0000000000000000  00000040  2**0
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .data         00000000  0000000000000000  0000000000000000  00000083  2**0
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000008  0000000000000000  0000000000000000  00000088  2**3
                  ALLOC
  3 .data.rel     00000008  0000000000000000  0000000000000000  00000088  2**3
                  CONTENTS, ALLOC, LOAD, RELOC, DATA
  4 .comment      0000002c  0000000000000000  0000000000000000  00000090  2**0
                  CONTENTS, READONLY
  5 .note.GNU-stack 00000000  0000000000000000  0000000000000000  000000bc  2**0
                  CONTENTS, READONLY
  6 .note.gnu.property 00000020  0000000000000000  0000000000000000  000000c0  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  7 .eh_frame     00000038  0000000000000000  0000000000000000  000000e0  2**3
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
$ objdump -h main

main:     file format elf64-x86-64

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .note.gnu.property 00000020  00000000004001c8  00000000004001c8  000001c8  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  1 .text         0000005c  0000000000401000  0000000000401000  00001000  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  2 .eh_frame     00000058  0000000000402000  0000000000402000  00002000  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  3 .data         00000010  0000000000403000  0000000000403000  00003000  2**3
                  CONTENTS, ALLOC, LOAD, DATA
  4 .bss          00000008  0000000000403010  0000000000403010  00003010  2**3
                  ALLOC
  5 .comment      0000002b  0000000000000000  0000000000000000  00003010  2**0
                  CONTENTS, READONLY

通过objdump也可以看出来main.o.text段大小为00000019,swap.o.text段大小为00000043,生成的main文件.text段大小为0000005c ,由此验证了链接的规则

目标文件的三种形式

  1. 可重定位目标文件(relocatable object file: .a(Linux); .obj(Windows))
    • 包含二进制代码和数据
    • 其形式可以在编译时与其他可重定位目标文件合并起来,创建一个可执行目标文件
  2. 可执行目标文件
    • 可以没有扩展名(Linux)或者 a.out ;.exe(Windows)
    • 可直接加载到内存
  3. 共享目标文件
    • 可以在加载或者运行时被动态地加载进内存并链接
    • windows:.dll(动态链接库);Linux:.so(动态链接)

在linux下,我们可以通过file命令来查看目标文件的形式

$ file main.o
main.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
$ file main
main: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped
$ file /lib32/libmemusage.so
/lib32/libmemusage.so: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, BuildID[sha1]=2bc3203b302795968748f6e787828793b05f6b8b, for GNU/Linux 3.2.0, stripped

sections

常用段名说明
.text编译后的机器码
.rodataread only data 只读数据(const、字符串)
.data已初始化的全局变量和局部静态变量
.bbsblock started by symbol 未初始化的全局和静态变量。只是预留一个未定义的全局变量符号
.symtab符号表:存放函数和全局变量信息
.rel.text.text节重定位信息,合成可执行文件是需要重定位的指令和指针
.rel.data.data节重定位的信息
.debug、.line调试符号表
.strtab字符串表

符号解析

符号表里的符号有一下几类

  • 定义在本目标文件的全局符号,可以被其他目标文件引用
  • 在本目标文件引用的全局符号,却没有定义在本目标文件,叫做外部符号
  • 段名,由编译器产生,值就是起始地址
  • 局部符号,只在编译单元内部可见。对于链接过程没有用,链接器也会忽略他们
  • 行号信息,目标文件指令与源代码重代码行的对应关系

弱符号与强符号

  • 强符号:函数和已初始化的全局变量
  • 弱符号:未初始化的全局变量和外部符号(extern)

符号链接的规则:

  • 规则1:不允许强符号被多次定义;如果有多个强符号定义,则链接器报符号重复定义错误
  • 规则2:如果一个符号在某个目标文件中是强符号,在其他文件中是弱符号,那么选择强符号
  • 规则3:如果一个符号在所有目标文件中都是弱符号,那么先择器其中占用空间最大的一个