WangYu::Space

Study, think, create, and grow. Teach yourself and teach others.

编译与链接 - 重定位

分类:编译与链接创建时间:2023-07-23 16:11:50

在源码中,我们使用名称来访问定义于其他源文件中的符号。再目标文件中,机器指令可不能通过名称来访问符号,对符号的访问需要被替换为绝对地址或者偏移量。目标文件中,这些符号的地址自然是未知的,在链接时,所有目标文件被合并起来,链接器能获取到所有目标文件中定义的所有符号。此时所有的符号的地址都已经确定,链接器会将指令中对符号的访问地址修改为真实的地址,这个过程就叫做重定位。

例子

下面结合例子来说明重定位的原理,例子如下:

// swap.c
void swap(int *a, int *b) {
    int t = *a;
    *a = *b;
    *b = t;
}
// main.c
#include <stdio.h>

void swap(int *a, int *b);

int main() {
    int a = 10, b = 20;
    swap(&a, &b);
    printf("a: %d   b: %d\n", a, b);
    return 0;
}

main 函数中,调用了 swapprintf 两个外部定义的函数,下面观察在 main.o 中,对应的函数调用指令。

$ gcc -c main.c 
$ objdump -d main.o

main.o:     file format 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:   c7 45 fc 0a 00 00 00    movl   $0xa,-0x4(%rbp)
   f:   c7 45 f8 14 00 00 00    movl   $0x14,-0x8(%rbp)
  16:   48 8d 55 f8             lea    -0x8(%rbp),%rdx
  1a:   48 8d 45 fc             lea    -0x4(%rbp),%rax
  1e:   48 89 d6                mov    %rdx,%rsi
  21:   48 89 c7                mov    %rax,%rdi
  24:   e8 00 00 00 00          callq  29 <main+0x29>
  29:   8b 55 f8                mov    -0x8(%rbp),%edx
  2c:   8b 45 fc                mov    -0x4(%rbp),%eax
  2f:   89 c6                   mov    %eax,%esi
  31:   bf 00 00 00 00          mov    $0x0,%edi
  36:   b8 00 00 00 00          mov    $0x0,%eax
  3b:   e8 00 00 00 00          callq  40 <main+0x40>
  40:   b8 00 00 00 00          mov    $0x0,%eax
  45:   c9                      leaveq 
  46:   c3                      retq

在构造目标文件时,swapprintf 这两个函数的地址是未知的,在机器指令中,call 指令的操作数被设置为 0。

$ readelf -r main.o

Relocation section '.rela.text' at offset 0x220 contains 3 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000000025  000a00000004 R_X86_64_PLT32    0000000000000000 swap - 4
000000000032  00050000000a R_X86_64_32       0000000000000000 .rodata + 0
00000000003c  000b00000004 R_X86_64_PLT32    0000000000000000 printf - 4

......

可以看到,在 .rela.text 中有三条重定位条目,这意味着在 .text 中有三个位置需要被重定位。通过 offset 字段,可以找到要重定位的位置:

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:   c7 45 fc 0a 00 00 00    movl   $0xa,-0x4(%rbp)
   f:   c7 45 f8 14 00 00 00    movl   $0x14,-0x8(%rbp)
  16:   48 8d 55 f8             lea    -0x8(%rbp),%rdx
  1a:   48 8d 45 fc             lea    -0x4(%rbp),%rax
  1e:   48 89 d6                mov    %rdx,%rsi
  21:   48 89 c7                mov    %rax,%rdi
  24:   e8 00 00 00 00          callq  29 <main+0x29>
           ^^^^^^^^^^^
  29:   8b 55 f8                mov    -0x8(%rbp),%edx
  2c:   8b 45 fc                mov    -0x4(%rbp),%eax
  2f:   89 c6                   mov    %eax,%esi
  31:   bf 00 00 00 00          mov    $0x0,%edi
           ^^^^^^^^^^^
  36:   b8 00 00 00 00          mov    $0x0,%eax
  3b:   e8 00 00 00 00          callq  40 <main+0x40>
           ^^^^^^^^^^^
  40:   b8 00 00 00 00          mov    $0x0,%eax
  45:   c9                      leaveq 
  46:   c3                      retq

