PIC

position independent code,位值无关代码,浮动地址代码。指可在内存中任意位值正确地运行,而不受其绝对地址影响的一种机器码。

PIC 广泛使用于共享库,使得同一个库中的代码能够被加载到不同进程的地址空间中。

shared.c

#include <stdio.h>

static int static_val;
extern int extern_val;
int global_val;

extern void external();

static void internal()
{
    static_val = 0x11223344;
}

void sharefunc(int caller)
{
    internal();
    extern_val = 0x55667788;
    global_val = 0x99AABBCC;
    external();
}
gcc -fPIC -shared -o libshared.so shared.c

-shared 生成共享目标文件。通常用在建立共享库时。

-fPIC 作用于编译阶段,告诉编译器产生位值无关代码,产生的代码中,没有绝对地址,全部使用相对地址,故而代码可以被加载器加载到内存的任意位值,并且可以正确执行。这正是共享库所要求的,共享库在加载时,在内存的位值是不固定的。

模块内部的函数调用

这种情况比较简单,虽然模块的加载地址会变化,但是模块内部各个函数之间的相对位值是固定的,所以只要使用相对地址跳转来调用模块内部函数,就可以做到与加载地址无关。

来看看生成的动态库中 shardfunc() 是如何调用内部函数 internal() 的:

objdump -SD libshared.so > libshared.dump
0000000000001119 <internal>:
    1119:	f3 0f 1e fa          	endbr64 
    111d:	55                   	push   %rbp
    111e:	48 89 e5             	mov    %rsp,%rbp
    1121:	c7 05 01 2f 00 00 44 	movl   $0x11223344,0x2f01(%rip)        # 402c <static_val>
    1128:	33 22 11 
    112b:	90                   	nop
    112c:	5d                   	pop    %rbp
    112d:	c3                   	retq   

000000000000112e <sharefunc>:
    112e:	f3 0f 1e fa          	endbr64 
    1132:	55                   	push   %rbp
    1133:	48 89 e5             	mov    %rsp,%rbp
    1136:	48 83 ec 10          	sub    $0x10,%rsp
    113a:	89 7d fc             	mov    %edi,-0x4(%rbp)
    113d:	b8 00 00 00 00       	mov    $0x0,%eax
    1142:	e8 d2 ff ff ff       	callq  1119 <internal>
    1147:	48 8b 05 82 2e 00 00 	mov    0x2e82(%rip),%rax        # 3fd0 <extern_val>
    114e:	c7 00 88 77 66 55    	movl   $0x55667788,(%rax)
    1154:	48 8b 05 7d 2e 00 00 	mov    0x2e7d(%rip),%rax        # 3fd8 <global_val-0x58>
    115b:	c7 00 cc bb aa 99    	movl   $0x99aabbcc,(%rax)
    1161:	b8 00 00 00 00       	mov    $0x0,%eax
    1166:	e8 e5 fe ff ff       	callq  1050 <external@plt>
    116b:	90                   	nop
    116c:	c9                   	leaveq 
    116d:	c3                   	retq   

调用 internal() 函数的汇编代码在 0x1142 位值处,可以看到,生成的汇编码是 e8 d2 ff ff ff,从 Intel 的编程手册中可以查到,e8 是相对地址跳转 call 指令的操作码,其功能是调用从下一条指令地址算起偏移指定的偏移量的函数,指令的操作数 d2 ff ff ff 是一个 32 位的偏移量 0xffffffd2(Intel PC 的处理器是小端决定的?),是 -46 的补码形式。计算一下,从 call 的下一条指令地址算起, 0x1147 - 46 = 0x1119,刚好定位到 internal() 函数的开始地址。

这种寻址方式天生就是与加载地址无关的,所以不需要对动态库做特殊处理。

模块内部的数据访问

这种情况也不太复杂。在共享库被加载到内存时,统一模块内的代码段与数据段的相对地址也是固定的,因此也可以用相对地址寻址,用当前指令地址加某个固定偏移量的方式就可以定位到目标数据。

如上面 internal() 函数访问 static_val 的代码 0x1121 所示,它用当前 rip 加偏移量 0x2f01 来定位 static_val ,因为当一条指令在 CPU 中执行时,rip 已经被指向下一条指令了,所以当前 rip 的值就是下一条指令的地址 0x11280x112b + 0x2f01 = 0x402c 。模块内未初始化的静态变量的存储位值在 .bss 段内,dump 文件的内容也能印证这一点,.bss 段中其实没有指令,只有数据,这里初始数据都是 0 :

