Content #
生成地址无关代码时,碰到模块内部的数据访问。很明显,指令中不能直接包含数据的绝对地址,只能使用相对寻址。
现代的体系结构中,数据的相对寻址往往没有相对与当前指令地址(PC)的寻址方式,所以ELF用了一个很巧妙的办法来得到当前的PC值,然后再加上一个偏移量就可以达到访问相应变量的目的了。
0000044c <bar>:
44c: 55 push %ebp
44d: 89 e5 mov %esp,%ebp
44f: e8 40 00 00 00 call 494 <__i686.get_pc_thunk.cx>
454: 81 c1 8c 11 00 00 add $0x118c,%ecx
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
470: 5d pop %ebp
471: c3 ret
00000494 <__i686.get_pc_thunk.cx>:
494: 8b 0c 24 mov (%esp),%ecx
497: c3 ret
从call指令中可以看到,它先调用了一个叫“__i686.get_pc_thunk.cx”的函数,这个函数的作用就是把返回地址的值放到ecx寄存器,即把call的下一条指令的地址放到ecx寄存器。
当处理器执行call指令以后,下一条指令的地址会被压到栈顶,而esp寄存器就是始终指向栈顶的,那么当“__i686.get_pc_thunk.cx”执行“mov (%esp),%ecx”的时候,返回地址就被赋值到ecx寄存器了。
接着执行一条add指令和一条mov指令,可以看到变量a的地址是add指令地址(保存在ecx寄存器)加上两个偏移量0x118c和0x28,即如果模块被装载到 0x10000000这个地址的话,那么变量a的实际地址将是0x10000000 + 0x454 + 0x118c + 0x28 = 0x10001608。
From #
程序员的自我修养