编译、链接学习笔记(三)静态链接

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u013230511/article/details/77512799

什么是静态链接

代码经过编译生成目标文件后的下一步是将多个目标文件链接成一个可以执行文件。
将多个目标文件链接成一个可执行文件的过程称为静态链接。

目标文件对于外部符号的处理

单个源文件编译中当引用到外部文件的变量或者函数时(这些外部函数与变量也称为外部符号),会暂时将引用到地址以伪地址代替,等待链接时将真正引用的地址替换上。

以两个文件hello.c和world.c为例子。hello.c引用了两个外部变量,一个为系统标准输出函数printf,一个为全局变量globalInt。

//hello.c
#include <stdio.h>
extern int globalInt ;
int main()
{
  int b = doubleNumber(globalInt);
  return b;
}

//world.c
int globalInt = 4;
int doubleNumber(int a)
{
return a * a;
}

使用-c参数编译
gcc -c hello.c world.c
hello.c 与world.c 并分别输出目标文件,hello.o和world.o

再将hello.o反编译成汇编代码objdump -d hello.o
将world.o反编译成汇编代码objdump -d world.o

hello.o:     文件格式 elf64-x86-64

Disassembly of section .text:

0000000000000000 <main>:
   0:    55                       push   %rbp
   1:    48 89 e5                 mov    %rsp,%rbp
   4:    48 83 ec 10              sub    $0x10,%rsp
   8:    8b 05 00 00 00 00        mov    0x0(%rip),%eax        # e <main+0xe>
   e:    89 c7                    mov    %eax,%edi
  10:    b8 00 00 00 00           mov    $0x0,%eax
  15:    e8 00 00 00 00           callq  1a <main+0x1a>
  1a:    89 45 fc                 mov    %eax,-0x4(%rbp)
  1d:    8b 45 fc                 mov    -0x4(%rbp),%eax
  20:    c9                       leaveq 
  21:    c3                       retq   

这里需要留意两个行命令
1. 8: 8b 05 00 00 00 00 mov 0x0(%rip),%eax # e <main+0xe>
这条指令的作用是将全局变量globalInt传入doubleNumber作为参数。命令为将相对rip位置为0的内存区域,传送至eax中。 其中8b05为mov的命令码,00000000为地址。
2. 15: e8 00 00 00 00 callq 1a <main+0x1a>
这条指令的作用调用doubleNumber函数,即进行函数跳转。e8是call指令码,00000000是跳转地址

这里可以看到,无论globalInt的值还是doubleNumber函数肯定不会为0,此处编译器使用0时作为伪地址暂时填充。

world.o:     文件格式 elf64-x86-64

Disassembly of section .text:

0000000000000000 <doubleNumber>:
   0:    55                       push   %rbp
   1:    48 89 e5                 mov    %rsp,%rbp
   4:    89 7d fc                 mov    %edi,-0x4(%rbp)
   7:    8b 45 fc                 mov    -0x4(%rbp),%eax
   a:    0f af 45 fc              imul   -0x4(%rbp),%eax
   e:    5d                       pop    %rbp
   f:    c3                       retq   

地址与空间的分配策略

从上一篇文件说到,每个目标文件生成后,都会拥有自己的代码段,数据段,那么当他们进行链接后,生成的可执行文件的段结构是什么样的?
目前主流的方法是将性质相同的段进行合并,如不同目标文件的代码段,数据段都会合成同一个代码、数据段。
这里写图片描述

第一步 文件混合

合并各个目标文件中相同的段,并且将所有目标文件的符号表中的符号统一放置到全局的符号表中。

第二步 符号解析与重定位

读取段中的数据,重定位信息,调整代码中的地址。

使用ld连接器将hello.o 和 world.o进行链接

ld hello.o world.o -e main -o helloworld

使用objdump -h hello.o 查看链接前hello.o的各个段的属性

Idx Name          Size      VMA               LMA               File off  Algn
 0 .text         00000022  0000000000000000  0000000000000000  00000040  2**0
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
 1 .data         00000000  0000000000000000  0000000000000000  00000062  2**0
                  CONTENTS, ALLOC, LOAD, DATA
 2 .bss          00000000  0000000000000000  0000000000000000  00000062  2**0
                  ALLOC
 3 .comment      00000035  0000000000000000  0000000000000000  00000062  2**0
                  CONTENTS, READONLY
 4 .note.GNU-stack 00000000  0000000000000000  0000000000000000  00000097  2**0
                  CONTENTS, READONLY
 5 .eh_frame     00000038  0000000000000000  0000000000000000  00000098  2**3
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA

使用objdump -h world.o 查看链接前world.o的各个段的属性