Disassembly of section .bss:

0000000000004028 <completed.8060>:
    4028:	00 00                	add    %al,(%rax)
	...

000000000000402c <static_val>:
    402c:	00 00                	add    %al,(%rax)
	...

0000000000004030 <global_val>:
	...

所以,模块内数据的访问也是采用相对地址偏移的方式完成的,偏移位值是在编译时由编译器计算得到的。

它天生也是与加载地址无关的,也不需要对动态库做特殊处理。

模块之间的数据访问

。。。

模块之间的函数调用

。。。

共享库的加载过程

位值无关代码工作原理问题解决了,接下来就要找个实施者按照这套规则做事了,下面我们就来详细讨论一下动态链接的程序从装载进内存到可以正常工作之间经历的每个步骤

首先,装载是由操作系统完成的。操作系统会按照 ELF 文件的程序头信息把指定的段映射到进程的虚拟地址空间内,并设置相应的访问权限。

  • 如果程序是静态链接的,那么加载完成之后就会跳转到 ELF 文件头指定的入口地址去执行。
  • 如果程序是动态链接的,操作系统会把动态链接器的映像也映射到进程的虚拟空间内,然后把控制权交给动态链接器。

判断程序是动态链接还是静态链接的方法是看 ELF 文件的 .interp 段,该段保存的内容类型是字符串,如果是动态链接的程序,动态链接器的全路径名称就会被保存在里面;如果是静态链接的,而且不依赖任何别的共享库,该段就会没有内容,不过这种情况现在已经很少见了,大多数程序最少也要与 C 标准库动态链接。

$ objdump -s elfdemo.out 

elfdemo.out:     文件格式 elf64-x86-64

Contents of section .interp:
 400318 2f6c6962 36342f6c 642d6c69 6e75782d  /lib64/ld-linux-
 400328 7838362d 36342e73 6f2e3200           x86-64.so.2.    
Contents of section .note.gnu.property:
 400338 04000000 10000000 05000000 474e5500  ............GNU.
 400348 020000c0 04000000 03000000 00000000  ................
Contents of section .note.gnu.build-id:
 400358 04000000 14000000 03000000 474e5500  ............GNU.
 400368 d31074c2 3465f875 f6d188ce a193a3c6  ..t.4e.u........
 400378 6bbb049c                             k...            
Contents of section .note.ABI-tag:
 40037c 04000000 10000000 01000000 474e5500  ............GNU.
 40038c 00000000 03000000 02000000 00000000  ................
Contents of section .gnu.hash:
 4003a0 01000000 01000000 01000000 00000000  ................
 4003b0 00000000 00000000 00000000           ............    
Contents of section .dynsym:
 4003c0 00000000 00000000 00000000 00000000  ................
 4003d0 00000000 00000000 0b000000 12000000  ................
 4003e0 00000000 00000000 00000000 00000000  ................
 4003f0 10000000 12000000 00000000 00000000  ................
 400400 00000000 00000000 2e000000 20000000  ............ ...
 400410 00000000 00000000 00000000 00000000  ................
Contents of section .dynstr:
 400420 006c6962 632e736f 2e360070 75747300  .libc.so.6.puts.
 400430 5f5f6c69 62635f73 74617274 5f6d6169  __libc_start_mai
 400440 6e00474c 4942435f 322e322e 35005f5f  n.GLIBC_2.2.5.__
 400450 676d6f6e 5f737461 72745f5f 00        gmon_start__.   
Contents of section .gnu.version:
 40045e 00000200 02000000                    ........        
Contents of section .gnu.version_r:
 400468 01000100 01000000 10000000 00000000  ................
 400478 751a6909 00000200 22000000 00000000  u.i.....".......
Contents of section .rela.dyn:
 400488 f03f4000 00000000 06000000 02000000  .?@.............
 400498 00000000 00000000 f83f4000 00000000  .........?@.....
 4004a8 06000000 03000000 00000000 00000000  ................
Contents of section .rela.plt:
 4004b8 18404000 00000000 07000000 01000000  .@@.............
 4004c8 00000000 00000000                    ........        
Contents of section .init:
 401000 f30f1efa 4883ec08 488b05e9 2f000048  ....H...H.../..H
 401010 85c07402 ffd04883 c408c3             ..t...H....     
