为什么链接器会在.rela.plt中生成看似无用的重定位?

时间:2017-01-28 02:23:37

标签: c linker shared-libraries

首先,我正在玩的玩具程序:

prog.c中:

int func1();

int main(int argc, char const *argv[])
{
    func1();
    return 0;
}

lib.c:

int func1()
{
    return 0;
}

构建:

gcc -O3 -g -shared -fpic ./lib.c -o liba.so
gcc prog.c -g -la -L. -o prog -Wl,-rpath=$PWD

对于完整性:

$ gcc --version
gcc (GCC) 6.3.1 

$ ld --version
GNU ld version 2.26.1

现在,我的问题。我已经确认了我读过的关于动态符号的惰性绑定是如何工作的,即 最初func1的GOT条目指向PLT,然后指向跳转后的指令:

$ gdb prog    

(gdb) disassemble main
Dump of assembler code for function main:
   0x0000000000400666 <+0>: push   rbp
   0x0000000000400667 <+1>: mov    rbp,rsp
   0x000000000040066a <+4>: sub    rsp,0x10
   0x000000000040066e <+8>: mov    DWORD PTR [rbp-0x4],edi
   0x0000000000400671 <+11>:    mov    QWORD PTR [rbp-0x10],rsi
   0x0000000000400675 <+15>:    mov    eax,0x0
   0x000000000040067a <+20>:    call   0x400560 <func1@plt>    <<< call to shared lib via PLT
   0x000000000040067f <+25>:    mov    eax,0x0
   0x0000000000400684 <+30>:    leave  
   0x0000000000400685 <+31>:    ret    
End of assembler dump.
(gdb) disassemble 0x400560
Dump of assembler code for function func1@plt:
   0x0000000000400560 <+0>: jmp    QWORD PTR [rip+0x200ab2]        # 0x601018 <<< JMP to address stored in GOT
   0x0000000000400566 <+6>: push   0x0             <<< ... which initially points right back here
   0x000000000040056b <+11>:    jmp    0x400550
End of assembler dump. 
(gdb) x/g 0x601018
0x601018:   0x400566   <<< GOT point back right after the just-executed jump

这很好。现在,检查0x601018处的.got.plt部分,其中包含指向0x400566的指针,表明二进制文件本身保存地址,而动态链接器在加载时不执行任何操作。这是有道理的,因为这个地址在链接时是已知的:

$ readelf -x .got.plt ./prog 

Hex dump of section '.got.plt':
 NOTE: This section has relocations against it, but these have NOT been applied to this dump.
  0x00601000 ???????? ???????? ???????? ???????? ..`.............
  0x00601010 ???????? ???????? 66054000 ???????? ........f.@.....

(其中66054000是最初存储在GOT中的地址0x400566的little-endian rep。)

很好,很好。但是......然后......

$ readelf -r prog                             
<...>
Relocation section '.rela.plt' at offset 0x518 contains 1 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000601018  000100000007 R_X86_64_JUMP_SLO 0000000000000000 func1 + 0

为什么有这个GOT地址的重定位条目?我们已经看到正确的地址已经存在,在二进制文件中,在链接时放置在那里。 我还试验性地编辑了这个重定位,使其无效,程序运行正常。所以reloc似乎没什么贡献。它在那里做了什么,更普遍的是rela.plt中重新定位的实际情况是什么?

更新#1:

需要说明的是,这是关于PIC代码和64位代码的。以下是相关的部分地址,以帮助说明地址所属的位置:

$ readelf -S ./prog
Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 9] .rela.dyn         RELA             00000000004004e8  000004e8
       0000000000000030  0000000000000018   A       5     0     8
  [10] .rela.plt         RELA             0000000000400518  00000518
       0000000000000018  0000000000000018  AI       5    23     8
  [12] .plt              PROGBITS         0000000000400550  00000550
       0000000000000020  0000000000000010  AX       0     0     16
  [21] .dynamic          DYNAMIC          0000000000600e00  00000e00
       00000000000001f0  0000000000000010  WA       6     0     8
  [22] .got              PROGBITS         0000000000600ff0  00000ff0
       0000000000000010  0000000000000008  WA       0     0     8
  [23] .got.plt          PROGBITS         0000000000601000  00001000
       0000000000000020  0000000000000008  WA       0     0     8

更新#2:

编辑.rela.plt的节标题不会更改过程映像,因此我没有禁用重定位。 我也曾尝试更改reloc地址(到另一个可写地址),这似乎也没有什么区别,但事实证明,在延迟绑定期间解析器不使用该地址,尽管其余的reloc是。仅当使用LD_BIND_NOW关闭延迟绑定时才使用地址本身。

谢谢@yugr

1 个答案:

答案 0 :(得分:2)

  

这很好。现在,检查0x601018处的.got.plt部分,   其中包含此指针返回0x400566显示   二进制本身保存地址,没有动态链接器   在装载时做任何事情。

不是真的。请注意,0x400566处的代码最终跳转到0x400550,即之前的>到PLT存根。 0x400550处的代码将在堆栈上推送GOT的地址并调用动态链接器。有关详细信息,请查看this presentation(幻灯片14)。

  

这是有道理的,因为这个地址在链接时已知

是吗?地址将来自共享库,由于ASLR,它将在启动时以随机地址加载,因此静态链接器无法知道地址......

  

为什么有这个GOT地址的重定位条目?

当PLT存根调用动态链接器(第一次调用时)时,它会传递GOT条目的地址。动态链接器将搜索.rela.plt以了解如何重定位GOT条目(即函数名称和偏移量)。