为什么 ELF 目标文件包含字符串文字和标准库函数的虚拟地址?

Why do ELF object files contain dummy addresses for string literals and stdlib functions?

我用 C 写了这个 Hello World :

#include<stdio.h>

int main() {
  printf("Hello world !\n");
  return 0;
}

用gcc编译成汇编代码 我明白了:

    .file   "file.c"
    .section    .rodata.str1.1,"aMS",@progbits,1
.LC0:
    .string "Hello world !"
    .section    .text.unlikely,"ax",@progbits
.LCOLDB1:
    .section    .text.startup,"ax",@progbits
.LHOTB1:
    .p2align 4,,15
    .globl  main
    .type   main, @function
main:
.LFB11:
    .cfi_startproc
    subq    , %rsp
    .cfi_def_cfa_offset 16
    movl    $.LC0, %edi
    call    puts
    xorl    %eax, %eax
    addq    , %rsp
    .cfi_def_cfa_offset 8
    ret
    .cfi_endproc
.LFE11:
    .size   main, .-main
    .section    .text.unlikely
.LCOLDE1:
    .section    .text.startup
.LHOTE1:
    .ident  "GCC: (GNU) 4.9.2 20150304 (prerelease)"
    .section    .note.GNU-stack,"",@progbits

这里没问题。但是现在,我想将汇编代码与 objdump 反汇编的代码进行比较:

对于主要功能,我得到了这个:

0000000000000000 <main>:
   0:   48 83 ec 08             sub    [=12=]x8,%rsp
   4:   bf 00 00 00 00          mov    [=12=]x0,%edi
            5: R_X86_64_32  .rodata.str1.1
   9:   e8 00 00 00 00          callq  e <main+0xe>
            a: R_X86_64_PC32    puts-0x4
   e:   31 c0                   xor    %eax,%eax
  10:   48 83 c4 08             add    [=12=]x8,%rsp
  14:   c3                      retq   

我不明白两件事:

为什么在edi上移动数字0意味着加载字符串"Hello world"?

另外,指令callq调用地址e。但是地址e处的指令不是函数puts而是xor。那么真实地址是什么?

答案是 linker 应用了各种修正。当我执行 objdump -d hello.o 时,我得到这个:

Disassembly of section .text:

0000000000000000 <main>:
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   bf 00 00 00 00          mov    [=10=]x0,%edi
   9:   e8 00 00 00 00          callq  e <main+0xe>
   e:   b8 00 00 00 00          mov    [=10=]x0,%eax
  13:   5d                      pop    %rbp
  14:   c3                      retq   

然而,objdump -d hello 的摘录产生了这个:

400536: 55                      push   %rbp
400537: 48 89 e5                mov    %rsp,%rbp
40053a: bf e0 05 40 00          mov    [=11=]x4005e0,%edi
40053f: e8 cc fe ff ff          callq  400410 <puts@plt>
400544: b8 00 00 00 00          mov    [=11=]x0,%eax
400549: 5d                      pop    %rbp
40054a: c3                      retq   

不同之处在于,字符串偏移量的零和 puts 的地址现在实际上由 linker 填充。您可以使用 objdump -r hello.o

找到那些 重定位条目
hello.o:     file format elf64-x86-64

RELOCATION RECORDS FOR [.text]:
OFFSET           TYPE              VALUE 
0000000000000005 R_X86_64_32       .rodata
000000000000000a R_X86_64_PC32     puts-0x0000000000000004

也就是说,linker 找到了 .rodata 的实际地址(这是字符串的地址)并将其放在偏移量 0x50x5 的地址处库 puts 代码并将其放置在偏移量 0xa.

This article on relocation 更详细地描述了该过程,并正确地指出虽然某些重定位发生在 link 时间,但加载程序也可以提供重定位数据。