Contents of section .plt:
 401020 ff35e22f 0000f2ff 25e32f00 000f1f00  .5./....%./.....
 401030 f30f1efa 68000000 00f2e9e1 ffffff90  ....h...........
Contents of section .plt.sec:
 401040 f30f1efa f2ff25cd 2f00000f 1f440000  ......%./....D..
Contents of section .text:
 401050 f30f1efa 31ed4989 d15e4889 e24883e4  ....1.I..^H..H..
 401060 f0505449 c7c0d011 400048c7 c1601140  .PTI....@.H..`.@
 401070 0048c7c7 36114000 ff15722f 0000f490  .H..6.@...r/....
 401080 f30f1efa c3662e0f 1f840000 00000090  .....f..........
 401090 b8404040 00483d40 40400074 13b80000  .@@@.H=@@@.t....
 4010a0 00004885 c07409bf 40404000 ffe06690  ..H..t..@@@...f.
 4010b0 c366662e 0f1f8400 00000000 0f1f4000  .ff...........@.
 4010c0 be404040 004881ee 40404000 4889f048  .@@@.H..@@@.H..H
 4010d0 c1ee3f48 c1f80348 01c648d1 fe7411b8  ..?H...H..H..t..
 4010e0 00000000 4885c074 07bf4040 4000ffe0  ....H..t..@@@...
 4010f0 c366662e 0f1f8400 00000000 0f1f4000  .ff...........@.
 401100 f30f1efa 803d352f 00000075 13554889  .....=5/...u.UH.
 401110 e5e87aff ffffc605 232f0000 015dc390  ..z.....#/...]..
 401120 c366662e 0f1f8400 00000000 0f1f4000  .ff...........@.
 401130 f30f1efa eb8af30f 1efa5548 89e54883  ..........UH..H.
 401140 ec10897d fc488975 f0488d3d d00e0000  ...}.H.u.H.=....
 401150 e8ebfeff ffb80000 0000c9c3 0f1f4000  ..............@.
 401160 f30f1efa 41574c8d 3da32c00 00415649  ....AWL.=.,..AVI
 401170 89d64155 4989f541 544189fc 55488d2d  ..AUI..ATA..UH.-
 401180 942c0000 534c29fd 4883ec08 e86ffeff  .,..SL).H....o..
 401190 ff48c1fd 03741f31 db0f1f80 00000000  .H...t.1........
 4011a0 4c89f24c 89ee4489 e741ff14 df4883c3  L..L..D..A...H..
 4011b0 014839dd 75ea4883 c4085b5d 415c415d  .H9.u.H...[]A\A]
 4011c0 415e415f c366662e 0f1f8400 00000000  A^A_.ff.........
 4011d0 f30f1efa c3                          .....           
Contents of section .fini:
 4011d8 f30f1efa 4883ec08 4883c408 c3        ....H...H....   
Contents of section .rodata:
 402000 01000200 00000000 00000000 00000000  ................
 402010 49276d20 436f6e73 74204461 74610000  I'm Const Data..
 402020 4d657373 61676520 496e204d 61696e00  Message In Main.
Contents of section .eh_frame_hdr:
 402030 011b033b 44000000 07000000 f0efffff  ...;D...........
 402040 88000000 10f0ffff b0000000 20f0ffff  ............ ...
 402050 60000000 50f0ffff 74000000 06f1ffff  `...P...t.......
 402060 c8000000 30f1ffff e8000000 a0f1ffff  ....0...........
 402070 30010000                             0...            
