模块内部的数据访问(PIC)

模块内部的数据访问(PIC)

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 #

程序员的自我修养