不难看出,第一个位置需要被修改为 swap 函数的真实地址,第二个位置需要指向 .rodata 段中字符串 “a: %d b: %d\n” 的位置,第三个位置则需要被修改为 printf 函数的位置。

对链接后的可执行文件 main 的代码做反汇编,内容如下:

0000000000401195 <main>:
  401195:       55                      push   %rbp
  401196:       48 89 e5                mov    %rsp,%rbp
  401199:       48 83 ec 10             sub    $0x10,%rsp
  40119d:       c7 45 fc 0a 00 00 00    movl   $0xa,-0x4(%rbp)
  4011a4:       c7 45 f8 14 00 00 00    movl   $0x14,-0x8(%rbp)
  4011ab:       48 8d 55 f8             lea    -0x8(%rbp),%rdx
  4011af:       48 8d 45 fc             lea    -0x4(%rbp),%rax
  4011b3:       48 89 d6                mov    %rdx,%rsi
  4011b6:       48 89 c7                mov    %rax,%rdi
  4011b9:       e8 1e 00 00 00          callq  4011dc <swap>
  4011be:       8b 55 f8                mov    -0x8(%rbp),%edx
  4011c1:       8b 45 fc                mov    -0x4(%rbp),%eax
  4011c4:       89 c6                   mov    %eax,%esi
  4011c6:       bf 04 20 40 00          mov    $0x402004,%edi
  4011cb:       b8 00 00 00 00          mov    $0x0,%eax
  4011d0:       e8 6b fe ff ff          callq  401040 <printf@plt>
  4011d5:       b8 00 00 00 00          mov    $0x0,%eax
  4011da:       c9                      leaveq 
  4011db:       c3                      retq

可以看到,前面提到的三个需要被重定位的位置均已被修改。

重定位原理

在编译过程中,编译器能收集到全部的外部定义的符号,并明确生成的机器指令中那些位置需要被修改。据此,编译器生成重定位条目,描述每一个需要被重定位的位置。不同的指令,其寻址方式不同,做重定位时,有的地址需要被修改为绝对地址,有的则需要被修改为偏移量。重定位条目中包含的多个字段,这些字段明确地给出了重定位的地址以及需要被改写的内容该如何计算。

链接阶段,所有符号的位置都已经固定下来了。链接器只需要读取重定位表,遍历其中的每一个重定位记录,并按描述执行重定位即可。

重定位记录

目标文件中那些符号需要重定位,如何做重定位,这些信息都存储在 ELF 文件的 .rela.* 节,可以使用 readelf -r 命令来查看重定位信息:

$ readelf -r main.o

Relocation section '.rela.text' at offset 0x220 contains 3 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000000025  000a00000004 R_X86_64_PLT32    0000000000000000 swap - 4
000000000032  00050000000a R_X86_64_32       0000000000000000 .rodata + 0
00000000003c  000b00000004 R_X86_64_PLT32    0000000000000000 printf - 4

Relocation section '.rela.eh_frame' at offset 0x268 contains 1 entry:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000000020  000200000002 R_X86_64_PC32     0000000000000000 .text + 0

上面命令输出的每一行重定位信息都使用如下结构之一来存储:

typedef struct {
  Elf64_Addr	r_offset;		/* Address */
  Elf64_Xword	r_info;	     /* Relocation type and symbol index */
} Elf64_Rel;

typedef struct {
  Elf64_Addr	r_offset;		/* Address */
  Elf64_Xword	r_info;		/* Relocation type and symbol index */
  Elf64_Sxword	r_addend;		/* Addend */
} Elf64_Rela;

这两者的区别仅仅是 Elf64_Rela 中多了一个 r_addend 字段,对某个重定位 section,其中包含的只能是 Elf64_RelElf64_Rela 中的一种,这两不会同时出现。该结构各字段说明如下:

r_offset

需要被重定位改写的地址偏移量,对于不同的 ELF 文件类型该字段的有不同的解读:

r_info

该字段中包含两部分信息:指令访问的变量或函数在符号表中的下标、重定位类型

