3. 设备树编译与反编译:DTC工具使用、DTS到DTB的编译、DTB反编译为DTS、反编译实战技巧
设备树源文件(.dts)是人可读的文本,但U-Boot和内核最终吃的是二进制DTB。这中间的关键桥梁就是DTC——Device Tree Compiler。说白了,它就是设备树的“编译器+反编译器”。
我刚开始接触设备树时,总觉得这步很简单,不就是一条命令嘛。直到有一次在项目中,我反编译出来的DTS跟原始文件对不上,排查了半天才发现是编译时带了特殊参数。嗯,这里面的门道其实不少。
核心认知:DTC不仅是编译工具,更是调试利器。学会反编译,就等于拿到了查看“黑盒”DTB的钥匙。
3.1 DTC工具初探
DTC通常随U-Boot或Linux内核源码一起发布。你可以在 scripts/dtc/ 目录下找到它。我个人习惯在U-Boot根目录下直接编译它:
# 在U-Boot源码根目录
make scripts/dtc/dtc
# 或者直接编译整个工具集
make dtc
编译完成后,scripts/dtc/dtc 就是我们要用的二进制。你也可以把它复制到系统路径下:
cp scripts/dtc/dtc /usr/local/bin/
小提示:不同版本的DTC对设备树语法的支持有细微差异。我建议始终使用与当前U-Boot版本匹配的DTC,避免因版本不一致导致的编译错误。
3.2 DTS到DTB的编译
编译命令的基本格式很简单:
dtc -I dts -O dtb -o output.dtb input.dts
参数说明:
-I dts:输入格式为DTS文本-O dtb:输出格式为DTB二进制-o output.dtb:指定输出文件名input.dts:源文件
实际项目中,我们通常还会加几个常用选项:
| 选项 | 作用 | 我什么时候用 |
|---|---|---|
-@ |
生成插件节点(__symbols__) | 需要动态加载overlay时必加 |
-H epapr |
生成EPAPR风格的header | PowerPC平台常用 |
-W no-unit_address_vs_reg |
关闭某些警告 | 处理老旧DTS时经常用 |
-O dtb -b 0 |
指定物理CPU启动编号 | 多核处理器启动时 |
一个更完整的编译示例:
dtc -I dts -O dtb -@ -W no-unit_address_vs_reg \
-o my-board.dtb my-board.dts
注意:如果你的DTS文件包含了 #include 指令(通常引用 .dtsi 文件),直接使用 dtc 命令会报错。这时你需要先用C预处理器展开:
cpp -nostdinc -I include -I arch/arm/dts \
-undef -x assembler-with-cpp \
my-board.dts my-board-preprocessed.dts
dtc -I dts -O dtb -o my-board.dtb my-board-preprocessed.dts
U-Boot的编译系统(Kbuild)会自动处理这些步骤,但手动调试时你得自己来。
3.3 DTB反编译为DTS
反编译是调试的“后悔药”。拿到一个DTB,你想看看里面到底定义了哪些节点?反编译!
dtc -I dtb -O dts -o output.dts input.dtb
就这么简单。但反编译出来的DTS跟原始DTS长得不太一样,你想想看:
- 注释全没了——DTB不保存注释
- 节点顺序可能变化——DTB按phandle排序
- 属性值格式变了——比如
reg属性会显示为原始字节数组 - 标签(label)丢失——标签在编译时被解析掉了
我曾经反编译一个生产环境中的DTB,发现里面多了一个我从来没见过的节点。后来一查,是U-Boot在启动时动态添加的。反编译帮我抓到了这个“幽灵节点”。
3.4 反编译实战技巧
光会跑命令还不够,得会用反编译来解决问题。下面是我总结的几个实战场景:
场景一:验证编译结果
编译完DTB后,反编译回来对比一下:
dtc -I dts -O dtb -o test.dtb test.dts
dtc -I dtb -O dts -o test-decompiled.dts test.dtb
diff test.dts test-decompiled.dts
如果diff有输出,别慌。通常是注释、标签、格式差异。重点关注节点结构和属性值是否一致。
场景二:提取U-Boot使用的DTB
U-Boot启动时,可能从多个位置加载DTB(FIT镜像、分区、网络)。你可以把U-Boot内存中的DTB dump出来反编译:
# 在U-Boot命令行
md.b ${fdtcontroladdr} 0x10000
# 或者用fdt命令
fdt list
然后把内存内容保存成文件,再反编译。我常用这招来确认U-Boot实际用的是哪个DTB。
场景三:对比不同版本的DTB
项目迭代中,DTB经常改。反编译后做diff,能快速定位改动:
dtc -I dtb -O dts -o v1.dts v1.dtb
dtc -I dtb -O dts -o v2.dts v2.dtb
diff -u v1.dts v2.dts
技巧:如果diff输出太多格式差异,可以用 -s 选项让DTC输出更紧凑的格式:
dtc -I dtb -O dts -s -o compact.dts input.dtb
这样输出的DTS没有缩进和换行,对比起来更干净。
3.5 知识体系总览
下面这张图帮你理清DTC工具在整个设备树调试流程中的位置:
3.6 常见问题与避坑
我曾经踩过的坑:
- 编译时忘记加
-@——导致overlay加载失败,排查了两天 - 反编译后直接修改再编译——因为标签丢失,编译出来的DTB跟原始行为不一致
- 使用系统自带的旧版DTC——不支持某些新语法,编译报错
我的建议是:把DTC当成调试工具箱里的“瑞士军刀”。编译时多用 -W 选项开启警告,反编译时多用 -s 选项简化输出。熟练了之后,你会发现设备树调试其实没那么玄乎。