4、设备树编译器(DTC):编译流程、反编译技巧、常用编译选项
设备树编译器,圈里人都叫它 DTC。说实话,这工具看着不起眼,但你要是用不好它,调试设备树的时候能把你折腾得够呛。我最早接触 DTC 的时候,以为就是个简单的文本转二进制工具,后来踩了不少坑才明白——这玩意儿门道多着呢。
4.1 DTC 的编译流程:从 .dts 到 .dtb
DTC 的工作流程,说白了就三步:
- 预处理:处理
#include、/include/和宏定义 - 解析:把 .dts 文本解析成抽象语法树
- 输出:根据选项生成 .dtb 或 .dts
我刚开始用的时候,以为 #include 和 C 语言一样是预处理指令。其实 DTC 自己就支持 /include/ 语法,但更常见的做法是用 C 预处理器先跑一遍。嗯,这里要注意:
// 推荐做法:用 cpp 预处理
cpp -nostdinc -I include -undef -x assembler-with-cpp \
my-board.dts | dtc -O dtb -o my-board.dtb
// 或者直接用 dtc 的 -E 选项(新版本支持)
dtc -E -I dts -O dtb -o my-board.dtb my-board.dts
我个人习惯用第一种方式,因为可以灵活控制 include 路径。你在实际项目中,头文件可能散落在多个目录里,cpp 的 -I 选项比 DTC 自带的路径管理要顺手得多。
4.2 反编译技巧:从 .dtb 回到 .dts
反编译,就是把二进制 .dtb 还原成可读的 .dts。这招在调试时特别有用。我记得有一次,客户给的 .dtb 文件死活点不亮屏幕,我反编译一看——好家伙,时序参数全被编译器优化掉了。
基本命令很简单:
dtc -I dtb -O dts -o decompiled.dts my-board.dtb
但这里有几个坑,我一个个说:
0x12345678 而不是 FOO_BAR。
另外,如果你遇到反编译失败的情况,多半是 .dtb 版本不匹配。DTC 的版本和内核版本最好对应上。我曾在一次项目中,用新版本的 DTC 去反编译老内核的 .dtb,结果报了一堆语法错误。
-s 选项,可以保留符号信息。虽然不能恢复宏定义,但至少能保留一些节点顺序的线索。
4.3 常用编译选项详解
DTC 的选项看着多,但常用的就那么几个。我按使用频率排个序:
| 选项 | 作用 | 我的使用场景 |
|---|---|---|
-O dtb |
输出二进制格式 | 最终生成给内核用的文件 |
-O dts |
输出文本格式(反编译) | 调试、逆向分析 |
-@ |
生成插件(overlay)支持 | 做动态设备树时必加 |
-H epapr |
指定头部格式 | PowerPC 平台常用 |
-W no-unit_address_vs_reg |
关闭某些警告 | 处理老旧 .dts 时 |
-V 17 |
指定 DTSpec 版本 | 兼容旧内核 |
这里重点说说 -@ 选项。你想想看,现在很多嵌入式设备都支持 FPGA 动态加载,或者外设热插拔。这时候就需要设备树插件(overlay)。不加 -@ 编译出来的 .dtb,内核是不认 overlay 的。
// 编译支持 overlay 的 .dtb
dtc -@ -I dts -O dtb -o base.dtb base.dts
// 编译 overlay 文件
dtc -@ -I dts -O dtb -o my-overlay.dtbo my-overlay.dts
我曾经在一个项目里,忘了加 -@,结果 overlay 加载时内核报 EPERM。查了两天才发现是编译选项的问题。嗯,从那以后我写 Makefile 都会把 -@ 加上,哪怕当前没用 overlay。
4.4 编译时常见的坑与避坑指南
我这些年用 DTC,踩过的坑能写个小本本。挑几个典型的说说:
坑一:警告当成错误
DTC 默认把某些警告当成错误处理。比如 unit_address_vs_reg 警告,如果你的节点 reg 属性和 unit name 不匹配,编译直接失败。
// 解决方案:用 -W no-xxx 关闭特定警告
dtc -W no-unit_address_vs_reg -I dts -O dtb -o output.dtb input.dts
坑二:大小端问题
DTC 默认用大端输出。但有些 SoC 是小端的。我记得有一次帮客户调试,他们用的小端 ARM 芯片,编译出来的 .dtb 死活不对。后来发现是没指定字节序。
// 强制小端输出
dtc -I dts -O dtb -o output.dtb -BE input.dts // 大端
dtc -I dts -O dtb -o output.dtb -LE input.dts // 小端
坑三:内存对齐
.dtb 文件要求 4 字节对齐。如果你手动拼过 .dtb,或者用非标准工具生成过,很容易出现对齐问题。DTC 编译时会自动处理,但反编译再编译就可能出问题。
4.5 进阶技巧:用 DTC 做自动化检查
DTC 不只是个编译器,它还能当静态检查工具用。我经常在 CI 流程里加这么一步:
# 检查设备树语法和语义
dtc -I dts -O dtb -o /dev/null -W all input.dts
# 检查 overlay 兼容性
dtc -@ -I dts -O dtb -o /dev/null -W all overlay.dts
加上 -W all 会开启所有警告。虽然有些警告是误报,但大部分能帮你提前发现问题。比如 graph_child_address 警告,能检查出 display 端口节点地址写没写对。
check-dts 目标,专门跑 DTC 检查。每次提交代码前跑一遍,能省去不少调试时间。
最后说一句,DTC 的版本迭代挺快的。新版本会加一些检查规则,也会修复一些 bug。我建议你定期更新一下 DTC,至少跟上你所用内核的版本。别像我以前那样,用着两年前的 DTC 去编译最新的内核设备树,结果一堆莫名其妙的错误。