ELF64_R_SYM(info) ((info)>>32)   // 高 32 位是符号表中的下标
ELF64_R_TYPE(info) ((Elf64_Word)(info)) // 低 32 位为重定位类型

r_addend

在计算符号重定位的位置时,该字段作为一个附加的偏移量。

重定位表

重定位记录存在于重定位表中,重定位表存在于某个以 .rela.rel 开头的 section 中,表中存放的是 Elf64_RelaElf64_Rel 结构。重定位 section 与一个符号表关联,重定位 section 的 header 中 sh_link 字段指向与之关联的符号表。

重定位 section 的 header 中 r_info 字段中包含了与该重定位条目关联的符号的下标,sh_info 字段指向真正执行重定位的 section 的 header,通过 Elf64_Rela 中的 r_offset 字段可定位到重定位的位置。

下图描述了前述各个表之间的关系:

可以使用实际例子来验证以上结论:

$ readelf -S main.o
There are 13 section headers, starting at offset 0x2f8:

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
       0000000000000026  0000000000000000  AX       0     0     1
  [ 2] .rela.text        RELA             0000000000000000  00000218
       0000000000000048  0000000000000018   I      10     1     8
  [ 3] .data             PROGBITS         0000000000000000  00000066
       0000000000000000  0000000000000000  WA       0     0     1
  [ 4] .bss              NOBITS           0000000000000000  00000066
       0000000000000000  0000000000000000  WA       0     0     1
  [ 5] .rodata           PROGBITS         0000000000000000  00000066
       000000000000000c  0000000000000000   A       0     0     1
  [ 6] .comment          PROGBITS         0000000000000000  00000072
       0000000000000012  0000000000000001  MS       0     0     1
  [ 7] .note.GNU-stack   PROGBITS         0000000000000000  00000084
       0000000000000000  0000000000000000           0     0     1
  [ 8] .eh_frame         PROGBITS         0000000000000000  00000088
       0000000000000058  0000000000000000   A       0     0     8
  [ 9] .rela.eh_frame    RELA             0000000000000000  00000260
       0000000000000030  0000000000000018   I      10     8     8
  [10] .symtab           SYMTAB           0000000000000000  000000e0
       0000000000000120  0000000000000018          11     9     8
  [11] .strtab           STRTAB           0000000000000000  00000200
       0000000000000018  0000000000000000           0     0     1
  [12] .shstrtab         STRTAB           0000000000000000  00000290
       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)

.rela.text 中存放的是 .text 中的重定位记录,.rela.text 的 Link 字段为 10,对应的是符号表 .text,info 字段为 1,对应的是 .text

重定位信息

Elf64_Rela 结构体中的 r_info 字段包含两部分信息,一个该重定位符号在符号表中的下标,第二部分信息是重定位类型。

重定位就是把指令中地址修改为真实地址,这里包含三部分信息:

  1. 需要修改的位置
  2. 需要修改多少个字节
  3. 需要修改为什么值

Elf64_Rela 结构体中的 r_offsetr_infor_addend 三个字段就提供了这部分信息。首先 r_offset 告知需要修改的位置,r_info 中的重定位类型指明了需要修改多少字节,重定位类型结合 r_addend 指明需要被修改为什么值。

不同的重定位类型,需要修改的字节数不同,且重定位时被修改的值的计算方式不同。下表列出了需要修改的字节数(Field),以及最终目标值的计算方式(Calculation)。

在 x86_64 架构上包含如下重定位类型:

NameValueFieldCalculation
R_X86_64_NONE0NoneNone
R_X86_64_641qwordS + A
R_X86_64_PC322dwordS + A – P
R_X86_64_GOT323dwordG + A
R_X86_64_PLT324dwordL + A – P
R_X86_64_COPY5NoneValue is copied directly from shared object
R_X86_64_GLOB_DAT6qwordS
R_X86_64_JUMP_SLOT7qwordS
R_X86_64_RELATIVE8qwordB + A
R_X86_64_GOTPCREL9dwordG + GOT + A – P
R_X86_64_3210dwordS + A
R_X86_64_32S11dwordS + A
R_X86_64_1612wordS + A
R_X86_64_PC1613wordS + A – P
R_X86_64_814word8S + A
R_X86_64_PC815word8S + A – P
R_X86_64_PC6424qwordS + A – P
R_X86_64_GOTOFF6425qwordS + A – GOT
R_X86_64_GOTPC3226dwordGOT + A – P
R_X86_64_SIZE3232dwordZ + A
R_X86_64_SIZE6433qwordZ + A

