【问题标题】:How shared library finds GOT section?共享库如何找到 GOT 部分?
【发布时间】:2016-09-30 06:44:50
【问题描述】:

当我阅读http://eli.thegreenplace.net/2011/11/03/position-independent-code-pic-in-shared-libraries/#id1时 问题来了:

PIC共享库在进程的虚拟地址空间某处加载后如何知道如何引用外部变量?

这是相关共享库的代码:

#include <stdio.h>

extern long var;

void
shara_func(void)
{
        printf("%ld\n", var);
}

生成对象代码,然后共享对象(库):

gcc -fPIC -c lib1.c                    # produce PIC lib1.o
gcc -fPIC -shared lib1.o -o liblib1.so # produce PIC shared library

在共享库中反汇编shara_func

objdump -d liblib1.so
 ...
 00000000000006d0 <shara_func>:
 6d0:   55                      push   %rbp
 6d1:   48 89 e5                mov    %rsp,%rbp
 6d4:   48 8b 05 fd 08 20 00    mov    0x2008fd(%rip),%rax        # 200fd8 <_DYNAMIC+0x1c8>
 6db:   48 8b 00                mov    (%rax),%rax
 6de:   48 89 c6                mov    %rax,%rsi
 6e1:   48 8d 3d 19 00 00 00    lea    0x19(%rip),%rdi        # 701 <_fini+0x9>
 6e8:   b8 00 00 00 00          mov    $0x0,%eax
 6ed:   e8 be fe ff ff          callq  5b0 <printf@plt>
 6f2:   90                      nop
 6f3:   5d                      pop    %rbp
 6f4:   c3                      retq   
...

我看到 0x6d4 地址处的指令将一些相对于 PC 的地址移动到 rax,我想这是 GOT 中的条目,GOT 相对引用自PC 在运行时获取外部变量var 的地址(它在运行时解析取决于var 的加载位置)。 然后在0x6db处执行指令后,我们将外部变量的实际内容放在rax中,然后将值从rax移动到rsi - 在寄存器中传递的第二个函数参数。

我以为进程内存中只有一个 GOT,但是, 看到那个库引用了 GOT 吗?当共享库(PIC 库)不知道它将在进程内存中加载的位置时,它如何知道进程 GOT 的偏移量?还是每个共享库都有自己加载的 GOT?如果您能澄清我的困惑,我将非常高兴。

【问题讨论】:

  • Bulat,您是否检查了 objdump -robjdump -R 在您的 ELF - man7.org/linux/man-pages/man1/objdump.1.html 中搜索 RELOC(重定位)?
  • @osgx,是的,即 0x200fd8 R_X86_64_GLOB_DAT var + 0 重定位。 PC 相对:0x2008fd + 0x6db = 0x200fd8。不明白链接器如何计算 0x2008fd 偏移量,如何知道运行时进程的 GOT 将位于何处。
  • 布拉特,事实是:有两个链接器:一个是ld命令,另一个是运行时链接器,RTLD,ld-linux.so.2linux中的程序(它被注册为动态ELF的解释器-@ 987654323@)。因此,由 ld 链接器创建的 R_X86_64_GLOB_DAT 将由运行时链接器 RTLD 解析:osxr.org:8080/glibc/source/sysdeps/x86_64/dl-machine.h#0304。它是打开lib文件的人;将其映射到正确的位置,创建真正的 GOT 并解析重定位以指向真正的 GOT。
  • @osgs,每个共享库由运行时链接器(RTLD)映射到进程的虚拟地址空间,共享库在其“数据部分”中附带 GOT 和 PLT(如果需要),以便进程可以拥有多个共享库的多个 GOT?
  • Bulat,如果我理解 eli.thegreenplace.net/2011/11/03/…technovelty.org/linux/… 正确,是的,如果 GOT 像每个库的 .data 一样加载,则有几个 GOT。并且链接器会将最终选择的变量的真实绝对地址(它可以解析为其他库或可执行文件)放入 GOT 中的正确条目中(在库的.got 部分内是否有任何额外的 RELOC 点?或者是 _GLOB_DAT ?)。 0x6db 将使用这个指针来获取变量。

标签: linux shared-libraries extern relocation position-independent-code


【解决方案1】:

我在想进程内存中只有一个 GOT,但是,看到库引用 GOT 了吗?

我们清楚地将.got 部分视为库的一部分。通过readelf,我们可以找到库的哪些部分以及它们是如何加载的:​​

readelf -e liblib1.so
...
Section Headers:
  [21] .got              PROGBITS         0000000000200fd0  00000fd0
       0000000000000030  0000000000000008  WA       0     0     8
