考虑以下代码:
$ 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
中。
链接器将执行重定位,使得
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
的偏移量。