在 Field 一栏中,qword 表示 64 位,dword 表示 32 位,word 表示 16 位,word8 表示 8 位。Calculation 一栏中,包含由一些字母组成的计算公式,这些字母的含义如下:

符号含义备注
AThe addend used to compute the value of the relocatable field.Elf64_Rela 中的 Addend 字段的值
BThe base address at which a shared object is loaded into memory during execution. Generally, a shared object file is built with a 0 base virtual address, but the execution address is different.动态库在内存中的起始虚拟地址
GThe offset into the global offset table at which the address of the relocation entry’s symbol resides during execution.当前符号在 GOT 中的位置
GOTThe address of the global offset table.GOT 起始地址
LThe section offset or address of the procedure linkage table entry for a symbol.section 偏移量,或者该符号在 PLT 中的位置
PThe section offset or address of the storage unit being relocated, computed using r_offset.section 偏移量,或需要被重定位改写的内存地址
SThe value of the symbol whose index resides in the relocation entry.重定位符号的值(符号表中该符号的 value 字段的值)

案例分析

这里以前文中的程序为例,来分析重定位的计算流程。

// main.c
#include <stdio.h>

void swap(int *a, int *b);

int main() {
    int a = 10, b = 20;
    swap(&a, &b);
    printf("a: %d   b: %d\n", a, b);
    return 0;
}

使用 objdump -d 可以看到汇编代码:

$ objdump -d main.o

main.o:     file format 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:   c7 45 fc 0a 00 00 00    movl   $0xa,-0x4(%rbp)
   f:   c7 45 f8 14 00 00 00    movl   $0x14,-0x8(%rbp)
  16:   48 8d 55 f8             lea    -0x8(%rbp),%rdx
  1a:   48 8d 45 fc             lea    -0x4(%rbp),%rax
  1e:   48 89 d6                mov    %rdx,%rsi
  21:   48 89 c7                mov    %rax,%rdi
  24:   e8 00 00 00 00          callq  29 <main+0x29>
           ^^^^^^^^^^^
  29:   8b 55 f8                mov    -0x8(%rbp),%edx
  2c:   8b 45 fc                mov    -0x4(%rbp),%eax
  2f:   89 c6                   mov    %eax,%esi
  31:   bf 00 00 00 00          mov    $0x0,%edi
           ^^^^^^^^^^^
  36:   b8 00 00 00 00          mov    $0x0,%eax
  3b:   e8 00 00 00 00          callq  40 <main+0x40>
           ^^^^^^^^^^^
  40:   b8 00 00 00 00          mov    $0x0,%eax
  45:   c9                      leaveq 
  46:   c3                      retq

其中画波浪线的地方需要被改写,下面是重定位表的内容:

$ readelf -r main.o

Relocation section '.rela.text' at offset 0x220 contains 3 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000000025  000a00000004 R_X86_64_PLT32    0000000000000000 swap - 4
000000000032  00050000000a R_X86_64_32       0000000000000000 .rodata + 0
00000000003c  000b00000004 R_X86_64_PLT32    0000000000000000 printf - 4

这里三处需要重定位,其中涉及两种重定位类型,我们分别来看。

先看重定位类型为 R_X86_64_PLT32swap,这个重定位类型下,重定位后的值的计算公式为:L + A – P

L 实际上就是需要被调用的函数的地址,这里 call 指令采用相对偏移量来寻址,基址就是下一条指令的地址。因为 call 指令的操作数是 4 个字节,因此 P + 4 就是下条指令的地址。因此与 swap 的实际调用地址的偏移量为 L - (P + 4),前面的计算公式 L + A – P = L - (P - A) 中,而此处 A 的取值恰好就是 -4。

注意: 当你看到可执行文件 main 的反汇编指令时,会发现 swap 并未出现在 PLT 中,这是因为 swap 是被链接到可执行文件中,无需 PLT。

