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

时间:2018-10-01 21:35:27

标签: 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);
}

输出:

-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”或任何其他函数的来源?

6 个答案:

答案 0 :(得分:31)

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

目的是,外部链接仅在PLT的一个位置发生,而不在代码中调用符号的所有位置发生。因此,如果调用printf(),则方法是:

  

main-> printf @ PLT-> printf @ libc

在运行时,您无法轻松找出调用的函数位于哪个外部库中;您将必须解析目标位置(PLT)上的操作码,该操作码通常是从.dynamic节中获取地址并跳转到该地址,然后查看符号的真正位置,最后,解析/ proc / pid / maps以获得外部库。

答案 1 :(得分:23)

在运行时,您可以使用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]

答案 2 :(得分:9)

    使用printf而非%p %X来指向
  1. 指针:

    printf("address of printf is 0x%p\n", printf);
    
  2. 如果您针对静态libc printf进行编译,则会链接到您的二进制文件中

  3. 使用编译时

    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?可以找到更多有关此的信息。

答案 3 :(得分:8)

  

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

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

如果您构建Linux系统from scratch,则可以梦想在构建时对每个库进行处理(例如,在构建每个共享库时,可以使用nm(1)获得所有的公共名称,将它们放在某个数据库中)。今天尚未真正做到这一点,但是一些研究项目正在朝这个方向发展(特别是softwareheritage,2019年还有其他项目)。

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

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

  

该功能在我自己的程序中定义

是的。详细了解dynamic linking。特别是,请阅读Drepper的How to Write Shared Libraries论文。了解what is the purpose of procedure linkage table

答案 4 :(得分:0)

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

答案 5 :(得分:0)

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

$ 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