ILD

共享库与位置无关代码
作者:Herbert Yuan 邮箱:yuanjp@hust.edu.cn
发布时间:2017-11-27 站点:Inside Linux Development

共享库的一个要求是代码应该可以在不同的进程中共享,这就要求在重定向时不能修改代码段的内容。共享库也实现成可加载到任何地址执行。对于同一个共享库,不同进程,其运行地址可能不同。


1 位置无关代码

位置无关代码,就是访问对象时,不使用对象的绝对地址,而是使用相对地址,通常是相对PC的地址,通过例子分析:


代码b.c

1
2
3
4
5
6
7
cat b.c
static int foo = 100;
 
int function(void)
{
    return foo;
}


编译x64和x86目标文件:

1
2
$ cc -c -fPIC -o b2.o b.c
$ cc -m32 -fPIC -c b.c


1.1 x64位置无关代码

目标文件elf信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ readelf -a b2.o
。。。
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
       000000000000000c  0000000000000000  AX       0     0     1
  [ 2] .rela.text        RELA             0000000000000000  000001c8
       0000000000000018  0000000000000018   I      10     1     8
  [ 3] .data             PROGBITS         0000000000000000  0000004c
       0000000000000004  0000000000000000  WA       0     0     4
。。。
Relocation section '.rela.text' at offset 0x1c8 contains 1 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000000006  000300000002 R_X86_64_PC32     0000000000000000 .data - 4


x64目标文件反汇编信息

1
2
3
4
5
6
7
8
9
10
11
12
13
$ objdump -d b2.o
 
b2.o:     file format elf64-x86-64
  
Disassembly of section .text:
 
0000000000000000 <function>:
   0:    55                       push   %rbp
   1:    48 89 e5                 mov    %rsp,%rbp
   4:    8b 05 00 00 00 00      mov    0x0(%rip),%eax        # a <function+0xa>
   a:    5d                       pop    %rbp
   b:    c3                       retq


分析:

可以看到,指令使用相对rip寄存器的地址访问。重定向类型为R_X86_64_PC32。是PC相对地址。


1.2 x86位置无关代码

目标文件的elf信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
$ readelf -a b.o
Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .group            GROUP           00000000 000034 000008 04     12  12  4
  [ 2] .text             PROGBITS        00000000 00003c 000015 00  AX  0   0  1
  [ 3] .rel.text         REL             00000000 000200 000018 08   I 12   2  4
  [ 4] .data             PROGBITS        00000000 000054 000004 00  WA  0   0  4
  [ 5] .bss              NOBITS          00000000 000058 000000 00  WA  0   0  1
  [ 6] .text.__x86.get_p PROGBITS        00000000 000058 000004 00 AXG  0   0  1
...
Relocation section '.rel.text' at offset 0x200 contains 3 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
00000004  00000c02 R_386_PC32        00000000   __x86.get_pc_thunk.ax
00000009  00000d0a R_386_GOTPC       00000000   _GLOBAL_OFFSET_TABLE_
0000000f  00000309 R_386_GOTOFF      00000000   .data
...
Symbol table '.symtab' contains 14 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
   ...
     5: 00000000     4 OBJECT  LOCAL  DEFAULT    4 foo
   ...
    12: 00000000     0 FUNC    GLOBAL HIDDEN     6 __x86.get_pc_thunk.ax
    13: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND _GLOBAL_OFFSET_TABLE
    ...


目标文件的反汇编信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ objdump -d b.o
 
b.o:     file format elf32-i386
  
Disassembly of section .text:
 
00000000 <function>:
   0:    55                       push   %ebp
   1:    89 e5                  mov    %esp,%ebp
   3:    e8 fc ff ff ff           call   4 <function+0x4>
   8:    05 01 00 00 00           add    $0x1,%eax
   d:    8b 80 00 00 00 00      mov    0x0(%eax),%eax
  13: 5d                       pop    %ebp
  14: c3                       ret    
 
