gcc的-fPIC对elf文件的影响

来源:互联网 发布:eclipse js文件格式化 编辑:程序博客网 时间:2024/05/16 01:49

written by hjjdebug.
例子还是用以前的例子, 很简单的.
参考1
参考2

1. 执行文件访问动态连接库的变量

执行文件访问动态连接库中的变量, 变量的地址竟然在bss节区中. 因而用的是相对寻址[pc + offset ], pc值加上偏移量得到变量地址, 从该地址读取或写入数据.
见test中的代码:

读反汇编代码:

val = 10;
4006d1: c7 05 65 09 20 00 0a movl $0xa,0x200965(%rip) # 601040 <TMC_END>
4006d8: 00 00 00

把立即数10 送到601040地址.

读节区表.
$readelf -S test


[25] .bss NOBITS 0000000000601040 00001040
0000000000000008 0000000000000000 WA 0 0 8

读符号表

$ readelf -s test |grep 601040
7: 0000000000601040 0 NOTYPE GLOBAL DEFAULT 24 _edata
8: 0000000000601040 4 OBJECT GLOBAL DEFAULT 25 val
10: 0000000000601040 0 NOTYPE GLOBAL DEFAULT 25 __bss_start
25: 0000000000601040 0 SECTION LOCAL DEFAULT 25
53: 0000000000601040 0 NOTYPE GLOBAL DEFAULT 24 _edata
55: 0000000000601040 4 OBJECT GLOBAL DEFAULT 25 val
65: 0000000000601040 0 NOTYPE GLOBAL DEFAULT 25 __bss_start
68: 0000000000601040 0 OBJECT GLOBAL HIDDEN 24 TMC_END

有很多符号等于这个地址,并且__bss_start,_edata,val 符号都出现了2次, 是什么原因呢?

我们把test文件的符号表去掉: strip test,
然后对比发现,strip 会把debug所有信息表, symtab表.strtab表都会被删除.
此时 readelf -s test, 符号个数大为减少,如下:
显示的只有动态符号了.
也可以不strip 符号, 用 readelf –dyn-sym test 只显示动态符号.

2.执行文件动态符号表 .dynsym

$ readelf -s test
Symbol table ‘.dynsym’ contains 13 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTab
2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2.5 (2)
3: 0000000000000000 0 NOTYPE WEAK DEFAULT UND gmon_start
4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND func
5: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses
6: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable
7: 0000000000601040 0 NOTYPE GLOBAL DEFAULT 24 _edata
8: 0000000000601040 4 OBJECT GLOBAL DEFAULT 25 val
9: 0000000000601048 0 NOTYPE GLOBAL DEFAULT 25 _end
10: 0000000000601040 0 NOTYPE GLOBAL DEFAULT 25 __bss_start
11: 0000000000400580 0 FUNC GLOBAL DEFAULT 11 _init
12: 0000000000400774 0 FUNC GLOBAL DEFAULT 14 _fini

由此可见, 动态符号是程序运行所必须的,不能被strip删除, 未定义的外部符号是共享库的函数地址,包括libc和其它共享库,实际上就是导入表.
有定义的符号就是导出表.,包括导出的变量.val 就是其中之 一, 其它共享库要重定位val的地址.
看源代码我们知道, val 本来是在common.c 中定义的,但现在它被映射到test的.bss 区域, 成了test 的导出符号.

3. 共享对象.got 表

再来看看common.so 中是怎样访问val 变量的.

int func(void)
{
6a5: 55 push %rbp
6a6: 48 89 e5 mov %rsp,%rbp
return (val+10);
6a9: 48 8b 05 28 09 20 00 mov 0x200928(%rip),%rax # 200fd8 <_DYNAMIC+0x1c8>
6b0: 8b 00 mov (%rax),%eax
6b2: 83 c0 0a add $0xa,%eax
}
6b5: 5d pop %rbp
6b6: c3 retq

val 的取值过程是:1. 从200fd8处取到一个地址, 我用gdb跟踪过, 这个取到的地址就是test中val的导出地址601040,当时看到这个地址, 感到很惊讶, 知道又看见了一个秘密!
2. 当然是从该地址中取到数据. 进行数据操作了.

