c - 在 ARM 上获取错误的 glibc 函数地址

标签 c function gdb arm glibc

我想获取一个函数的地址。使用函数名称可以在 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 给出了正确的地址。

最佳答案

无法保证指针的值实际上为您提供了您要查找的内容的内存中地址。对于函数指针,实际上更有可能具有完全不同的值。

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

这里发生的事情与动态链接有关。当您链接程序时,链接器不知道 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 <<a href="https://stackoverflow.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="791f16091c17391e160d5709150d" rel="noreferrer noopener nofollow">[email protected]</a>>

我们可以注意到的第一件事是 gdb 已经知道这实际上不是 fopen但内存中的这个特定位置称为 fopen@plt 。这只是一条指令:跳转到指令指针加上 0x200aea 处的指针值。 (linux/amd64 几乎完成了所有与指令指针相关的寻址)gdb 很好地告诉我们的是地址 0x601040并且恰好被命名为<a href="https://stackoverflow.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="64020b14010a24030b104a140810" rel="noreferrer noopener nofollow">[email protected]</a> 。 GOT 代表全局偏移表,PLT 代表过程链接表。

让我们深入兔子洞:

(gdb) x/g 0x601040
0x601040 <<a href="https://stackoverflow.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="d3b5bca3b6bd93b4bca7fda3bfa7" rel="noreferrer noopener nofollow">[email protected]</a>>:   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)

这里发生了一些奇怪的事情。地址<a href="https://stackoverflow.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="99fff6e9fcf7d9fef6edb7e9f5ed" rel="noreferrer noopener nofollow">[email protected]</a>只是回到 fopen@plt 之后的一条指令,然后将一些内容压入堆栈并跳转到其他代码,将更多内容压入堆栈并跳转以从表中获取另一个奇怪的地址,最终我们得到 _dl_runtime_resolve 。发生的事情是惰性绑定(bind)。动态链接器的开发人员发现动态库和程序包含的大多数链接信息永远不会被使用。当您运行从 libc 调用两个函数的程序时,您不想解析 libc 内部执行的所有成千上万个动态函数调用,这是浪费时间。此外,对于大多数程序来说,我们更看重快速启动而不是快速运行。因此,默认情况下,您的所有功能实际上都没有得到解决。当你第一次调用它们时,它们会在运行时得到解决。这就是_dl_runtime_resolve做。推送到堆栈很可能是向该函数传递参数的非标准方式,因为不允许此代码使用任何寄存器(调用代码认为它只是正常调用 fopen)。

但是等一下。 C 标准规定,如果两个函数指针指向同一个函数,则它们应该相等。如果其中一个指针可能来自您的程序而另一个指针来自动态库,那么它是如何工作的?嗯,这在很大程度上取决于体系结构,但经过一番挖掘后,我发现在我的体系结构上,即使库返回函数指针,该函数指针也会转换为主程序中的 PLT 函数。为什么?不知道。有人决定在某个时候以这种方式实现它。

关于c - 在 ARM 上获取错误的 glibc 函数地址,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34914362/

相关文章:

c - 在 C 中,一个长的 printf 语句可以分成多行吗?

c - 这句话到底是什么意思?

c++ - C++ 模板的函数解析是如何完成的?

shell - 来自外部程序或 shell 的 LLDB 设置参数

c - 在 Pic32 中启用 DHCP

c - 有什么建议或改进吗?

javascript - 如何创建一个将任何给定字符串与第二个参数连接起来的函数?

c++ - C++将函数调用列表作为参数传递

debugging - 海湾合作委员会/gdb : How to embed absolute path to source file in debug information?

c++ - GDB 和 C++ : Printing vector of pointers to objects