3. 设备树编译与反编译
好,咱们进入第三章。这一章我打算聊聊设备树的编译和反编译。说白了,就是怎么把咱们写好的 .dts 文件,变成内核能认的 .dtb 二进制文件,以及反过来怎么从 .dtb 还原出可读的源码。
你可能觉得这步很简单,不就是敲个命令吗?嗯,确实不复杂。但我在项目中遇到过几次因为编译选项不对,导致板子死活起不来的情况。所以,咱们还是得把里面的门道搞清楚。
3.1 DTS 到 DTB:编译过程
设备树编译器(Device Tree Compiler),简称 dtc。它就是咱们手里的“翻译官”。
我个人习惯,在开发阶段会频繁修改设备树,所以编译命令一定要熟记于心。最基本的用法是这样的:
dtc -I dts -O dtb -o my_board.dtb my_board.dts
解释一下参数:
-I dts:指定输入格式是.dts文本文件。-O dtb:指定输出格式是.dtb二进制文件。-o my_board.dtb:指定输出文件名。- 最后一个参数就是输入的源文件。
你看,是不是很简单?但这里有个坑。我曾经在调试一块新板子时,编译完烧进去,内核直接 panic。查了半天,发现是 dtc 版本太老,不认识我用的新语法。所以,务必确认你的 dtc 版本。
dtc --version。Linux 内核源码里也自带了一份 dtc,路径在 scripts/dtc/ 下。用内核自带的通常最保险。
3.2 编译过程中的“预处理”
你可能会问,.dts 文件里那些 #include 和 /include/ 是怎么处理的?
其实,dtc 在正式编译前,会先调用 C 预处理器(cpp)来处理这些包含指令。这也是为什么我们能在 .dts 里写 #include "soc.dtsi" 的原因。
我个人建议,不要手动去展开这些头文件。让工具链自动处理就好。如果你非要看预处理后的结果,可以用 -E 参数:
cpp -nostdinc -I include -undef -x assembler-with-cpp my_board.dts my_board_preprocessed.dts
嗯,这命令有点长。但调试复杂问题时,看看预处理后的文件,能帮你快速定位是哪个宏定义出了问题。
3.3 DTB 到 DTS:反编译过程
反编译,就是把二进制的 .dtb 还原成文本格式的 .dts。这在你拿到一个别人编译好的 .dtb,但没给源码时,特别有用。
命令也很对称:
dtc -I dtb -O dts -o my_board_reversed.dts my_board.dtb
你看,就是把 -I 和 -O 的参数互换一下。
但要注意,反编译出来的 .dts 文件,并不完全等于原始源码。为什么?因为编译过程中,注释会被丢掉,一些宏定义也被展开了。反编译出来的文件,可读性会差一些,所有节点和属性都是展开后的样子。
3.4 常用工具链
除了 dtc,咱们还有一些好用的工具。我整理了一个表格,方便你查阅:
| 工具名 | 用途 | 我的经验 |
|---|---|---|
dtc |
编译/反编译设备树 | 核心工具,必须掌握 |
fdtdump |
以可读格式 dump dtb 内容 | 快速查看 dtb 结构,比反编译快 |
fdtget |
从 dtb 中读取属性值 | 脚本里用,不用反编译整个文件 |
fdtput |
修改 dtb 中的属性值 | 慎用!容易改出问题 |
fdtlist |
列出 dtb 中的节点路径 | 快速了解 dtb 有哪些设备 |
这些工具在 dtc 的源码包中通常都包含了。编译 dtc 时,它们会一起被编译出来。
3.5 实战:一个完整的编译流程
咱们来走一遍完整的流程。假设你有一个项目,设备树文件分散在多个目录:
project/
├── dts/
│ ├── my_board.dts
│ └── my_board.dtsi
└── include/
└── soc.h
编译命令可以这样写:
dtc -I dts -O dtb \
-i ./dts \
-i ./include \
-o output/my_board.dtb \
dts/my_board.dts
这里用到了 -i 参数,指定搜索路径。这样 dtc 就能找到 #include "soc.h" 和 /include/ "my_board.dtsi" 了。
嗯,这里要注意一点:-i 可以指定多个路径,dtc 会按顺序搜索。
3.6 知识体系结构图
为了让你更直观地理解整个编译与反编译的流程,我画了一张图:
这张图清晰地展示了从源文件到二进制文件,再到反编译的完整链路。你可以看到,dtc 是核心枢纽,而其他工具则提供了辅助功能。
3.7 避坑指南
最后,分享几个我踩过的坑:
- 不要用
-@参数乱加插件:这个参数允许在 dtb 中插入插件(overlay)。如果你不需要,别加。加了会让 dtb 变大,而且某些旧版内核不识别。 - 注意字节序:
dtc默认生成大端序的 dtb。如果你的平台是小端序(比如 ARM),记得加-E -L参数?其实不用,dtc会自动适配。但如果你在 x86 上编译给 ARM 用,要确认一下。 - 编译警告别忽略:
dtc的警告通常意味着你的设备树有潜在问题。比如WARNING: unit_address_vs_reg,说明节点名和 reg 属性不匹配。我曾经忽略了这个警告,结果某个外设驱动死活加载不上。
好了,关于编译与反编译,就聊这么多。记住,工具是死的,人是活的。多动手敲命令,遇到问题多看看 dtc --help,你很快就能熟练起来。