readelf 和 ldd 查看文件的依赖
readelf 和 ldd 查看文件的依赖
readelf
和 ldd
都可以用来查看文件的依赖,但它们的工作原理、可靠性和使用场景有本质区别。
简单来说:
readelf
:是一个静态分析工具,直接读取并解析 ELF 文件本身的头信息和数据结构,告诉你它声称自己需要哪些共享库。ldd
:是一个动态模拟/加载工具,它实际上会启动程序的加载器,模拟其运行时的加载过程,然后告诉你在当前环境下实际会找到并加载哪些库。
下面我们进行详细的对比和解释。
1. readelf -d
(查看动态节信息)
readelf
是一个用于显示 ELF(Executable and Linkable Format)文件信息的强大工具。-d
选项用于显示文件的动态节(.dynamic section),这个节包含了链接器所需的各种信息,其中就包括依赖的共享库列表。
工作原理:
静态分析。它只是读取 ELF 文件中 DT_NEEDED
类型的条目,这些条目记录的是编译链接时由链接器写入的、程序运行所必需的共享库名字(如 libc.so.6
)。
使用方法:
bash
readelf -d /path/to/program | grep NEEDED
输出示例:
text
0x0000000000000001 (NEEDED) Shared library: [libssl.so.3]
0x0000000000000001 (NEEDED) Shared library: [libcrypto.so.3]
0x0000000000000001 (NEEDED) Shared library: [libpthread.so.0]
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
这个列表是程序内在的、声明的依赖,不依赖于你当前的系统环境。
优点:
绝对可靠:它直接读取文件内容,显示的是程序“设计上”需要什么,永远不会失败或被欺骗。
安全:因为它不执行任何代码,只是读取文件。
缺点:
只显示库的名字(SONAME),不显示库在磁盘上的完整绝对路径。
不关心这些库在你的系统上是否真实存在。
2. ldd
(列出动态依赖)
ldd
本身并不是一个真正的程序,而是一个shell脚本(在某些发行版上是二进制包装)。它的作用是设置环境变量,然后调用程序的运行时链接器(通常是 /lib/ld-linux.so.2
或 /lib64/ld-linux-x86-64.so.2
)来模拟加载过程。
工作原理:
动态模拟。它通过设置 LD_TRACE_LOADED_OBJECTS=1
环境变量,然后启动目标程序。链接器检测到这个变量后,不会真正运行程序的 main
函数,而是打印出它会加载哪些库以及这些库被解析到的具体路径,然后立即退出。
使用方法:
bash
ldd /path/to/program
输出示例:
text
linux-vdso.so.1 (0x00007ffc12df0000)libssl.so.3 => /usr/lib/x86_64-linux-gnu/libssl.so.3 (0x00007f8a1a2c0000)libcrypto.so.3 => /usr/lib/x86_64-linux-gnu/libcrypto.so.3 (0x00007f8a19c00000)libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f8a19bda000)libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f8a199c8000)/lib64/ld-linux-x86-64.so.2 (0x00007f8a1a518000)
这个列表是在当前系统环境和配置下,链接器实际能找到的库。
优点:
信息实用:直接显示了库的完整路径,方便定位问题。
反映了环境的影响:它会遵循
LD_LIBRARY_PATH
、/etc/ld.so.cache
、rpath
等运行时配置,展示最终的解析结果。
缺点:
有安全风险:因为它会实际加载程序的依赖库并执行其中的初始化代码(
init
代码)。如果程序是恶意的,或者某个依赖库被篡改,使用ldd
可能会意外执行恶意代码。可能不可靠:如果程序本身缺少某些关键的依赖,
ldd
可能会因为加载失败而报错,无法给出完整的依赖列表。
核心区别总结
特性 | readelf -d | ldd |
---|---|---|
工作原理 | 静态分析ELF文件结构 | 动态模拟加载过程 |
显示内容 | 声明的依赖项(SONAME) | 解析后的完整路径 |
环境依赖 | 否。结果与系统环境无关,只与文件本身有关。 | 是。结果受 LD_LIBRARY_PATH 、ld.so.cache 等影响。 |
可靠性 | 高。直接读取文件,总能成功。 | 较低。依赖库缺失或损坏会导致命令失败。 |
安全性 | 安全。不执行任何代码。 | 不安全。会执行库的初始化代码,有风险。 |
主要用途 | 查看程序的原始依赖声明,用于开发和调试。 | 查看库在当前系统下的实际位置,用于解决运行时依赖问题。 |
实践与建议
优先使用
readelf
或objdump
:
出于安全考虑,如果你只是想看程序依赖哪些库,应该优先使用静态分析工具。bash
# 使用 readelf readelf -d /bin/ls | grep NEEDED# 或者使用 objdump(效果类似) objdump -p /bin/ls | grep NEEDED
安全地使用
ldd
的替代方案:
如果你需要知道库的路径,但又担心安全问题,可以使用以下方法:bash
# 方法一:使用 ld.so 直接模拟(ldd 的本质) LD_TRACE_LOADED_OBJECTS=1 /lib64/ld-linux-x86-64.so.2 /path/to/program# 方法二:使用调试器(最安全,但复杂) gdb /path/to/program (gdb) start (gdb) info sharedlibrary
何时使用
ldd
:
当你需要快速诊断“库未找到”或“库版本冲突”等运行时问题时,ldd
非常有用,因为它能直接告诉你链接器找到了什么。但请仅在对可信的可执行文件使用时才使用它。
readelf
详细用法
readelf
详细用法,涵盖了最常见的选项和场景。
命令格式
bash
readelf <option(s)> <elf-file(s)>
主要选项和用法
1. 查看ELF文件头(File Header)
这是最常用的选项之一,它显示了文件的基本身份信息,包括文件类型、目标架构、入口点地址等。
命令:
bash
readelf -h /path/to/binary
# 或
readelf --file-header /path/to/binary
输出信息解读:
Magic
: ELF文件的魔数,标识这是一个ELF文件。Class
: 文件类别,ELF32
(32位)或ELF64
(64位)。Data
: 字节序,LSB
(小端序,Intel x86/x86_64)或MSB
(大端序,某些ARM、MIPS)。Type
: 文件类型,如EXEC
(可执行文件)、DYN
(共享库/PIE可执行文件)、REL
(可重定位文件,即.o目标文件)。Machine
: 目标架构,如x86-64
,ARM
,AArch64
。Version
: ELF版本,通常是1 (current)
。Entry point address
: 程序入口点的虚拟内存地址。Start of program headers
: 程序头表(Program Header Table)在文件中的偏移量。Start of section headers
: 节头表(Section Header Table)在文件中的偏移量。Flags
: 处理器特定的标志。Size of this header
: ELF文件头本身的大小。Size of program headers
: 每个程序头(Program Header)的大小。Number of program headers
: 程序头的数量。Size of section headers
: 每个节头(Section Header)的大小。Number of section headers
: 节头的数量。Section header string table index
: 节名称字符串表在节头表中的索引。
2. 查看节头表(Section Headers)
节头表描述了ELF文件中的各个“节”(Section),例如代码节(.text)、数据节(.data)等。注意: 节(Section)是链接视图的概念,在程序运行时,这些节会被组织成“段”(Segment)加载到内存。
命令:
bash
readelf -S /path/to/binary
# 或
readelf --section-headers /path/to/binary
输出信息解读:
[Nr]
: 节的索引号。Name
: 节的名称(如.text
,.data
,.bss
,.rodata
,.shstrtab
)。Type
: 节的类型(如PROGBITS
程序数据,SYMTAB
符号表,STRTAB
字符串表)。Address
: 该节加载到内存后的虚拟地址。Off
: 该节在文件中的偏移量。Size
: 节的大小。ES
: 条目大小(如果节是一个表)。Flg
: 标志(W
可写,A
可分配,X
可执行)。Lk
: 相关链接信息。Inf
: 额外信息。Al
: 对齐要求。
3. 查看程序头表(Program Headers / Segments)
程序头表描述了运行时的“段”(Segment),这些段告诉系统加载器如何将文件映射到进程的虚拟内存中。这是运行时视图。
命令:
bash
readelf -l /path/to/binary
# 或
readelf --program-headers /path/to/binary
# 或(显示得更详细)
readelf --segments /path/to/binary
输出信息解读:
Type
: 段的类型,最重要的有:LOAD
: 需要被加载到内存的段(通常包含代码和数据)。DYNAMIC
: 动态链接信息。INTERP
: 指定程序解释器(如/lib64/ld-linux-x86-64.so.2
)的路径。
Offset
: 段在文件中的偏移。VirtAddr
: 段的第一个字节在内存中的虚拟地址。PhysAddr
: 物理地址(通常与虚拟地址相同,用于没有MMU的系统)。FileSiz
: 段在文件中的大小。MemSiz
: 段在内存中的大小(可能比FileSiz
大,例如对于未初始化的.bss
节)。Flg
: 标志(R
可读,W
可写,X
可执行)。Align
: 对齐方式。
输出末尾的 Section to Segment mapping
部分非常有用,它显示了哪些节被合并到了哪个运行时段中。
4. 查看动态节信息(Dynamic Section)
这是查看共享库依赖、rpath
等动态链接信息的核心选项。
命令:
bash
readelf -d /path/to/binary
# 或
readelf --dynamic /path/to/binary
常用信息过滤:
bash
# 查看依赖的共享库 (NEEDED)
readelf -d /bin/ls | grep NEEDED# 查看RPATH/RUNPATH(链接器搜索库的额外路径)
readelf -d /bin/ls | grep RPATH
readelf -d /bin/ls | grep RUNPATH# 查看符号表位置
readelf -d /bin/ls | grep SYMTAB# 查看初始化函数(init_array)和终止函数(fini_array)
readelf -d /bin/ls | grep INIT_ARRAY
readelf -d /bin/ls | grep FINI_ARRAY
5. 查看符号表(Symbol Table)
符号表包含了函数、变量等符号的信息。
命令:
bash
# 查看 .dynsym (动态符号表,用于动态链接)
readelf --dyn-syms /path/to/binary# 查看 .symtab (完整符号表,通常存在于非strip的可执行文件中)
readelf --syms /path/to/binary
常用过滤:
bash
# 查看动态符号表中定义的函数(过滤掉UND未定义的)
readelf --dyn-syms /bin/ls | grep -v UND# 查找某个特定的符号(如malloc)
readelf --dyn-syms /bin/ls | grep malloc
6. 查看重定位信息(Relocations)
显示需要在链接时或加载时进行修正的位置。
命令:
bash
readelf -r /path/to/binary
# 或
readelf --relocs /path/to/binary
7. 查看节内容(Section Contents)
以十六进制和ASCII形式显示指定节的内容。
命令:
bash
readelf -x <section-name> /path/to/binary
# 或
readelf --hex-dump=<section-name> /path/to/binary# 例如,查看 .comment 节的内容
readelf -x .comment /bin/ls
8. 显示所有信息(All)
使用 -a
选项可以显示 readelf
能提供的所有信息。输出会非常长,通常用于全面分析或重定向到文件仔细查看。
命令:
bash
readelf -a /path/to/binary
总结
你的需求 | 应使用的 readelf 选项 |
---|---|
快速查看文件基本信息(架构、类型) | -h (--file-header ) |
查看代码、数据等节的布局 | -S (--section-headers ) |
查看运行时内存如何映射 | -l (--program-headers , --segments ) |
查看依赖的共享库和rpath | -d (--dynamic ) |
查看导出的函数/符号 | --dyn-syms |
全面分析,获取所有信息 | -a (--all ) |