说在运行时,我想找出在哪里定义了函数“ 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”或任何其他函数的来源?
答案 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
来指向指针:
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?可以找到更多有关此的信息。
答案 3 :(得分:8)
在运行时说,我想找出在哪里定义了函数“ 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。
答案 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