LinuxC程序:如何查找函数所属的库

LinuxC程序:如何查找函数所属的库,c,linux,C,Linux,比如在运行时,我想找出函数“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);

比如在运行时,我想找出函数“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$head/proc/28837/maps

00400000-00401000 r-xp 00000000 08:06 6946857/data2/temp/del/a您观察到的地址位于程序链接表(PLT)中。当编译和链接二进制文件时,如果不知道外部(动态链接)符号的位置,则使用此机制

这样做的目的是,外部链接只发生在一个地方,即PLT,而不是在代码中调用符号的所有地方。因此,如果调用了
printf()
,方法是:

主要->printf@PLT -> printf@libc

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

  • 指针是使用
    %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
    

  • 阅读以了解更多信息。

    解析elf文件以获得所需的动态链接库。然后,您可以在运行时搜索所需的符号来解析它们,您可以使用
    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]
    
    比如在运行时,我想找出函数“printf”的定义位置

    从一般和绝对的角度来看,你可能不能(至少不容易)。给定的函数可能在多个库中定义(对于
    printf
    ,这是不可能的;因为它位于C标准库中)

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

    顺便说一句,您可以有几个库定义
    printf
    。例如,如果您在计算机上同时安装GNU和(或者更可能的情况是,如果您有多个版本的
    glibc
    )。一个特定的程序不太可能同时使用这两种方法(但理论上仍然可以同时使用这两种方法)

    也许你想要Linux特有的功能。从某个给定的地址,它告诉您拥有它的共享对象

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


    对。阅读更多关于。特别是读德雷珀的论文。理解。

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

    $ 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
    

    哈哈。在运行时,我将如何找到它?请注意,“printf”只是一个简单的例子。伪代码
    系统(“man%s | grep\.h”)
    (只是开玩笑)您可能会在地址记录中发现一个存根,链接器使用它将程序中的调用与库中的实现连接起来。这样的存根可能对重新定位、弱符号等有用。我不知道所有不同的情况。但是存根本身通常只是一条简单的分支指令,它将程序流重定向到它的实际目的地。@ti7(和其他指令)。让我们尽量不要把库和标题混淆。@weather:那个主页上说printf在libc中的什么地方。那么?关于扫描整个Linux系统的研究项目,我参与了H2020解码器项目的提议(用于ICT-16调用)。我们得到了资金,该项目将于2019年启动。所以请继续关注!(但我们不会扫描整个Linux发行版,只扫描几个库,可能不会扫描
    libc
    )我遇到了这里描述的错误“有时,传递给dladdr()的函数指针可能会让您感到惊讶。在某些体系结构上(特别是i386和x86-64),dli_fname和dli_fbase可能最终指向调用dladdr()的对象,即使用作参数的函数应该来自动态链接的库。“好吧,最初的问题是关于任何函数,既不是关于
    printf
    (这是一个示例),也不是关于任何其他特定的glic函数。你的命令在一般情况下不起作用。它显示的只是一个版本标签,看起来是
    GLIBC_2.2.5
    ,但也可能是
    V_2.2.5
    。因为OP说的是“在运行时”,所以你不能静态地推断出任何东西,
    readelf
    不是适合这项工作的工具。不幸的是,我不能使用fPIC,这是一个巨大的工作项目,我无法更改构建过程。