Disassembly of section .text.__x86.get_pc_thunk.ax:
 
00000000 <__x86.get_pc_thunk.ax>:
   0:    8b 04 24                 mov    (%esp),%eax
   3:    c3


分析:

有3条重定向,386架构不允许直接访问当前指令地址,因此通过__x86.get_pc_thunk.ax函数间接访问,返回地址被压入栈中,通过栈来读取当前指令地址,存储到%eax中,然后通过两种重定向R_386_GOTPC和R_386_GOTOFF来确定相对偏移。


2 位置无关共享库

这里关注共享库访问全局符号的情况,因为对于本地符号(static),链接共享库时,符号地址和访问地址的相对位置是可以确定的。通过位置无关代码就可以满足共享库的要求。在动态加载(动态链接阶段)的视角来看,这些符号是不需要再次解析的。

注意:共享库中的全局符号,可以被外部全局符号覆盖,设计如此,这样用户就可以提供自己的变量和函数。


2.1 全局变量

代码c.c

1
2
3
4
5
6
7
$ cat c.c
extern int foo;
 
int function()
{
    return foo;
}


编译

1
$ cc -shared -fPIC -o libc.so c.c


反汇编信息:

1
2
3
4
5
6
7
8
9
$ objdump -d libc.so
...
0000000000000690 <function>:
 690:  55                       push   %rbp
 691:  48 89 e5                 mov    %rsp,%rbp
 694:  48 8b 05 45 09 20 00     mov    0x200945(%rip),%rax        # 200fe0 <_DYNAMIC+0x1a0>
 69b:  8b 00                  mov    (%rax),%eax
 69d:  5d                       pop    %rbp
 69e:  c3                       retq


地址为相对rip偏移0x200945,反汇编给出地址为0x200fe0。查看elf信息,它落到.got section里,而且存在一条foo的重定向信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
$ readelf -a libc.so
...
Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  LOAD           0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x000000000000072c 0x000000000000072c  R E    200000
  LOAD           0x0000000000000e28 0x0000000000200e28 0x0000000000200e28
                 0x00000000000001f8 0x0000000000000200  RW     200000
 
 Section to Segment mapping:
  Segment Sections...

   00     .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r 

.rela.dyn .init .plt .plt.got .text .fini .eh_frame_hdr .eh_frame 

   01     .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss 
   ...
    
Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
[19] .got              PROGBITS         0000000000200fd0  00000fd0
       0000000000000030  0000000000000008  WA       0     0     8
 
Relocation section '.rela.dyn' at offset 0x470 contains 9 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000200fe0  000400000006 R_X86_64_GLOB_DAT 0000000000000000 foo + 0

.got和.data是在同一个可读写的段里面的。


总结:

访问外部变量时,使用一个中间层.got,重定向将符号的patch到.got而不是.text,这样就避免了修改代码段。


2.2 外部函数

代码d.c及编译

1
2
3
4
5
6
7
8
$ cat d.c
int foo();
 
int function
{
    return foo();
}
$ cc -shared -fPIC -o libd.so d.c


编译

1
$ cc -shared -fPIC -o libd.so d.c


反汇编:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ objdump -d libd.so
...
Disassembly of section .plt:
 
0000000000000570 <foo@plt-0x10>:
 570:  ff 35 92 0a 20 00      pushq  0x200a92(%rip)        # 201008 <_GLOBAL_OFFSET_TABLE_+0x8>
 576:  ff 25 94 0a 20 00      jmpq   *0x200a94(%rip)        # 201010 <_GLOBAL_OFFSET_TABLE_+0x10>
 57c:  0f 1f 40 00            nopl   0x0(%rax)
 
