如何通过loader引用共享库中的变量?

时间:2011-06-10 13:19:38

标签: shared-libraries elf

我现在通过下面的过程链接表了解如何引用动态函数:

Dump of assembler code for function foo@plt:
0x0000000000400528 <foo@plt+0>: jmpq   *0x2004d2(%rip)        # 0x600a00 <_GLOBAL_OFFSET_TABLE_+40>
0x000000000040052e <foo@plt+6>: pushq  $0x2
0x0000000000400533 <foo@plt+11>:    jmpq   0x4004f8
(gdb) disas 0x4004f8
No function contains specified address.

但是我不知道动态变量是如何被引用的,虽然我发现这些值一旦启动就在GOT中填充,但是没有像上面那样的存根,它是如何工作的?

2 个答案:

答案 0 :(得分:1)

动态加载程序在将控制转移到用户程序之前重定位所有对变量的引用。

它们没有“存根”,因为一旦用户程序开始执行,加载程序就无法重新获得控制权并更新变量地址。如果您不清楚这一点,那么您还没有真正了解PLT延迟解析存根的工作原理。

答案 1 :(得分:0)

通过全局偏移表间接访问全局变量。

  • 编译程序时,编译器会生成执行的代码 间接访问,并发出指定的重定位信息 正在使用的全局偏移表中的条目。
  • 链接器在创建final时执行这些重定位 动态可加载对象,导致机器代码没有 需要在加载时进一步修补。

要查看此操作,请考虑以下代码片段。

int v1;
int f(void) { return !v1; }

函数f引用全局v1。机器码生成 该函数如下所示(在i386上):

% gcc -c -fpic a.c
% objdump --disassemble --reloc a.o
[snip]
Disassembly of section .text:

00000000 <f>:
   0:   55                      push   %ebp
   1:   89 e5                   mov    %esp,%ebp
   3:   e8 fc ff ff ff          call   4 <f+0x4>
            4: R_386_PC32   __i686.get_pc_thunk.cx
   8:   81 c1 02 00 00 00       add    $0x2,%ecx
            a: R_386_GOTPC  _GLOBAL_OFFSET_TABLE_
   e:   8b 81 00 00 00 00       mov    0x0(%ecx),%eax
            10: R_386_GOT32 v1
  14:   8b 00                   mov    (%eax),%eax
  16:   85 c0                   test   %eax,%eax
  18:   0f 94 c0                sete   %al
  1b:   0f b6 c0                movzbl %al,%eax
  1e:   5d                      pop    %ebp
  1f:   c3                      ret    

Disassembly of section .text.__i686.get_pc_thunk.cx:

00000000 <__i686.get_pc_thunk.cx>:
   0:   8b 0c 24                mov    (%esp),%ecx
   3:   c3                      ret

机器代码演练:

  • (偏移0x0和0x1)标准功能序言。
  • (偏移0x3)__i686.get_pc_thunk.cx的调用准备 通过加载指令的地址来进行PC相对寻址 在致电注册%ecx
  • 之后
  • (偏移量0x8) %ecx中的值被调整为指向开始 全局偏移表的。这种调整由信号发出信号 类型R_386_GOTPC的重定位条目。
  • (偏移量0xE)检索全局v1的地址。该 R_386_GOT32重定位提供v1条目的偏移量 全局偏移表的基础。
  • (偏移量0x14) v1中的值被检索到寄存器%eax
  • (偏移0x16--0x1F)函数f的其余计算。

在最终的共享对象中,链接器将函数的代码修补为 以下内容:

% gcc -shared -o a.so a.o
% objdump --disassemble a.so
...snip...
0000044c <f>:
 44c:   55                      push   %ebp
 44d:   89 e5                   mov    %esp,%ebp
 44f:   e8 18 00 00 00          call   46c <__i686.get_pc_thunk.cx>
 454:   81 c1 a0 1b 00 00       add    $0x1ba0,%ecx
 45a:   8b 81 f8 ff ff ff       mov    -0x8(%ecx),%eax
 460:   8b 00                   mov    (%eax),%eax
 462:   85 c0                   test   %eax,%eax
...snip...
  • 假设对象在内存中的偏移 O 处加载,则 偏移0x44F处的调用指令将加载 O + 0x454 + 0x1BA0,即 O + 0x1FF4进入%ecx
  • 偏移0x45A处的指令从%ecx中减去8 获取全局偏移表中v1的槽位地址, 即,v1的时隙距离开始时的偏移量为0x1FEC 共享对象。

查看共享对象的动态重定位记录,我们 查看指示运行时加载程序存储的重定位记录 偏移量为0x1FEC的v1的实际地址。

% objdump -R a.so
DYNAMIC RELOCATION RECORDS
OFFSET   TYPE              VALUE
...snip...
00001fec R_386_GLOB_DAT    v1
...snip...

进一步阅读: