3. 依赖分析工具

做驱动集成,说白了就是跟各种 .ko 和 .so 打交道。你编译好了,insmod 或者加载动态库时,系统突然告诉你「找不到符号」或者「版本不匹配」——这种场景我遇到过太多次了。这时候,手里没几把趁手的分析工具,就像瞎子摸象。

我个人习惯,在集成阶段一定会跑一遍依赖分析。今天聊的这四个工具:ldd、objdump、nm、readelf,是我日常排查依赖问题的「四件套」。每个工具侧重点不同,但组合起来,基本能覆盖 90% 的依赖场景。

3.1 ldd:快速查看动态链接依赖

ldd 全称是 List Dynamic Dependencies。它的用法极其简单,但输出信息量很大。

# 查看一个 .so 或可执行文件的动态依赖
ldd /usr/lib/libexample.so

输出大概长这样:

linux-vdso.so.1 (0x00007ffe3b7f0000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f8a1c200000)
/lib64/ld-linux-x86-64.so.2 (0x00007f8a1c600000)

每一行左边是「需要的库名」,右边是「实际解析到的路径」。如果某个库找不到,会显示 not found。嗯,这里要注意:ldd 实际上会尝试加载这些库,所以如果你在交叉编译环境下用 ldd 分析 ARM 的二进制,结果可能不准。我建议用 readelf -d 替代。

关键点:ldd 显示的是「运行时」的依赖解析结果,不是编译时的。如果某个库路径不对,ldd 会直接告诉你。

我的经验:有一次客户反馈驱动加载失败,我远程一看,ldd 输出里有个 libssl.so.1.1 => not found。系统里装的是 1.0 版本,版本号差一位,折腾了半天。后来我养成了习惯:集成前先跑一遍 ldd,把 not found 全部消灭掉。

3.2 objdump:查看符号表和重定位信息

objdump 是个大杀器,功能非常多。在依赖分析场景,我主要用它看两个东西:动态符号表和重定位表。

3.2.1 查看动态符号表

objdump -T libexample.so

输出示例:

libexample.so:     file format elf64-x86-64

DYNAMIC SYMBOL TABLE:
0000000000002000 g    DF .text  0000000000000010  Base        example_func
0000000000002010 g    DF .text  0000000000000020  Base        init_device
0000000000000000      D  *UND*  0000000000000000              printf

注意看 *UND* 那一行。这表示 printf 是「未定义符号」,需要从其他库(比如 libc)中解析。说白了,这就是你的依赖缺口。

3.2.2 查看重定位表

objdump -R libexample.so

输出:

DYNAMIC RELOCATION RECORDS
OFFSET           TYPE              VALUE  
0000000000003000 R_X86_64_JUMP_SLOT  printf
0000000000003008 R_X86_64_JUMP_SLOT  malloc

重定位表告诉你:运行时需要把 printf 和 malloc 的实际地址填到这些偏移位置。如果这些符号在依赖库里找不到,加载就会失败。

我曾经踩过的坑:有一次编译内核模块,objdump -T 看到一堆 *UND* 符号,我以为没问题,结果 insmod 时直接报 Unknown symbol。后来发现是模块依赖的另一个 .ko 没加载。记住:*UND* 符号必须被其他已加载模块或库提供,否则就是运行时错误。

3.3 nm:列出符号表

nm 比 objdump 更轻量,专门用来列出目标文件中的符号。它的输出格式很直观。

nm -D libexample.so

输出:

0000000000002000 T example_func
0000000000002010 T init_device
                 U printf
                 U malloc

符号类型字母的含义:

符号类型 含义 说明
T Text(代码段) 该符号定义在当前文件中,且是全局的
U Undefined(未定义) 该符号需要从外部解析,是依赖项
D Data(数据段) 已初始化的全局变量
W Weak(弱符号) 弱定义,可以被其他强符号覆盖

实用技巧:用 nm -u 只显示未定义符号,快速定位依赖缺失。比如:nm -u *.ko | grep -v "0" 可以列出所有模块的未解析符号。

3.4 readelf:ELF 文件的瑞士军刀

readelf 是我个人最常用的工具。它不依赖系统的动态链接器,所以交叉编译环境下也能用。而且它显示的信息比 objdump 更底层、更完整。

3.4.1 查看动态段依赖

