为什么用 -fPIC -shared 编译的共享对象文件的反汇编会引入“call 4”和“add 2”等虚拟地址?

问题描述 投票:0回答:1

考虑以下代码:

$ cat foo.c
static int foo = 100;

int function(void)
{
    return foo;
}

我了解了libfoo.so的反汇编

$ gcc -m32 -fPIC -shared -o libfoo.so foo.c
$ objdump -D libfoo.so

000004cc <function>:
 4cc:   55                      push   %ebp
 4cd:   89 e5                   mov    %esp,%ebp
 4cf:   e8 0e 00 00 00          call   4e2 <__x86.get_pc_thunk.cx>
 4d4:   81 c1 c0 11 00 00       add    $0x11c0,%ecx
 4da:   8b 81 18 00 00 00       mov    0x18(%ecx),%eax
 4e0:   5d                      pop    %ebp
 4e1:   c3                      ret    

000004e2 <__x86.get_pc_thunk.cx>:
 4e2:   8b 0c 24                mov    (%esp),%ecx
 4e5:   c3                      ret    
 4e6:   66 90                   xchg   %ax,%ax
...

000016ac <foo>:
    16ac:   64 00 00                add    %al,%fs:(%eax)

function
中,
foo
的地址计算为0x4d4(调用
ecx
__x86.get_pc_thunk.cx
的值)+ $0x11c0 + 0x18 = 0x16ac。 0x16ac 是
foo
的地址。

但是我不明白

的拆解
$ gcc -m32 -fPIC -shared -c foo.c
$ objdump -D foo.o
00000000 <function>:
   0:   55                      push   %ebp
   1:   89 e5                   mov    %esp,%ebp
   3:   e8 fc ff ff ff          call   4 <function+0x4>
   8:   81 c1 02 00 00 00       add    $0x2,%ecx
   e:   8b 81 00 00 00 00       mov    0x0(%ecx),%eax
  14:   5d                      pop    %ebp
  15:   c3                      ret    

00000000 <foo>:
   0:   64 00 00                add    %al,%fs:(%eax)

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

为什么

call   4 <function+0x4>
为什么
add    $0x2,%ecx

更新:(向 objdump 添加了 -r 标志,-R 标志产生错误

not a dynamic object, Invalid operation

$ objdump -D -r foo.o
00000000 <function>:
   0:   55                      push   %ebp
   1:   89 e5                   mov    %esp,%ebp
   3:   e8 fc ff ff ff          call   4 <function+0x4>
        4: R_386_PC32   __x86.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_GOTOFF    .data
 14:    5d                      pop    %ebp
 15:    c3                      ret    

现在

4
call   4 <function+0x4>
中有意义,因为该指令在文本部分的偏移量是4。我仍然不知道为什么
0x2
add    $0x2,%ecx
中。

c linux assembly x86 position-independent-code
1个回答
3
投票

链接器将执行重定位,使得

final value
=
symbol
+
offset
-
PC
。请注意,此公式中的
PC
是重定位本身的地址,而不是指令的地址,因为链接器不知道指令边界。然而,汇编器知道它们并且可以创建正确的偏移量。

让我们看看

call __x86.get_pc_thunk.cx
是如何工作的。在 x86 上,
call
指令使用相对寻址,但
PC
的值已经递增以指向后续指令。您可以在第一个转储中验证这一点:

 4cf:   e8 0e 00 00 00          call   4e2 <__x86.get_pc_thunk.cx>
 4d4:   81 c1 c0 11 00 00       add    $0x11c0,%ecx

注意指令中的偏移量是

0e
。已经递增的
PC
4d4
并且肯定是跳转的目标
4e2
=
4d4
+
0e
(所有数字均为十六进制)。

现在是迁移后的版本:

   3:   e8 fc ff ff ff          call   4 <function+0x4>
        4: R_386_PC32   __x86.get_pc_thunk.cx

它使用

R_386_PC32
,但它位于指令的第二个字节,而
call
需要与更新的
PC
之间的偏移量,这显然多了
4
字节。这意味着正确的结果是
4
少,因此指令包含
fffffffc
,即
-4
。请注意,无论
call
的地址是什么,该偏移量始终是
-4
。反汇编器会自动将其添加到更新的
PC
中,在本例中为
8
,因此它通过执行
call 4
到达
8-4

好的,继续

R_386_GOTPC

   3:   e8 fc ff ff ff          call   4 <function+0x4>
        4: R_386_PC32   __x86.get_pc_thunk.cx
  8:    81 c1 02 00 00 00       add    $0x2,%ecx
        a: R_386_GOTPC  _GLOBAL_OFFSET_TABLE_

__x86.get_pc_thunk.cx
函数只是将返回地址从堆栈加载到寄存器
ecx
中。本例中的返回地址是
8
。要实现的目标是在
_GLOBAL_OFFSET_TABLE_
中拥有
ecx
的地址。我们需要知道它距离
PC
中已有的参考
ecx
有多远,并添加该距离。为此,使用了
R_386_GOTPC
重定位,但这会给出地址
0a
的偏移量,因为那是重定位条目所在的位置。与地址
8
的偏移量当然会更多
2
。这个
2
是指令中编码的内容。

总结一下:指令中存储的重定位偏移量是重定位地址与所需参考点的差值:

offset
=
PC
-
reference
。在第一种情况下,该参考点高
4
字节,在第二种情况下,低
2
字节,分别给出
-4
2
的偏移量。

© www.soinside.com 2019 - 2024. All rights reserved.