...

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  LOAD           0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x000000000000078c 0x000000000000078c  R E    200000
  LOAD           0x0000000000000df8 0x0000000000200df8 0x0000000000200df8
                 0x0000000000000230 0x0000000000000238  RW     200000
...
 Section to Segment mapping:
  Segment Sections...
   00     ... .init .plt .plt.got .text .fini .rodata .eh_frame_hdr .eh_frame 
   01     .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss 
   02     .dynamic 

所以,有.got 部分,但runtime linker ld-linux.so.2(注册为动态ELF 的解释器)不加载部分;它加载具有LOAD 类型的程序头所描述的段。 .got 是带有 RW 标志的段 01 LOAD 的一部分。其他库将拥有自己的 GOT(考虑从类似的源编译 liblib2.so,它不会对 liblib1.so 有任何了解,并且将拥有自己的 GOT);所以它只是图书馆的“全球”;但不是加载后内存中的整个程序映像。

当共享库(PIC 库)不知道它将被加载到进程内存中的哪个位置时,它如何知道进程 GOT 的偏移量?

当它需要几个 ELF 对象并将它们全部组合到一个库中时,它是由静态链接器完成的。链接器将生成.got 部分并将其放在与库代码(pc-relative,rip-relative)有已知偏移量的某个地方。它将指令写入程序头,因此相对地址是已知的,它是访问自己的GOT唯一需要的地址。

objdump-r/-R标志一起使用时,它将打印记录在ELF文件或库中的重定位信息(静态/动态);它可以与 -d 标志结合使用。 lib1.o 对象在这里进行了重定位;没有已知的 GOT 偏移量,mov 全部为零:

$ objdump -dr lib1.o 
lib1.o:     file format elf64-x86-64

Disassembly of section .text:

0000000000000000 <shara_func>:
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   48 8b 05 00 00 00 00    mov    0x0(%rip),%rax        # b <shara_func+0xb>
            7: R_X86_64_REX_GOTPCRELX   var-0x4
   b:   48 8b 00                mov    (%rax),%rax
   e:   48 89 c6                mov    %rax,%rsi

在库文件中,这被gcc -shared 转换为相对地址(它在内部调用ld 变体collect2):

$ objdump -d liblib1.so 

liblib1.so:     file format elf64-x86-64

00000000000006d0 <shara_func>:
 6d0:   55                      push   %rbp
 6d1:   48 89 e5                mov    %rsp,%rbp
 6d4:   48 8b 05 fd 08 20 00    mov    0x2008fd(%rip),%rax        # 200fd8 <_DYNAMIC+0x1c8>

最后,动态重定位到 GOT 以将 var 的实际地址放在这里(由 rtld - ld-linux.so.2 完成):

$ objdump -R liblib1.so 

liblib1.so:     file format elf64-x86-64

DYNAMIC RELOCATION RECORDS
OFFSET           TYPE              VALUE 
...
0000000000200fd8 R_X86_64_GLOB_DAT  var

让我们使用你的库,添加可执行文件和定义,编译它并在启用 rtld 调试的情况下运行:

