1.类型一 模块内部调用或跳转--相对地址调用/跳转 偏移量=目标地址 - 下一条指令的地址
8048344 <bar>:
8048344: 55 push %ebp
....
8048349 <foo>:
8048357: e8 e8 ff ff ff call 8048344<bar>
804835C: ...
2.类型二 模块内部数据访问--当前指令地址加上固定的偏移量
0000044c <bar>:
.....
44f: e8 40 00 00 00 call 494<__i686.get_pc_thunk.cx>//把下一条指令地址压栈,跳转
454: 81 c1 8c 11 00 00 add $0x118c,%ecx //%ecx=0x454 + 0x118C,GOT表地址
45a: c7 81 28 00 00 00 01 movl %0x1,0x28(%ecx) //a=1
.....
494: <__i686.get_pc_thunk.cx>:
494: 8b 0c 24 mov (%esp),%ecx
497: c3 ret
a的实际地址是0x10000000+0x454 + 0x118c + 0x28 = 0x10001608
3.类型三 模块间数据访问--当前指令地址加上固定的偏移,得到GOT表地址,然后根据变量地址在GOT中的偏移就可以得到变量的地址
0000044c <bar>:
.....
44f: e8 40 00 00 00 call 494 <__i686.get_pc_thunk.cx>
454: 81 c1 8c 11 00 00 add $0x118c,%ecx //%ecx=0x454 + 0x118C,GOT表地址
45a: c7 81 28 00 00 00 01 movl $0x1,0x28(%ecx) //a = 1
461: 00 00 00
464: 8b 81 f8 ff ff ff mov 0xfffffff8(%ecx),%eax
46a: c7 00 02 00 00 00 movl $0x2,(%eax) //b = 2
.....
494: <__i686.get_pc_thunk.cx>:
494: 8b 0c 24 mov (%esp),%ecx
497: c3 ret
程序首先计算出变量b的地址在GOT中的位置:即0x10000000 + 0x454 + 0x118c +(-8) = 0x100015d8,然后使用寄存器ji
间接寻址方式给变量b赋值2.
注意:
共享模块中定义的全局变量也采用此类型来访问,GOT表指向可执行文件的.bss。
类型四 模块间调用, 跳转--当前指令地址加上固定的偏移,得到GOT表地址,然后根据函数地址在GOT中的偏移就可以得到函数 的地址
call 494 <__i686.get_pc_thunk.cx>
add $0x118c,%ecx //%ecx=0x454 + 0x118C,GOT表地址
mov 0xfffffffc(%ecx),%eax
call *(%eax)
.....
494: <__i686.get_pc_thunk.cx>:
494: 8b 0c 24 mov (%esp),%ecx
497: c3 ret
注意:
PLT(Procedure Linkage Table)为了实现延迟绑定,在这个过程中间又增加了一层间接跳转。调用函数并不直接通过GOT跳转,而是通过一个叫做PLT项的结构来进行跳转。每个外部函数在PLT中都有一个相应的项,比如bar()函数在PLT中的项的地址我们称之为bar@plt.看看bar@plt的实现
示例的PLT结构:
bar@plt:
jmp *(bar@GOT)
push n
push moduleID
jump _dl_runtime_resolve
为了实现延迟绑定,链接器在初始化的阶段并没有将bar()的实际地址填入到bar@GOT,而是将上面代码中的第二条指令push n 的地址填入到bar@GOT中。数字 n是bar这个符号引用在重定位表".rel.plt"中的下标。_dl_runtime_resolve()函数来完成符号解析和重定位工作。_dl_rutime_resolve()在进行一系列工作以后将bar()的真正地址填入到bar@GOT中。
ELF将GOT拆分成了两个表叫做".got"和".got.plt".其中".got"用来保存全局变量引用的地址,"got.plt"用来保存函数引用的地址。".got.plt"前三项有特殊意义:
第一项保存的是".dynamic"段的地址,这个段描述了本模块动态链接相关的信息。
第二项保存的是本模块的ID.
第三项保存的是_dl_runtime_resolve()的地址。
为了减少代码的重复,ELF把上面示例中的最后两条指令放到PLT中的第一项。并且归定每一项的长度是16个字节,刚好用来存放三条指令。
PLT0:
push *(GOT + 4) //本模块的ID
jmp *(GOT+8) //跳转到_dl_runtime_resolve(),完成符号解析和重定位工作,bar@GOT填入真正地址
...
bar@plt:
jmp *(bar@GOT)
push n //数字 n是bar这个符号引用在重定位表".rel.plt"中的下标
jump PLT0
PLT在ELF文件中以独立的节存放,节名通常为".plt".因为他本身是一些地址无关的代码,所以可以跟.text节等一起合并一个可读可执行的Segment被装载入内存。