0000000000401195 <main>:
  401195:       55                      push   %rbp
  401196:       48 89 e5                mov    %rsp,%rbp
  401199:       48 83 ec 10             sub    $0x10,%rsp
  40119d:       c7 45 fc 0a 00 00 00    movl   $0xa,-0x4(%rbp)
  4011a4:       c7 45 f8 14 00 00 00    movl   $0x14,-0x8(%rbp)
  4011ab:       48 8d 55 f8             lea    -0x8(%rbp),%rdx
  4011af:       48 8d 45 fc             lea    -0x4(%rbp),%rax
  4011b3:       48 89 d6                mov    %rdx,%rsi
  4011b6:       48 89 c7                mov    %rax,%rdi
  4011b9:       e8 1e 00 00 00          callq  4011dc <swap>
                   ^^^^^^^^^^^
  4011be:       8b 55 f8                mov    -0x8(%rbp),%edx
  4011c1:       8b 45 fc                mov    -0x4(%rbp),%eax
  4011c4:       89 c6                   mov    %eax,%esi
  4011c6:       bf 04 20 40 00          mov    $0x402004,%edi
                   ^^^^^^^^^^^
  4011cb:       b8 00 00 00 00          mov    $0x0,%eax
  4011d0:       e8 6b fe ff ff          callq  401040 <printf@plt>
                   ^^^^^^^^^^^
  4011d5:       b8 00 00 00 00          mov    $0x0,%eax
  4011da:       c9                      leaveq 
  4011db:       c3                      retq   

00000000004011dc <swap>:
  4011dc:       55                      push   %rbp
  4011dd:       48 89 e5                mov    %rsp,%rbp
  4011e0:       48 89 7d e8             mov    %rdi,-0x18(%rbp)
  4011e4:       48 89 75 e0             mov    %rsi,-0x20(%rbp)
  4011e8:       48 8b 45 e8             mov    -0x18(%rbp),%rax
  4011ec:       8b 00                   mov    (%rax),%eax
  4011ee:       89 45 fc                mov    %eax,-0x4(%rbp)
  4011f1:       48 8b 45 e0             mov    -0x20(%rbp),%rax
  4011f5:       8b 10                   mov    (%rax),%edx
  4011f7:       48 8b 45 e8             mov    -0x18(%rbp),%rax
  4011fb:       89 10                   mov    %edx,(%rax)
  4011fd:       48 8b 45 e0             mov    -0x20(%rbp),%rax
  401201:       8b 55 fc                mov    -0x4(%rbp),%edx
  401204:       89 10                   mov    %edx,(%rax)
  401206:       90                      nop
  401207:       5d                      pop    %rbp
  401208:       c3                      retq   
  401209:       0f 1f 80 00 00 00 00    nopl   0x0(%rax)

main 中对 swap 的调用是通过计算 swap 函数与 callq 后的一条指令的偏移量实现的。计算结果为 0x4011dc - 0x4011be = 0x1e,可以看到在 4011b9 处,callq 的操作数正是 0x1e

下一个重定位需要修改 mov 指令的操作数,重定位类型为 R_X86_64_32,需要修改 2 个字节,重定位目标值计算公式为 S + A

根据 info 字段的值 00050000000a,可以得知该符号在符号表中的下标为 5,查看符号表:

$ readelf -s main.o

Symbol table '.symtab' contains 12 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: 0000000000000000     0 SECTION LOCAL  DEFAULT    7 
     7: 0000000000000000     0 SECTION LOCAL  DEFAULT    8 
     8: 0000000000000000     0 SECTION LOCAL  DEFAULT    6 
     9: 0000000000000000    71 FUNC    GLOBAL DEFAULT    1 main
    10: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND swap
    11: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND printf

下标为 5 的符号是一个 section,section 的 index 也是 5。查看 section 列表会发现对应的 section 为 .rodata

