我想获取一个函数的地址。使用函数名在x86上获取正确的地址,包括本地函数和glibc函数。
但是在ARM上,本地函数地址是正确的,而glibc函数地址是错误的。
这是我的简单程序:
#include <stdio.h>
int sum(int a, int b)
{
return a + b;
}
int main(int argc, char *argv[])
{
char buffer[32] = { '\0' };
sprintf(buffer, "cat /proc/%d/maps", getpid());
printf("sum = %p\n", sum);
printf("fopen = %p\n", fopen);
system(buffer);
return 0;
}
# x-compile it to an ARM executable:
$ arm-linux-gnueabihf-4.9.1-gcc -g -o misc misc.c
# debug on ARM
/home # ./gdb ./misc
GNU gdb (GDB) 7.5.1
Copyright (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "arm-linux".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/misc...done.
(gdb) b 16
Breakpoint 1 at 0x8534: file misc.c, line 16.
(gdb) r
Starting program: /home/misc
sum = 0x8491
fopen = 0x835c
00008000-00009000 r-xp 00000000 00:13 1703976 /home/misc
00010000-00011000 rw-p 00000000 00:13 1703976 /home/misc
76ed9000-76fd0000 r-xp 00000000 1f:08 217 /lib/libc-2.19-2014.06.so
76fd0000-76fd7000 ---p 000f7000 1f:08 217 /lib/libc-2.19-2014.06.so
76fd7000-76fd9000 r--p 000f6000 1f:08 217 /lib/libc-2.19-2014.06.so
76fd9000-76fda000 rw-p 000f8000 1f:08 217 /lib/libc-2.19-2014.06.so
76fda000-76fdd000 rw-p 00000000 00:00 0
76fdd000-76ff7000 r-xp 00000000 1f:08 199 /lib/ld-2.19-2014.06.so
76ffb000-76ffe000 rw-p 00000000 00:00 0
76ffe000-76fff000 r--p 00019000 1f:08 199 /lib/ld-2.19-2014.06.so
76fff000-77000000 rw-p 0001a000 1f:08 199 /lib/ld-2.19-2014.06.so
7efdf000-7f000000 rw-p 00000000 00:00 0 [stack]
ffff0000-ffff1000 r-xp 00000000 00:00 0 [vectors]
Breakpoint 1, main (argc=1, argv=0x7efffe64) at misc.c:16
16 misc.c: No such file or directory.
(gdb) p fopen
$1 = {<text variable, no debug info>} 0x76f26a50 <fopen>
(gdb)
注意glibc文本段映射到地址 76ed9000 ,那么fopen
如何处于 0x835c 等有线地址?
但是,下一行(gdb) p fopen
,gdb会给出正确的地址。
答案 0 :(得分:2)
无法保证指针的值实际上为您提供了您正在寻找的内容的内存地址。对于函数指针,您实际上更有可能拥有完全不同的值。
下面我完全过分了,但是删除这个解释会很遗憾。所以这里是一个简短的版本:函数指针只保证与另一个函数指针的比较将相等,当涉及共享库时,这会变得很复杂。
这里发生的事情与动态链接有关。当您链接程序时,链接器不知道libc在内存中的位置,这只能由动态链接器在运行时解决。一个天真的方法是只重写程序代码中的函数地址,但这是低效的,因为这意味着程序的每次执行都无法与其他运行共享可执行文件的内存。相反,存在称为PLT的东西。当您对动态链接函数执行函数调用时,运行的实际代码是跳转到程序中的本地函数,然后从表中加载函数的实际地址并跳转到该函数(这在不同的体系结构上是非常不同的) ,但这是一般的想法。)
我在amd64上构建了你的程序,让我们看看它的实际效果:
(gdb) break system
Breakpoint 1 at 0x400510
(gdb) run
Starting program: /home/art/./foo
sum = 0x400660
fopen = 0x400550
[...]
正如你在amd64上看到的那样,fopen的价值也是可疑的。让我们看一下该地址潜伏的代码:
(gdb) x/i 0x400550
0x400550 <fopen@plt>: jmpq *0x200aea(%rip) # 0x601040 <fopen@got.plt>
我们首先注意到的是gdb已经知道这实际上并不是fopen
,但是内存中的这个特定位置称为fopen@plt
。并且它只是一条指令:跳转到指针指针的值加上0x200aea
(linux / amd64几乎所有寻址相对于指令指针)gdb很好地告诉我们的是地址0x601040
,恰好名为fopen@got.plt
。 GOT代表全局偏移表,PLT代表程序链接表。
让我们走下兔子洞:
(gdb) x/g 0x601040
0x601040 <fopen@got.plt>: 0x0000000000400556
(gdb) x/i 0x0000000000400556
0x400556 <fopen@plt+6>: pushq $0x5
(gdb)
0x40055b <fopen@plt+11>: jmpq 0x4004f0
(gdb) x/i 0x4004f0
0x4004f0: pushq 0x200b12(%rip) # 0x601008
(gdb)
0x4004f6: jmpq *0x200b14(%rip) # 0x601010
(gdb) x/g 0x601010
0x601010: 0x00007ffff7df0290
(gdb) x/i 0x00007ffff7df0290
0x7ffff7df0290 <_dl_runtime_resolve>: sub $0x78,%rsp
(gdb)
这里发生了一些奇怪的事。 fopen@got.plt
中的地址只返回fopen@plt
之后的一条指令,然后在堆栈上推送一些内容并跳转到其他一些代码,这些代码将更多内容推送到堆栈并跳转以从表中获取另一个奇怪的地址结束了我们_dl_runtime_resolve
。发生了什么是懒惰的约束。动态链接器的开发人员发现动态库和程序包含的大多数链接信息永远不会被使用。当您运行从libc调用两个函数的程序时,您不想解决libc在内部执行的所有成千上万的动态函数调用,这是浪费时间。此外,我们重视大多数程序的快速运行时快速启动。因此,默认情况下,您的所有功能都没有实际解决。它们会在运行时解析,第一次调用它们。这是_dl_runtime_resolve
的作用。推送到堆栈很可能是将参数传递给该函数的非标准方法,因为此代码不允许使用任何寄存器(调用代码认为它通常只调用fopen
)。
但等一下。 C标准说如果两个函数指针指向相同的函数,它们应该相等。如果其中一个指针来自您的程序而另一个指针来自动态库,那该怎么办?嗯,这非常依赖于架构,但经过一些挖掘后,我发现在我的架构上,即使库返回一个函数指针,该函数指针也会转换为我主程序中的PLT函数。为什么?不知道。有人决定在某个时候以这种方式实施它。