下面看看这个神奇的200fd8 是什么地址? 是谁把val的地址601040放进去的?(当然是加载器了!, 加载器把执行文件和动态库沟通了.)

$readelf -s common.so |grep 200fd8 竟然什么都没有查到, 看来它不是一个符号地址,
看看它落入那个节:


[19] .got PROGBITS 0000000000200fd0 00000fd0
0000000000000030 0000000000000008 WA 0 0 8

原来落入.got 节,是他的第2项entry

共享库要访问变量, 由于当前变量地址不能确定,所以定义了一个全局偏移地址表, 专门储存变量的地址, 在运行时,
这个表要由加载器完成初始化,填好对应的映射地址!
顺便看看.got中都有什么内容.
$ readelf -x .got common.so

Hex dump of section ‘.got’:
0x00200fd0 00000000 00000000 00000000 00000000 …………….
0x00200fe0 00000000 00000000 00000000 00000000 …………….
0x00200ff0 00000000 00000000 00000000 00000000 …………….
原来它的初始化值全是0,
再看看common.o 的动态重定位项都是什么?

4.共享对象重定位表

$ readelf -r common.so

Relocation section ‘.rela.dyn’ at offset 0x468 contains 9 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000200df8 000000000008 R_X86_64_RELATIVE 670
000000200e00 000000000008 R_X86_64_RELATIVE 630
000000201028 000000000008 R_X86_64_RELATIVE 201028
000000200fd0 000200000006 R_X86_64_GLOB_DAT 0000000000000000 _ITM_deregisterTMClone + 0
000000200fd8 000800000006 R_X86_64_GLOB_DAT 0000000000201030 val + 0
000000200fe0 000300000006 R_X86_64_GLOB_DAT 0000000000000000 gmon_start + 0
000000200fe8 000400000006 R_X86_64_GLOB_DAT 0000000000000000 _Jv_RegisterClasses + 0
000000200ff0 000500000006 R_X86_64_GLOB_DAT 0000000000000000 _ITM_registerTMCloneTa + 0
000000200ff8 000600000006 R_X86_64_GLOB_DAT 0000000000000000 __cxa_finalize + 0

Relocation section ‘.rela.plt’ at offset 0x540 contains 2 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000201018 000300000007 R_X86_64_JUMP_SLO 0000000000000000 gmon_start + 0
000000201020 000600000007 R_X86_64_JUMP_SLO 0000000000000000 __cxa_finalize + 0
看到原来.got表项地址原来都是 R_X86_64_GLOB_DAT 全局数据型重定位.
又要多说一句了, 重定位项中.rela.plt 节包含2项jump_slot型重定位, 这很容易理解,是外部函数地址,但.rela.dyn中除了.got的6个dat重定位, 还有3个relative 重定位,是什么意思呢?
经查,它们分别属于.init_array, .fini_array, .data 节.
这三项的具体含义还要再调查.
relative 重定位项是需要rebase的,就是说目前的值是以位置0为基础的,将来模块加载到位置A, 则其对应项都要加上A.

计算机中有句名言,现在确定不了的事情,就放到下一步去做. 不知道是别人说的还是我说的, 反正.got表就是这么做的.

5. 共享对象PIC 是如何实现的.

PIC代码对数据的访问采用了相对寻址方式[PC+OFFSET], PC 随加载地址不同而不同,它抵消了加载位置的变动. OFFSET 是固定的,

所以无论程序放到何处,都能从固定偏移中取到正确的数据,或跳转到固定偏移处,这就是所谓的位置无关代码了.

所谓PIC 位置无关代码,是说代码中不能有重定位信息, 要把代码部分固定下来, 放到什么位置都可以正常运行. 那与位置相关的部分必需要从代码中剥离或者去除, 可以把他们放到另一个段, 这里就是.GOT, 具有可读可写属性,.GOT 可以认为是一种数据段.加载器可以对它进行修改使它初始化为正确的数据.
重定位的方式依赖于.rel.dyn, .rel.plt, .rel.plt 相当于是对函数引用的修正,修正位置在.got.plt, .rel.dyn 是对数据引用的修正,修正的位置在.got和数据段. 对数据段的修正是rebase, r_relative 类型.