Idx Name          Size      VMA               LMA               File off  Algn
 0 .text         00000010  0000000000000000  0000000000000000  00000040  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 1 .data         00000004  0000000000000000  0000000000000000  00000050  2**2
                  CONTENTS, ALLOC, LOAD, DATA
 2 .bss          00000000  0000000000000000  0000000000000000  00000054  2**0
                  ALLOC
 3 .comment      00000035  0000000000000000  0000000000000000  00000054  2**0
                  CONTENTS, READONLY
 4 .note.GNU-stack 00000000  0000000000000000  0000000000000000  00000089  2**0
                  CONTENTS, READONLY
 5 .eh_frame     00000038  0000000000000000  0000000000000000  00000090  2**3
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA

使用objdump -h helloworld 查看链接后helloworld的各个段的属性

Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         00000032  00000000004000e8  00000000004000e8  000000e8  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .eh_frame     00000058  0000000000400120  0000000000400120  00000120  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  2 .data         00000004  0000000000600178  0000000000600178  00000178  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  3 .comment      00000034  0000000000000000  0000000000000000  0000017c  2**0
                  CONTENTS, READONLY

可以看到链接后的helloworld文件.text段大小为0x32,为hello.o和world.o的.text大小之和。相同还有.data段也为为hello.o和world.o的.data段大小之和。
这里写图片描述

如上图看到的,其实这里的地址的分配这里有两层意思,第一层指的是链接后文件的存放的磁盘大小,第二层指的是程序装载到内存后,所占的虚拟内存区域的大小。
物理的磁盘的大小可以从File Off列和Size列共同计算得出某段的起始偏移和大小,加载后段所以在的虚拟内存偏移可以从VMA(Virtual Memory Address)计算得出。

符号解析

刚才说到,目标文件在编译时,会将外部符号的引用地址使用伪地址进行填充,那么当多个目标文件进行链接时,会符号进行解析。

再对hello.o和world.o的链接后的helloworld进行反编译

objdump -d helloworld
helloworld:     文件格式 elf64-x86-64


Disassembly of section .text:

00000000004000e8 <main>:
  4000e8:    55                       push   %rbp
  4000e9:    48 89 e5                 mov    %rsp,%rbp
  4000ec:    48 83 ec 10              sub    $0x10,%rsp
  4000f0:    8b 05 82 00 20 00        mov    0x200082(%rip),%eax   # 600178 <globalInt>
  4000f6:    89 c7                    mov    %eax,%edi
  4000f8:    b8 00 00 00 00           mov    $0x0,%eax
  4000fd:    e8 08 00 00 00           callq  40010a <doubleNumber>
  400102:    89 45 fc                 mov    %eax,-0x4(%rbp)
  400105:    8b 45 fc                 mov    -0x4(%rbp),%eax
  400108:    c9                       leaveq 
  400109:    c3                       retq   

000000000040010a <doubleNumber>:
  40010a:    55                       push   %rbp
  40010b:    48 89 e5                 mov    %rsp,%rbp
  40010e:    89 7d fc                 mov    %edi,-0x4(%rbp)
  400111:    8b 45 fc                 mov    -0x4(%rbp),%eax
  400114:    0f af 45 fc              imul   -0x4(%rbp),%eax
  400118:    5d                       pop    %rbp
  400119:    c3                       retq   

再次看文件开头时指出的两处命令,如今已经指明了具体的值。

4000f0:    8b 05 82 00 20 00        mov    0x200082(%rip),%eax   # 600178 <globalInt>

指出globalInt的位置为0x600178。

4000fd:    e8 08 00 00 00           callq  40010a <doubleNumber>

调用callq最终跳转的地址为0x040010a,也就是doubleNumber函数的位置。

用图直观一点表示
这里写图片描述

重定向符号

上面可以看到链接后生成的文件,最终的地址得到正确的修改,指向了正确的函数或者全局变量的地址。那符号是如何找到重定向到正确的地址呢?
正常有三步骤

1.从重定义表获取需重定向的符号

当生成目标文件时,如果有使用到外部定义的符号时,会将符号写入到对应的重定位段中。比如在text段中遇到外部符号,写入到.rel.text中。

查看hello.o的重定位段

objdump -r hello.o
hello.o:     文件格式 elf64-x86-64

RELOCATION RECORDS FOR [.text]:
OFFSET           TYPE              VALUE 
000000000000000a R_X86_64_PC32     globalInt-0x0000000000000004
0000000000000016 R_X86_64_PC32     doubleNumber-0x0000000000000004

2.从全局符号表中找到符号

目标文件链接成一个文件后,符号表都统一写入到全局符号表中,从中可以找到对应的符号相应的具体的地址

3.修正指令

根据指令来进行修正

小结

静态链接的大体过程可以分为两大阶段,1.目标文件的合并,2.符号的解析与重定向

没有更多推荐了,返回首页