Contents of section .eh_frame:
 402078 14000000 00000000 017a5200 01781001  .........zR..x..
 402088 1b0c0708 90010000 10000000 1c000000  ................
 402098 b8efffff 2f000000 00440710 10000000  ..../....D......
 4020a8 30000000 d4efffff 05000000 00000000  0...............
 4020b8 24000000 44000000 60efffff 20000000  $...D...`... ...
 4020c8 000e1046 0e184a0f 0b770880 003f1a3a  ...F..J..w...?.:
 4020d8 2a332422 00000000 14000000 6c000000  *3$"........l...
 4020e8 58efffff 10000000 00000000 00000000  X...............
 4020f8 1c000000 84000000 36f0ffff 26000000  ........6...&...
 402108 00450e10 8602430d 065d0c07 08000000  .E....C..]......
 402118 44000000 a4000000 40f0ffff 65000000  D.......@...e...
 402128 00460e10 8f02490e 188e0345 0e208d04  .F....I....E. ..
 402138 450e288c 05440e30 8606480e 38830747  E.(..D.0..H.8..G
 402148 0e406e0e 38410e30 410e2842 0e20420e  .@n.8A.0A.(B. B.
 402158 18420e10 420e0800 10000000 ec000000  .B..B...........
 402168 68f0ffff 05000000 00000000 00000000  h...............
Contents of section .init_array:
 403e10 30114000 00000000                    0.@.....        
Contents of section .fini_array:
 403e18 00114000 00000000                    ..@.....        
Contents of section .dynamic:
 403e20 01000000 00000000 01000000 00000000  ................
 403e30 0c000000 00000000 00104000 00000000  ..........@.....
 403e40 0d000000 00000000 d8114000 00000000  ..........@.....
 403e50 19000000 00000000 103e4000 00000000  .........>@.....
 403e60 1b000000 00000000 08000000 00000000  ................
 403e70 1a000000 00000000 183e4000 00000000  .........>@.....
 403e80 1c000000 00000000 08000000 00000000  ................
 403e90 f5feff6f 00000000 a0034000 00000000  ...o......@.....
 403ea0 05000000 00000000 20044000 00000000  ........ .@.....
 403eb0 06000000 00000000 c0034000 00000000  ..........@.....
 403ec0 0a000000 00000000 3d000000 00000000  ........=.......
 403ed0 0b000000 00000000 18000000 00000000  ................
 403ee0 15000000 00000000 00000000 00000000  ................
 403ef0 03000000 00000000 00404000 00000000  .........@@.....
 403f00 02000000 00000000 18000000 00000000  ................
 403f10 14000000 00000000 07000000 00000000  ................
 403f20 17000000 00000000 b8044000 00000000  ..........@.....
 403f30 07000000 00000000 88044000 00000000  ..........@.....
 403f40 08000000 00000000 30000000 00000000  ........0.......
 403f50 09000000 00000000 18000000 00000000  ................
 403f60 feffff6f 00000000 68044000 00000000  ...o....h.@.....
 403f70 ffffff6f 00000000 01000000 00000000  ...o............
 403f80 f0ffff6f 00000000 5e044000 00000000  ...o....^.@.....
 403f90 00000000 00000000 00000000 00000000  ................
 403fa0 00000000 00000000 00000000 00000000  ................
 403fb0 00000000 00000000 00000000 00000000  ................
 403fc0 00000000 00000000 00000000 00000000  ................
 403fd0 00000000 00000000 00000000 00000000  ................
 403fe0 00000000 00000000 00000000 00000000  ................
Contents of section .got:
 403ff0 00000000 00000000 00000000 00000000  ................
Contents of section .got.plt:
 404000 203e4000 00000000 00000000 00000000   >@.............
 404010 00000000 00000000 30104000 00000000  ........0.@.....
Contents of section .data:
 404020 00000000 00000000 00000000 00000000  ................
 404030 49276d20 53746174 69632044 61746100  I'm Static Data.
Contents of section .comment:
 0000 4743433a 20285562 756e7475 20392e33  GCC: (Ubuntu 9.3
 0010 2e302d31 37756275 6e747531 7e32302e  .0-17ubuntu1~20.
 0020 30342920 392e332e 3000               04) 9.3.0.      

接下来,动态链接器会负责加载依赖的其他共享库,并修正互相之间引用的函数和数据地址

不过这里还有一个问题需要解决,就是动态链接器自身也是一个共享库,它自己的加载地址也是不能确定的,所以它需要有一点特殊,就是它自身不能再依赖其他的共享库,否则就会陷入鸡生蛋、蛋生鸡的循环里面出不来了。

动态链接器中有一段特殊的自举代码,用于完成自身的指令和数据的地址修正,自举完成之后,再去实际干活。

动态链接器是 glibc 的一部分,有兴趣的读者可以去阅读这段精巧的自修正代码,在 glibc 源码目录的 elf/rtld.c 文件里面。

自举完成之后,要顺利完成接下来的任务,动态连接器还需要知道以下这样一些信息。

  • 还有哪些依赖的共享库需要加载?
  • 哪些位置的数值需要修正?
  • 需要修正的位置引用的是哪个共享库内的内容?
  • 应该如何对这些位置做修正?

下面就来依次看看 ELF 文件如何提供这些信息。