阅读笔记-程序员的自我修养4

4.1 空间与地址分配
目标文件“a.o”和“b.o”,输出可执行文件“ab”。
可执行文件中的代码段和数据段都是由输入的目标文件中合并而来的

4.1.2 相似段合并
第一步 空间与地址分配
所有的输入目标文件的各个段的长度、属性和位置,并且将它们合并,计算出输出文件中各个段合并后的长度与位置,并建立映射关系。
符号定义和符号引用,放到全局符号表。

第二步 符号解析与重定位
使用上面第一步中收集到的所有信息,读取输入文件中段的数据、重定位信息,进行符号解析与重定位、调整代码中的地址

在这里插入图片描述

//a.o    objdump -h a.o
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         00000027  0000000000000000  0000000000000000  00000040  2**0
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
                  ...................
//b.o   objdump -h b.o
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         0000004a  0000000000000000  0000000000000000  00000040  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 .......

在链接之前,目标文件中的所有段的VMA都是0,
.text段,a.o长度27,b.o长度4a
一起是71

//ab.o       ld a.o b.o -e main -o ab     objdump -h ab
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         00000071  00000000004000e8  00000000004000e8  000000e8  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
..................

链接后.text段长度71

4.1.3 符号地址的确定
“a.o”中的“main”函数在“.text”段的偏移是X,链接后,“a.o”的“.text”段位于虚拟地址0x08048094
那么“main”的地址应该是0x08048094 + X

4.2 符号解析与重定位
4.2.1 重定位

目标文件代码段中的起始地址以0x00000000开始,等到空间分配完成以后,各个函数才会确定自己在虚拟地址空间中的位置。

//objdump -d a.o
0000000000000000 <main>:
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   48 83 ec 10             sub    $0x10,%rsp
   8:   c7 45 fc 64 00 00 00    movl   $0x64,-0x4(%rbp)
   f:   48 8d 45 fc             lea    -0x4(%rbp),%rax
  13:   be 00 00 00 00          mov    $0x0,%esi       
  //第14字节是shared,地址用$0x0暂时表示,对应后面重定位表
  18:   48 89 c7                mov    %rax,%rdi
  1b:   e8 00 00 00 00          callq  20 <main+0x20>
  //swap的地址用00 00 00 00暂时表示
  20:   b8 00 00 00 00          mov    $0x0,%eax
  25:   c9                      leaveq
  26:   c3                      retq

“main”的起始地址为0x00000000
这两条指令的地址部分用“0x00000000”和“0xFFFFFFFC”代替,把真正的地址计算工作留给了链接器

[wangruiqi@host-10-46-52-171 LDLearn]$ objdump -d ab
00000000004000e8 <main>:
  4000e8:       55                      push   %rbp
.........
  4000fb:       be 00 10 60 00          mov    $0x601000,%esi
........
  400103:       e8 07 00 00 00          callq  40010f <swap>
  400108:       b8 00 00 00 00          mov    $0x0,%eax
  //400108+07000000(小端)=40010f 
.......................................................
.......................................................
000000000040010f <swap>:
  40010f:       55                      push   %rbp
............
  400158:       c3                      retq

main函数的两个重定位入口都已经被修正到正确的位置

4.2.2 重定位表 .rel.xxxx objdump -r a.o

//objdump -r a.o
RELOCATION RECORDS FOR [.text]:
OFFSET           TYPE              VALUE
0000000000000014 R_X86_64_32       shared         
//14表示在.text的偏移14处,需要重定向
//见objdump -d a.o
000000000000001c R_X86_64_PC32     swap-0x0000000000000004

RELOCATION RECORDS FOR [.eh_frame]:
OFFSET           TYPE              VALUE
0000000000000020 R_X86_64_PC32     .text

4.2.3 符号解析
每个目标文件定义一些符号,引用到定义在其他目标文件的符号。
重定位的入口都是对一个符号的引用,
对某个符号的引用进行重定位时,它就要确定这个符号的目标地址。
这时候链接器就会去查找由所有输入目标文件的符号表组成的全局符号表,找到相应的符号后进行重定位。

//readelf -s a.o
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 a.c
     2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1
.............................
     8: 0000000000000000    39 FUNC    GLOBAL DEFAULT    1 main
     9: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND shared
    10: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND swap

swap是UND 未定义的符号
链接器扫描完所有的输入目标文件之后,所有这些未定义的符号都应该能够在全局符号表中找到,否则报符号未定义错误。

4.2.4 指令修正方式 objdump -r a.o 重定位表的TYPE字段
? 近址寻址或远址寻址。
? 绝对寻址或相对寻址。
? 寻址长度为8位、16位、32位或64位。

4.3 COMMON块
链接器不支持符号的类型,它只知道一个符号的名字,并不知道类型是否一致
多个同名弱符号,链接后输出文件中大小,以输入文件中最大的那个为准

4.4 C++相关问题
4.4.1 重复代码消除
4.4.2 全局构造与析构
C++的全局对象构造函数也是在这一时期被执行的,我们知道C++的全局对象的构造函数在main之前被执行,C++全局对象的析构函数在main之后被执行。

.init 该段里面保存的是可执行指令,它构成了进程的初始化代码。因此,当一个程序开始运行时,在main函数被调用之前,Glibc的初始化部分安排执行这个段的中的代码。
.fini 该段保存着进程终止代码指令。
C++的全局对象构造和析构函数就由此实现

4.4.3 C++与ABI
2种格式的编译器产生的目标文件编译到一起(Application Binary Interface)

4.5 静态库链接
静态库包含的目标文件 ar -t libc.a

4.6 链接过程控制
GCC内嵌汇编

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值