【问题标题】:Getting wrong glibc function address on ARM在 ARM 上获取错误的 glibc 函数地址
【发布时间】:2016-04-27 03:27:34
【问题描述】:

我想得到一个函数的地址。对于本地函数和 glibc 函数,使用函数名称可以在 x86 上获得正确的地址。

但在 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 给出了正确的地址。

【问题讨论】:

标签: c function gdb arm glibc


【解决方案1】:

无法保证指针的值实际上为您提供了您正在寻找的东西的内存地址。对于函数指针,您实际上更有可能拥有完全不同的值。

下面我只是完全过度了,但删除这个解释会很遗憾。所以这里有一个简短的版本:函数指针只保证与另一个函数指针的比较会比较相等,当涉及到共享库时,这会很快变得复杂。

这里发生的事情与动态链接有关。当您链接程序时,链接器不知道 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代表Global Offset Table,PLT代表Procedure Linkage Table。

我们去兔子洞吧:

(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 函数。为什么?不知道。有人决定在某个时候以这种方式实现它。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-10-12
    • 2019-05-05
    • 2014-02-23
    • 2013-03-24
    • 1970-01-01
    • 1970-01-01
    • 2013-07-25
    • 1970-01-01
    相关资源
    最近更新 更多