Linux C程序:如何找到函数所属的库

Posted

技术标签:

【中文标题】Linux C程序:如何找到函数所属的库【英文标题】:Linux C program: How to find the library to which a function belongs 【发布时间】:2018-10-01 21:35:27 【问题描述】:

假设在运行时,我想找出函数“printf”的定义位置。我该怎么做? 我的第一次尝试是打印出“printf”的地址,并将其与进程的虚拟地址映射进行比较:

我的程序:

#include <stdio.h>
#include <unistd.h>

void main()

    printf("address of printf is 0x%X\n", printf);
    printf("pid is  %d\n", getpid());
    while (1);

输出:

-bash-4.1$ ./a &
[1] 28837
-bash-4.1$ address of printf is 0x4003F8
pid is  28837

但是,这表示该函数是在我自己的程序中定义的!

-bash-4.1$ head /proc/28837/maps 
00400000-00401000 r-xp 00000000 08:06 6946857                            /data2/temp/del/a      <<<<<<< Address 0x4003F8 is in my own program?
00600000-00601000 rw-p 00000000 08:06 6946857                            /data2/temp/del/a
397ec00000-397ec20000 r-xp 00000000 08:11 55837039                       /lib64/ld-2.12.so
397ee1f000-397ee20000 r--p 0001f000 08:11 55837039                       /lib64/ld-2.12.so
397ee20000-397ee21000 rw-p 00020000 08:11 55837039                       /lib64/ld-2.12.so
397ee21000-397ee22000 rw-p 00000000 00:00 0 
397f000000-397f18a000 r-xp 00000000 08:11 55837204                       /lib64/libc-2.12.so
397f18a000-397f38a000 ---p 0018a000 08:11 55837204                       /lib64/libc-2.12.so
397f38a000-397f38e000 r--p 0018a000 08:11 55837204                       /lib64/libc-2.12.so
397f38e000-397f38f000 rw-p 0018e000 08:11 55837204                       /lib64/libc-2.12.so

不应该是对 libc 的调用吗?如何找出这个“printf”或任何其他函数的来源?

【问题讨论】:

哈哈。在运行时,我将如何找到它?请注意,“printf”只是一个简单的例子。 伪代码system("man %s | grep \.h")(开个玩笑) 您在地址获取中可能会发现一个存根,链接器使用它来将程序中的调用与库中的实现连接起来。这样的存根可能对重定位、弱符号等有用。我不知道所有不同的情况。但存根本身通常只是将程序流重定向到其实际目的地的简单分支指令。 @ti7(和其他人)。让我们尽量不要将库与标头混淆。 @weather:该手册页在哪里说 printf 在 libc.so 中? 【参考方案1】:

您观察到的地址位于过程链接表 (PLT) 中。在编译和链接二进制文件时,当外部(动态链接)符号的位置未知时,使用此机制。

目的是,外部链接只发生在一个地方,PLT,而不是在整个代码中调用符号的所有地方。所以,如果调用printf(),方式是:

main -> printf@PLT -> printf@libc

在运行时,您无法轻易找出您调用的函数位于哪个外部库中;您必须在目的地(PLT)解析操作码,它通常从 .dynamic 部分获取地址并跳转到那里,然后查看符号的实际位置,最后解析 /proc/pid/maps 以获取外部库。

【讨论】:

【参考方案2】:

在运行时,您可以为此使用gdb

(terminal 1)$ ./a
pid is  16614
address of printf is 0x400450

(terminal 2)$ gdb -p 16614
(...)
Attaching to process 16614
(...)
0x00000000004005a4 in main ()
(gdb)

(gdb) info sym printf
printf in section .text of /lib/x86_64-linux-gnu/libc.so.6

如果你不想中断你的程序或者不愿意使用gdb,你也可以要求ld.so输出一些调试信息:

(terminal 1)$ LD_DEBUG=bindings LD_DEBUG_OUTPUT=syms ./a
pid is  17180
address of printf is 0x400450