0000000000000580 <foo@plt>:
 580:  ff 25 92 0a 20 00      jmpq   *0x200a92(%rip)        # 201018 <_GLOBAL_OFFSET_TABLE_+0x18>
 586:  68 00 00 00 00           pushq  $0x0
 58b:  e9 e0 ff ff ff           jmpq   570 <_init+0x28>
  
 ...
 00000000000006a0 <function>:
 6a0:  55                       push   %rbp
 6a1:  48 89 e5                 mov    %rsp,%rbp
 6a4:  b8 00 00 00 00           mov    $0x0,%eax
 6a9:  e8 d2 fe ff ff           callq  580 <foo@plt>
 6ae:  5d                       pop    %rbp
 6af:  c3                       retq


elf信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
$ readelf -a libd.so
Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 7] .rela.dyn         RELA             0000000000000470  00000470
       00000000000000c0  0000000000000018   A       3     0     8
  [ 8] .rela.plt         RELA             0000000000000530  00000530
       0000000000000018  0000000000000018  AI       3    21     8
  [ 9] .init             PROGBITS         0000000000000548  00000548
       0000000000000020  0000000000000010  AX       0     0     16
  [11] .plt.got          PROGBITS         0000000000000590  00000590
       0000000000000010  0000000000000000  AX       0     0     8
  [12] .text             PROGBITS         00000000000005a0  000005a0
       0000000000000110  0000000000000000  AX       0     0     16
  [20] .got              PROGBITS         0000000000200fd8  00000fd8
       0000000000000028  0000000000000008  WA       0     0     8
  [21] .got.plt          PROGBITS         0000000000201000  00001000
       0000000000000020  0000000000000008  WA       0     0     8
   
 Section to Segment mapping:
  Segment Sections...
   00     .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version 
   .gnu.version_r .rela.dyn .rela.plt .init .plt .plt.got .text .fini .eh_frame_hdr .eh_frame 
   01     .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss 
    
Relocation section '.rela.plt' at offset 0x530 contains 1 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000201018  000400000007 R_X86_64_JUMP_SLO 0000000000000000 foo + 0


为了实现懒加载,函数调用实现成如下框架[1]



.plt section包含n个PLT片,第一个PLT[0]用于解析符号。后续的PLT都对应一个外部函数。如上述反汇编0x6a9通过call指令调用0x580处指令,也即PLT[1]。PLT[1]包括3条指令,第一条指令是jmp指令[3],它跳转到指定内存地址存储的地址。这个内存地址是0x201018。这个地址在.got.plt section,其值为0x00000586,这就是PLT[1]的第二条指令的地址,如下:

1
2
3
4
5
6
7
8
Disassembly of section .got.plt:
 
0000000000201000 <_GLOBAL_OFFSET_TABLE_>:
  201000: 18 0e                  sbb    %cl,(%rsi)
  201002: 20 00                  and    %al,(%rax)
    ...
  201018: 86 05 00 00 00 00      xchg   %al,0x0(%rip)        # 20101e <_GLOBAL_OFFSET_TABLE_+0x1e>
    ...


第二条指令准备符号解析的参数,第三条指令跳转到PLT[0]执行,PLT[0]最终将调用动态链接器中的函数执行解析任务。在符号解析后,将修改0x201018内存处的值,使其执行正确的地址,这样就可以直接跳转到函数处执行。


3 思考

位置无关总是与共享库联系在一起,可执行文件不需要共享代码段,因此通常是位置相关的。


访问本地函数,总是使用相对PC地址,因为他们在一个.text section,偏移在编译阶段就可以确定,这样不需要任何重定向信息。


位置无关代码和got实现是两回事。不需要got也可以实现位置无关代码。但是got通常是位置无关的。


链接-fPIC共享库时,通常也要求目标文件是-fPIC编译的。


参考

【1】https://eli.thegreenplace.net/2011/11/03/position-independent-code-pic-in-shared-libraries

【2】https://eli.thegreenplace.net/2011/11/11/position-independent-code-pic-in-shared-libraries-on-x64 

【3】https://stackoverflow.com/questions/20251097/what-does-this-intel-jmpq-instruction-do


Copyright © linuxdev.cc 2017-2024. Some Rights Reserved.