程序员的自我修养第四章 静态链接笔记

第四章 静态链接

连接过程的本质就是多个不同的目标文件“粘合”到一起,为了使不同的目标文件可以相互粘合,这些目标文件必须有固定的规则才行,可以将符号看链接过程的粘合剂

  1. 可重定位文件(.o文件):

    • 可重定位文件是编译过程的中间产物,它包含了程序的机器码和符号表,但尚未确定最终的运行时地址。

    • 在可重定位文件中,所有的地址都是相对于文件开头的偏移量,而不是绝对地址。这意味着文件本身并没有分配虚拟地址空间,因为它还没有准备好被执行。

    • 系统在可重定位文件存在时不会为其分配虚拟地址空间,因为可重定位文件不是直接执行的目标。

  2. 可执行文件:

    • 可执行文件是链接过程的产物,它将多个可重定位文件(以及可能的其他资源)合并成一个单一的、可以直接由操作系统加载和执行的文件。

    • 在链接过程中,链接器会确定可执行文件中各个段(如代码段、数据段等)的位置和大小,并为它们分配虚拟地址空间。这些虚拟地址空间是逻辑上的地址,用于在程序运行时映射到物理内存(或虚拟内存)中的相应位置。

 /* a.c */
 extern int shared;
 ​
 int main(){
     int a = 100;
     swap(&a,&shared);
 /* b.c */
 int shared = 1;
 ​
 void swap(int* a,int* b){
     *a ^= *b ^= *a ^= *b;//这是位操作符异或, 二进制的数学运算。这是一种不需要临时变量就可以交换ab的方法

使用gcc将上述两个程序编译成目标文件a.o,b.o:

 $ gcc -c a.c b.c

4.1 空间与地址分配

 ld a.o b.o -e main -o ab -lc  //将a.o和b.o静态链接成可执行文件ab 

扫描所有的输入目标文件,并且获得它们的各个段的长度、属性和位置,并且将输入目标文件中的符号表中所有的符号定义和符号引用收集起来,统一放到一个全局符号表。这一步中,链接器将能够获得所有输入目标文件的段长度,并且将它们合并,计算出输出文件中各个段合并后的长度与位置,并建立映射关系(程序头表)。

有两种分配方式:

4.1.1 按序叠加 一个最简单的方案就是将输入的目标文件按照次序叠加起来,如下图所示:

在这里插入图片描述

按序叠加的优点是做法很简单,直接将各个目标文件依次合并;缺点是在有很多输入文件的情况下,输出文件将会有很多零散的段,每个段都需要有一定的地址和空间对齐要求,会造成内存空间大量的内部碎片,非常浪费空间。

4.1.2 相似段合并 一个更实际的方法是将相同性质的段合并到一起,将所有输入文件的“.text”合并到输出文件的“.text”段,接着是“.data”段、“.bss”段等,如下图所示:

在这里插入图片描述

需要注意的是,“.bss”段在目标文件和可执行文件中并不占用文件的空间,但是它在装载时占用地址空间。所以链接器在合并各个段的同时,也将“.bss”合并,并且分配虚拟空间。现在的链接器空间分配的策略基本上都采用上述方法中的第二种,使用这种方法的链接器一般都采用一种叫两步链接(Two-pass Linking) 的方法。整个链接过程分两步。

4.2 符号解析与重定位

4.2.1 符号解析

具体步骤如下

  1. 收集符号信息::链接器首先扫描所有的输入目标文件,收集它们各自符号表中的所有符号定义和符号引用信息,并将这些信息统一放入一个全局符号表中。

  2. 解析未定义符号:全局符号表中会包含一些标记为UND(未定义)的符号,这些符号在当前目标文件中被引用但未被定义。链接器的任务是找到这些未定义符号在其他目标文件或库中的定义,并确定它们的实际地址。

  3. 确定符号地址:一旦找到了未定义符号的定义,链接器就会根据这些定义来更新程序中所有引用该符号的位置的地址。

    在相似段合并后,链接器会为每一个符号计算其正确的虚拟地址。这一计算过程涉及为符号添加一个偏移量,该偏移量等于符号所在段在输出文件中的虚拟地址加上该符号在其原始段内的偏移。通过这种方式,链接器确保每个符号都能够被调整到其在最终可执行文件中的正确虚拟地址。

  4. 分配虚拟内存地址:在链接过程中,链接器还会为可执行文件中的各个段分配虚拟内存地址(VMA)。这些地址是程序在运行时占用的虚拟地址空间中的位置。对于全局符号来说,它们的地址就是它们在各自段中的偏移量加上该段的起始虚拟地址。

 $ readelf -s a.o
 ​
    Num:    Value          Size Type    Bind   Vis      Ndx Name
    ...
      4: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND shared
      5: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND swap
 $ readelf -s ab //符号表
     11: 0000000000401083    79 FUNC    GLOBAL DEFAULT   12 swap
     12: 0000000000404020     4 OBJECT  GLOBAL DEFAULT   16 shared
     ...
 $ objdump -h ab //段表
 Idx Name          Size      VMA               LMA              File off  Algn
 24 .data         00000014  0000000000004000  0000000000004000  00003000  2**3
                   CONTENTS, ALLOC, LOAD, DATA

4.2.2 重定位

反汇编结果中对 "swap","shared" 的引用语句如下

 $ objdump -d a.o
 0000000000000000 <main>:
   26:   48 8d 15 00 00 00 00    lea    0x0(%rip),%rdx        # 2d <main+0x2d>
   //`%rip`是指令指针寄存器,`0x0(%rip)`实际上是一个相对于指令指针寄存器存储的值的偏移量,这里偏移量为0。
   2d:   48 89 d6                mov    %rdx,%rsi
   ...  
   33:   e8 00 00 00 00          call   38 <main+0x38> 
   38:   b8 00 00 00 00          mov    $0x0,%eax
$ objdump -d ab
0000000000401030 <main>:
  401056:       48 8d 15 c3 2f 00 00    lea    0x2fc3(%rip),%rdx        # 404020 <shared>
  ...  
  401063:       e8 1b 00 00 00          call   401083 <swap>
  401068:       b8 00 00 00 00          mov    $0x0,%eax

0000000000401083 <swap>:  //00401068 + 000001b = 00401083

重定位表如下:

$  objdump -r a.o

a.o:     file format elf64-x86-64

RELOCATION RECORDS FOR [.text]:
OFFSET           TYPE              VALUE
0000000000000029 R_X86_64_PC32     shared-0x0000000000000004
0000000000000034 R_X86_64_PLT32    swap-0x0000000000000004
000000000000004d R_X86_64_PLT32    __stack_chk_fail-0x0000000000000004

RELOCATION RECORDS FOR [.eh_frame]:
OFFSET           TYPE              VALUE
0000000000000020 R_X86_64_PC32     .text
  1. OFFSET:表示在文件中需要修改的位置的偏移量,从节的开始处计算。

  2. TYPE:描述了重定位的类型,即如何修改OFFSET指定的位置。

  3. VALUE:提供了重定位所需的具体值或符号,用于计算最终的地址或值。

    例如,shared-0x0000000000000004表示需要引用的符号是shared,并且需要从该符号的地址中减去4来得到最终的值。

重定位表(Relocation Table)的结构专门用来保存这些与重定位相关的信息,其中每个要被重定位的地方叫一个重定位入口

  • 若代码段”.text“有需要重定位的地方,则会有一个“.rel.text”段

  • 若数据段”.data“有需要重定位的地方,则会有一个“.rel.data”段

typedef struct
{
  Elf64_Addr    r_offset;     /* Address */
  Elf64_Xword   r_info;          /* Relocation type and symbol index */
} Elf64_Rel;
  • r_offset:这是一个Elf64_Addr类型的字段,它表示了一个偏移量,这个偏移量是相对于节(section)开始的。它指定了需要被重定位的位置,即在哪里需要修改地址或数据。

  • r_info:字段是一个Elf64_Xword类型的复合字段,它包含了重定位类型和符号索引两部分信息,r_info的高位部分(表示重定位类型,而低位部分则表示符号索引。符号索引用于指向符号表中的一个条目,而重定位类型则决定了如何根据该符号来计算最终的地址或值。

  • 13
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
程序员自我修养:链接,装载与库》是一本由林锐、郭晓东、郑蕾等人合著的计算机技术书籍,在该书中,作者从程序员的视角出发,对链接、装载与库等概念进行了深入的阐述和解析。 在计算机编程中,链接是指将各个源文件中的代码模块组合成一个可执行的程序的过程。链接可以分为静态链接和动态链接两种方式。静态链接是在编译时将所有代码模块合并成一个独立的可执行文件,而动态链接是在运行时根据需要加载相应的代码模块。 装载是指将一个程序从磁盘上加载到内存中准备执行的过程。在装载过程中,操作系统会为程序分配内存空间,并将程序中的各个模块加载到相应的内存地址上。装载过程中还包括解析模块之间的引用关系,以及进行地址重定位等操作。 库是指一组可重用的代码模块,通过链接和装载的方式被程序调用。库可以分为静态库和动态库。静态库是在编译时将库的代码链接到程序中,使程序与库的代码合并为一个可执行文件。动态库则是在运行时通过动态链接的方式加载并调用。 《程序员自我修养:链接,装载与库》对于理解链接、装载和库的原理和机制具有极大的帮助。通过学习这些概念,程序员可以更好地优化代码结构和组织,提高程序的性能和可维护性。同时,了解链接、装载和库的工作原理也对于进行调试和故障排除具有重要意义。 总之,链接、装载与库是计算机编程中的重要概念,对于程序员来说掌握这些知识是非常必要的。《程序员自我修养:链接,装载与库》这本书提供了深入浅出的解释和实例,对于想要学习和掌握这些知识的程序员来说是一本非常有价值的参考书籍。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值