$ cat main.c 
long var;
int main(){
    shara_func();
    return 0;
}
$ gcc main.c -llib1 -L. -o main -Wl,-rpath=`pwd`
$ LD_DEBUG=all ./main 2>&1 |less
...
   311:     symbol=var;  lookup in file=./main [0]
   311:     binding file /test3/liblib1.so [0] to ./main [0]: normal symbol `var'

因此,链接器能够将var 的重定位绑定到定义它的“主”ELF 文件:

$ gdb -q ./main 
Reading symbols from ./main...(no debugging symbols found)...done.
(gdb) b main
Breakpoint 1 at 0x4006da
(gdb) r
Starting program: /test3/main 

Breakpoint 1, 0x00000000004006da in main ()
(gdb) disassemble shara_func
Dump of assembler code for function shara_func:
   0x00007ffff7bd56d0 <+0>: push   %rbp
   0x00007ffff7bd56d1 <+1>: mov    %rsp,%rbp
   0x00007ffff7bd56d4 <+4>: mov    0x2008fd(%rip),%rax        # 0x7ffff7dd5fd8
   0x00007ffff7bd56db <+11>:    mov    (%rax),%rax
   0x00007ffff7bd56de <+14>:    mov    %rax,%rsi

你的 func 中的 mov 没有变化。 func+4之后的rax是0x601040,是./main根据/proc/$pid/maps的第三个映射:

00601000-00602000 rw-p 00001000 08:07 6691394                            /test3/main

它是在这个程序头之后从main加载的(readelf -e ./main)

LOAD           0x0000000000000df0 0x0000000000600df0 0x0000000000600df0
               0x0000000000000248 0x0000000000000258  RW     200000

它是 .bss 部分的一部分:

 [26] .bss              NOBITS           0000000000601038  00001038
      0000000000000010  0000000000000000  WA       0     0     8

进入 func+11 后,我们可以检查 GOT 中的值:

(gdb) b shara_func
(gdb) r
(gdb) si
0x00007ffff7bd56db in shara_func () from /test3/liblib1.so
1: x/i $pc
=> 0x7ffff7bd56db <shara_func+11>:  mov    (%rax),%rax
(gdb) p $rip+0x2008fd
$6 = (void (*)()) 0x7ffff7dd5fd8
(gdb) x/2x 0x7ffff7dd5fd8
0x7ffff7dd5fd8: 0x00601040  0x00000000

谁给这个 GOT 条目写了正确的值?

(gdb) watch *0x7ffff7dd5fd8
Hardware watchpoint 2: *0x7ffff7dd5fd8
(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /test3/main 

Hardware watchpoint 2: *0x7ffff7dd5fd8

Old value = <unreadable>
New value = 6295616
0x00007ffff7de36bf in elf_machine_rela (..) at ../sysdeps/x86_64/dl-machine.h:435
(gdb) bt
#0  0x00007ffff7de36bf in elf_machine_rela (...) at ../sysdeps/x86_64/dl-machine.h:435
#1  elf_dynamic_do_Rela (...) at do-rel.h:137
#2  _dl_relocate_object (...) at dl-reloc.c:258
#3  0x00007ffff7ddaf5b in dl_main (...)        at rtld.c:2072
#4  0x00007ffff7df0462 in _dl_sysdep_start (start_argptr=start_argptr@entry=0x7fffffffde20, 
    dl_main=dl_main@entry=0x7ffff7dd89a0 <dl_main>) at ../elf/dl-sysdep.c:249
#5  0x00007ffff7ddbe7a in _dl_start_final (arg=0x7fffffffde20) at rtld.c:307
#6  _dl_start (arg=0x7fffffffde20) at rtld.c:413
#7  0x00007ffff7dd7cc8 in _start () from /lib64/ld-linux-x86-64.so.2

(gdb) x/2x 0x7ffff7dd5fd8
0x7ffff7dd5fd8: 0x00601040  0x00000000

glibc 的运行时链接器 (rtld.c),就在调用 main 之前 - 这是源代码(版本不同) - http://code.metager.de/source/xref/gnu/glibc/sysdeps/x86_64/dl-machine.h

329 case R_X86_64_GLOB_DAT:
330 case R_X86_64_JUMP_SLOT:
331   *reloc_addr = value + reloc->r_addend;
332   break;

通过反向步进,我们可以获得代码的历史记录和旧值 = 0:

(gdb) b _dl_relocate_object 
(gdb) r
(gdb) dis 3
(gdb) target record-full
(gdb) c
(gdb) disp/i $pc
(gdb) rsi
(gdb) rsi
(gdb) rsi
(gdb) x/2x 0x7ffff7dd5fd8
0x7ffff7dd5fd8: 0x00000000  0x00000000


=> 0x7ffff7de36b8 <_dl_relocate_object+1560>:   add    0x10(%rbx),%rax
=> 0x7ffff7de36bc <_dl_relocate_object+1564>:   mov    %rax,(%r10)
=> 0x7ffff7de36bf <_dl_relocate_object+1567>:   nop

【讨论】:

  • 读到中间,怎么能找到gdb中调试的进程的PID?
  • 可以打开其他控制台(终端)并在ps输出中搜索进程。如果测试程序被称为main 并且只有一个程序运行的进程,还有pidof main
  • Bulat,您能否从stackoverflow.com/questions/5130654/… 中删除您关于随机化 va 空间的评论并将问题移至此处?它可以在构建时在您的内核中禁用(对于 MMU-less 内核.. 或太旧的内核.. 或某些第三方补丁); /proc 不能挂载;可以使用 sysctl - sysctl -a|grep randomize 访问值;你的猫也尝试setarch x86_64 -R ./program禁用它(askubuntu.com/questions/318315)。
  • “把问题移到这里”是什么意思,怎么做?我认为问题相关且切中要害,我们可以在那个地方回答。事实上,我有 MMU 和安装了 /proc 的相对较新的内核。
  • 你有'/proc/sys/kernel/'目录吗?您可以发布您的内核版本并列出/proc/sys/kernel/*rand* 文件吗? sysctl -a | grep randomize 输出中是否有任何随机化?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-21
  • 2014-06-13
  • 2016-06-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多