(terminal 2)$ fgrep printf syms.17180
    17180:  binding file ./a [0] to /lib/x86_64-linux-gnu/libc.so.6 [0]: normal symbol `printf' [GLIBC_2.2.5]

【讨论】:

【参考方案3】:

    指针是printfed 使用%p,而不是%X

    printf("address of printf is 0x%p\n", printf);
    

    如果您针对静态 libc 进行编译,printf 将链接到您的二进制文件中

    编译时使用

    gcc -fPIC a.c # (older gccs)
    ...
    gcc -fno-plt a.c # (gcc 6 and above)
    

    输出:

    address of printf is 0x0x7f40acb522a0
    

    里面
    7f40acaff000-7f40accc2000 r-xp 00000000 fd:00 100687388                  /usr/lib64/libc-2.17.so
    

阅读What does @plt mean here? 了解更多信息。

【讨论】:

很遗憾,我不能使用 fPIC,这是一个巨大的工作项目,我无法更改构建过程。【参考方案4】:

说在运行时,我想找出函数“printf”是在哪里定义的。

总的来说,您可能不能(至少不容易)。一个给定的函数可能在几个库中定义(对于printf,这不太可能;因为它在C标准库中)。

如果您构建您的 Linux 系统 from scratch,您可能会梦想在构建时处理每个库(例如,在构建每个共享库时,您可以使用 nm(1) 获取其所有公共名称并将它们放在某个数据库)。今天这还没有真正完成,但一些研究项目正朝着这个方向发展(特别是 softwareheritage,以及 2019 年的其他项目)。

顺便说一句,您可以有几个库定义printf。例如,如果您在计算机上同时安装了 GNU glibc 和 musl-libc(或者更有可能,如果您有 几个 glibc 的变体)。一个特定的程序不太可能同时使用两者(但理论上仍然可以dlopen 两者都使用)。

也许您想要 Linux 特定的 dladdr(3) 函数。从某个给定的地址,它会告诉您拥有它的共享对象。

函数在我自己的程序中定义

是的。阅读更多关于dynamic linking 的信息。特别是,阅读 Drepper 的 How to Write Shared Libraries 论文。了解what is the purpose of procedure linkage table。

【讨论】:

关于扫描整个Linux系统的研究项目,我参与提出了H2020 DECODER项目(用于ICT-16调用)。我们获得了资金,该项目将于 2019 年启动。敬请期待! (但我们不会扫描整个 Linux 发行版,只会扫描几个库,而且可能不会扫描 libc 我遇到了这里描述的这个错误man7.org/linux/man-pages/man3/dladdr.3.html“有时,您传递给 dladdr() 的函数指针可能会让您感到惊讶。在某些架构(尤其是 i386 和 x86-64)上,dli_fname 和 dli_fbase 可能最终指向您调用 dladdr() 的对象,即使用作参数的函数应该来自动态链接库。”【参考方案5】:

解析所需的动态链接库的 elf 文件。然后您可以解析它们以搜索所需的符号

【讨论】:

【参考方案6】:

您可以静态推断。无需执行:

$ readelf -Ws a.out | grep printf
      1: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND printf@GLIBC_2.2.5 (2)
     51: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND printf@@GLIBC_2.2.5

【讨论】:

嗯,最初的问题是关于 any 函数,既不是 printf(这是一个例子)也不是任何其他 glic 函数。您的命令在一般情况下不起作用。它显示的只是一个版本标签,似乎是GLIBC_2.2.5,但也可能是V_2.2.5。由于 OP 说“在运行时”,你不能静态地推断出任何东西,readelf 不是适合这项工作的工具。

以上是关于Linux C程序:如何找到函数所属的库的主要内容,如果未能解决你的问题,请参考以下文章

C程序中让两个不同版本的库共存

在 Linux 上使用纯 C 项目中用 C++ 编写的库?

Unix/Linux环境C编程新手教程(22) C/C++怎样获取程序的执行时间

linux下,编写一个c语言程序实现...(详细见正文)!急!

为啥我的 gcc 编译器不能识别 bzip2 函数,但允许我包含它们所属的库?

linux 下的动态库制作 以及在python 中如何调用 c 函数库