显示Linux中可执行文件使用的所有共享库
1. 概述
在 Linux 中,二进制可执行文件通常在运行时加载共享库。有时,我们希望对程序启动时要加载哪些库有一个概览。
在本教程中,我们将了解几种列出程序使用的所有共享库的方法。
2. 库简介
在编程中,库是预编译代码片段的集合。一个库可以在不同的程序中重复使用。
在 Linux 中,库可以分为:
- 静态库:在编译时静态绑定到程序
- 共享库:在程序启动时加载并在运行时加载到内存中
接下来,让我们看看如何列出一个程序的共享库。
3.使用 ldd命令
*ldd *实用程序是一个 shell 脚本。它输出程序所需的共享库。使用此命令的语法非常简单:
ldd [option]... file...
让我们看看Vim 编辑器 需要哪些共享库:
$ ldd /usr/bin/vim
linux-vdso.so.1 (0x00007ffc75fb1000)
libgtk-3.so.0 => /usr/lib/libgtk-3.so.0 (0x00007fa4dcb5e000)
libgdk-3.so.0 => /usr/lib/libgdk-3.so.0 (0x00007fa4dca64000)
libXau.so.6 => /usr/lib/libXau.so.6 (0x00007fa4db7a9000)
....
liblzma.so.5 => /usr/lib/liblzma.so.5 (0x00007fa4db63f000)
liblz4.so.1 => /usr/lib/liblz4.so.1 (0x00007fa4db61d000)
libgcrypt.so.20 => /usr/lib/libgcrypt.so.20 (0x00007fa4db4ff000)
libgpg-error.so.0 => /usr/lib/libgpg-error.so.0 (0x00007fa4db4d8000)
ldd命令 对于列出程序的共享库非常方便。
但是,我们应该谨慎使用它,因为ldd实用程序可能会执行程序以获取共享库列表。我们不应该在不受信任的可执行文件上运行ldd命令。
4. 使用objdump和grep命令
objdump 命令 是GNU Binutils 包的成员。它显示来自目标文件的信息。要列出程序所需的共享库,我们将其与grep 结合使用:
objdump -p File | grep 'NEEDED'
让我们使用objdump命令列出vim的共享库:
$ objdump -p /usr/bin/vim | grep 'NEEDED'
NEEDED libpython3.7m.so.1.0
NEEDED libcrypt.so.2
NEEDED libpthread.so.0
NEEDED libdl.so.2
NEEDED libutil.so.1
NEEDED libm.so.6
NEEDED libselinux.so.1
NEEDED libtinfo.so.6
NEEDED libacl.so.1
NEEDED libgpm.so.2
NEEDED libc.so.6
如果我们将上面的输出与ldd命令的输出进行比较 ,我们将看到ldd命令列出的库比objdump命令多得多。例如,liblz4.so.1和 libgcrypt.so.20在ldd命令的输出中,但它们没有出现在 objdump的输出中。
这是因为objdump命令正在转储程序本身列为库的内容。但是,ldd实用程序列出了ld.so 将加载哪些库。它遵循图表,以便我们可以看到这些库在运行时将加载什么。
因此,与objdump命令相比,ldd可以更好地了解运行时需要提供的内容。
5. 使用readelf命令
与objdump一样,readelf 命令是 GNU Binutils 的一部分。它显示有关ELF 格式目标文件的信息,执行与 objdump 类似的功能。
让我们使用readelf命令检查vim 的共享库,通过grep过滤:
$ readelf --dynamic /usr/bin/vim | grep NEEDED
0x0000000000000001 (NEEDED) Shared library: [libpython3.7m.so.1.0]
0x0000000000000001 (NEEDED) Shared library: [libcrypt.so.2]
0x0000000000000001 (NEEDED) Shared library: [libpthread.so.0]
0x0000000000000001 (NEEDED) Shared library: [libdl.so.2]
0x0000000000000001 (NEEDED) Shared library: [libutil.so.1]
0x0000000000000001 (NEEDED) Shared library: [libm.so.6]
0x0000000000000001 (NEEDED) Shared library: [libselinux.so.1]
0x0000000000000001 (NEEDED) Shared library: [libtinfo.so.6]
0x0000000000000001 (NEEDED) Shared library: [libacl.so.1]
0x0000000000000001 (NEEDED) Shared library: [libgpm.so.2]
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
从上面的输出中,我们可以看到vim的共享库。
6. 读取*/proc/pid/maps*文件
如果程序已经在运行,我们还可以通过读取文件*/proc/PID/maps*来获取加载的共享库列表。
在这个文件中,每一行都描述了一个进程或线程中的一个连续虚拟内存区域。如果进程加载了共享库,该库将显示在此文件中。
同样,我们以 Vim 编辑器为例,获取其加载的共享库。
我们可以 使用*pgrep 命令获取正在运行的vim进程的PID*:
$ pgrep vim
179015
/ proc/179015/maps文件包含:
$ cat /proc/179015/maps
...
7f2cb67c3000-7f2cb67c6000 r--p 00000000 08:13 3810274 /usr/lib/libnss_files-2.31.so
7f2cb67c6000-7f2cb67cd000 r-xp 00003000 08:13 3810274 /usr/lib/libnss_files-2.31.so
..
7f2cb6a89000-7f2cb6a8a000 r--p 00002000 08:13 3810903 /usr/lib/libutil-2.31.so
7f2cb6a8a000-7f2cb6a8b000 r--p 00002000 08:13 3810903 /usr/lib/libutil-2.31.so
...
7f2cb9802000-7f2cb9803000 rw-p 00000000 00:00 0
7ffe77658000-7ffe7767a000 rw-p 00000000 00:00 0 [stack]
7ffe776c8000-7ffe776cc000 r--p 00000000 00:00 0 [vvar]
7ffe776cc000-7ffe776ce000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]
通过上面的输出,我们看到加载的共享库列在最后一列。
但是,最后一列包含一些我们不需要的其他值。此外,还有许多重复项。我们可以过滤数据并使用awk 单行删除重复项以获得共享库的干净列表:
$ awk '$NF!~/\.so/{next} {$0=$NF} !a[$0]++' /proc/179015/maps
...
/usr/lib/libpython3.8.so.1.0
/usr/lib/libgpg-error.so.0.29.0
/usr/lib/libgcrypt.so.20.2.5
/usr/lib/liblz4.so.1.9.2
/usr/lib/liblzma.so.5.2.5
/usr/lib/libsystemd.so.0.28.0
/usr/lib/libogg.so.0.8.4
/usr/lib/libvorbis.so.0.4.8
/usr/lib/libblkid.so.1.1.0
/usr/lib/libXdmcp.so.6.0.0
/usr/lib/libXau.so.6.0.0
/usr/lib/libdatrie.so.1.3.5
...
让我们了解一下awk单行代码的工作原理:
- $NF !~ /.so/{next} – 如果最后一列不包含“ .so ”,我们将忽略它
- {$0=$NF} – 如果最后一列包含共享库,我们用最后一列替换该行,这是库的文件名
*!a[$0]++*是删除重复行的 awk技巧:
- 当带有值*“foo”的行第一次到达awk*时,awk创建一个关联数组元素:a[“foo”],默认值:0
- a[“foo”]++返回原始值0然后将其值递增1,因此表达式返回 0 然后我们有 a[“foo”]=1
- *!a[“foo”]++将变为 !0,它被评估为true,*因此,触发默认操作:打印当前行
- 当再次出现“ foo ”的行时,数组元素已经存在, a[“foo”] ++会返回 1并保持2
- *!a[“foo”]++*这次会变成 !1,因此我们有 false:什么都不做。这样,重复的行只打印一次