readelf -d libexample.so | grep NEEDED

输出:

0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
0x0000000000000001 (NEEDED)             Shared library: [libm.so.6]

这比 ldd 更可靠,因为它直接从 ELF 文件的 .dynamic 段读取依赖信息,不会去实际加载库。

3.4.2 查看符号版本信息

readelf -V libexample.so

输出:

Version symbols section '.gnu.version' contains 5 entries:
 Addr: 0x0000000000003000  Offset: 0x00003000  Link: 5 (.dynsym)
  000:   0 (*local*)       2 (GLIBC_2.2.5)   2 (GLIBC_2.2.5)   3 (GLIBC_2.3)
  004:   1 (*global*)

版本信息是驱动集成中容易忽略的点。我曾经遇到一个情况:模块依赖 GLIBC_2.14,但目标系统只支持到 GLIBC_2.12,结果加载时直接报 version GLIBC_2.14 not found。用 readelf -V 可以提前发现这种版本不匹配。

3.4.3 查看 ELF 头信息

readelf -h libexample.so

输出:

ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF64
  Machine:                           Advanced Micro Devices X86-64
  Entry point address:               0x2000
  Start of section headers:          123456 (bytes into file)

这里可以快速确认文件是 32 位还是 64 位,目标架构是什么。集成时如果架构不匹配,加载会直接失败。

我的习惯:拿到一个陌生的 .ko 或 .so,我第一件事就是 readelf -h 看架构,然后 readelf -d | grep NEEDED 看依赖,最后 readelf -s 看符号。三步走,基本能摸清这个文件的底细。

3.5 工具对比与选择

工具 主要用途 适用场景 注意事项
ldd 查看运行时动态库依赖 本地调试、快速验证 交叉编译环境不可用
objdump 查看符号表、重定位、反汇编 深入分析符号依赖 输出信息量大,需过滤
nm 列出符号(轻量) 快速查看未定义符号 不显示版本信息
readelf ELF 文件全面分析 交叉编译、版本检查、架构确认 功能最全,但参数多

3.6 实战:一次完整的依赖分析流程

假设你拿到一个驱动模块 mydriver.ko,加载时报 Unknown symbol。我会这样排查:

  1. 第一步:readelf -h mydriver.ko 确认架构和位数是否正确。
  2. 第二步:nm -u mydriver.ko 列出所有未定义符号。
  3. 第三步:cat /proc/kallsyms | grep symbol_name 检查内核是否导出了这些符号。
  4. 第四步:如果符号在其他模块中,用 modprobe --show-depends mydriver 查看模块依赖链。
  5. 第五步:如果符号在动态库中,用 readelf -d mydriver.ko | grep NEEDED 确认依赖库,再用 ldconfig -p | grep libname 检查库是否存在。

重要提醒:内核模块的依赖分析和用户态动态库略有不同。内核模块使用 EXPORT_SYMBOL 导出的符号,而不是动态链接器。所以 nm -u 看到的未定义符号,必须在内核符号表或已加载的其他模块中找到。我建议用 cat /proc/kallsyms 来验证。

好了,这四个工具的基本用法和输出解读就聊到这里。你想想看,其实依赖分析没那么神秘,无非就是搞清楚「谁需要谁」以及「谁提供了谁」。工具只是手段,关键是理解背后的 ELF 结构和链接机制。下次遇到加载失败,别慌,拿出这四件套,一步步排查,问题基本都能定位到。

依赖分析工具知识体系 依赖分析工具 ldd objdump nm readelf 功能 • 查看运行时动态库依赖 • 显示库解析路径 • 快速定位 not found ⚠ 交叉编译环境不可用 功能 • 查看动态符号表 • 查看重定位表 • 反汇编代码 ⚠ 输出信息量大 功能 • 列出符号表 • 显示符号类型 • 快速定位 U 符号 ⚠ 不显示版本信息 功能 • 查看动态段依赖 • 查看符号版本 • 查看 ELF 头信息 ⚠ 参数较多需熟悉 核心思路:先看架构 → 再看依赖 → 最后查符号 四件套组合使用,覆盖 90% 的依赖分析场景

总结一句话:依赖分析不是玄学,是工程。工具给你数据,经验给你判断。多练几次,你也能一眼看出问题出在哪。