$ readelf -S main.o
There are 13 section headers, starting at offset 0x2e8:

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
       0000000000000047  0000000000000000  AX       0     0     1
  [ 2] .rela.text        RELA             0000000000000000  00000220
       0000000000000048  0000000000000018   I      10     1     8
  [ 3] .data             PROGBITS         0000000000000000  00000087
       0000000000000000  0000000000000000  WA       0     0     1
  [ 4] .bss              NOBITS           0000000000000000  00000087
       0000000000000000  0000000000000000  WA       0     0     1
  [ 5] .rodata           PROGBITS         0000000000000000  00000087
       000000000000000f  0000000000000000   A       0     0     1
  [ 6] .comment          PROGBITS         0000000000000000  00000096
       0000000000000012  0000000000000001  MS       0     0     1
  [ 7] .note.GNU-stack   PROGBITS         0000000000000000  000000a8
       0000000000000000  0000000000000000           0     0     1
  [ 8] .eh_frame         PROGBITS         0000000000000000  000000a8
       0000000000000038  0000000000000000   A       0     0     8
  [ 9] .rela.eh_frame    RELA             0000000000000000  00000268
       0000000000000018  0000000000000018   I      10     8     8
  [10] .symtab           SYMTAB           0000000000000000  000000e0
       0000000000000120  0000000000000018          11     9     8
  [11] .strtab           STRTAB           0000000000000000  00000200
       0000000000000019  0000000000000000           0     0     1
  [12] .shstrtab         STRTAB           0000000000000000  00000280
       0000000000000061  0000000000000000           0     0     1

回到重定位,重定位时 mov 指令的操作数需要被修改为 .rodata 的 value + addend,这里 addend 为 0。

main.o 中的 .rodata 在链接后会被合并到可执行文件的 .rodata 中。这里我们计算最终的地址时,需要关注的是 main 的符号表中 .rodata 的值。

查看 main 的 section 列表,得知 .rodata 的 index 为 16,再查看符号表:

$ readelf -s main

...

Symbol table '.symtab' contains 76 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 00000000004002a8     0 SECTION LOCAL  DEFAULT    1 
     2: 00000000004002dc     0 SECTION LOCAL  DEFAULT    2 
     3: 0000000000400300     0 SECTION LOCAL  DEFAULT    3 
     4: 0000000000400328     0 SECTION LOCAL  DEFAULT    4 
     5: 0000000000400348     0 SECTION LOCAL  DEFAULT    5 
     6: 00000000004003a8     0 SECTION LOCAL  DEFAULT    6 
     7: 00000000004003e8     0 SECTION LOCAL  DEFAULT    7 
     8: 00000000004003f0     0 SECTION LOCAL  DEFAULT    8 
     9: 0000000000400410     0 SECTION LOCAL  DEFAULT    9 
    10: 0000000000400428     0 SECTION LOCAL  DEFAULT   10 
    11: 0000000000401000     0 SECTION LOCAL  DEFAULT   11 
    12: 0000000000401030     0 SECTION LOCAL  DEFAULT   12 
    13: 0000000000401060     0 SECTION LOCAL  DEFAULT   13 
    14: 0000000000401070     0 SECTION LOCAL  DEFAULT   14 
    15: 00000000004012c4     0 SECTION LOCAL  DEFAULT   15 
    16: 0000000000402000     0 SECTION LOCAL  DEFAULT   16 
    17: 0000000000402014     0 SECTION LOCAL  DEFAULT   17
    ...

可以看到 .rodata 的 value 为 0000000000402000,最终计算出重定位目标值 S + A = 0x402000

总结

在编译阶段,编译器以源文件作为一个单元来编译产生目标文件。因为目标文件需要被链接器链接为可执行文件,且目标文件中各个 section 在可执行文件中的位置是不确定的,目标文件中的变量和函数的地址也不确定。在编译阶段,实际上无法待访问符号最终的地址。链接器首先排定目标文件中的各个 section,这样各符号的地址就确定了,此后链接器开始改写指令中的访问地址,将其修改为运行时真正的地址,这个过程就叫做重定位。

在编译阶段,编译器会生成一个重定位表,表中的各个元素就是需要被重定位的条目。每一个条目就对应编译指令中需要被改写的一个位置。重定位条目中描述了重定位策略、待重定位改写的地址和重定位的符号信息。在链接阶段,链接器基于这些信息来执行重定位,让目标文件的指令中引用到正确的地址上。

评论 (评论内容仅博主可见,不会公开显示)