3、设备树编译与反编译:DTC工具使用、DTS到DTB的编译过程、DTB反编译为DTS
设备树源文件(.dts)是人类可读的文本描述,而内核引导时实际使用的是二进制格式的设备树 Blob(.dtb)。从 .dts 到 .dtb 的转换,以及逆向的还原操作,都依赖 DTC(Device Tree Compiler) 工具完成。本章深入讲解 DTC 的使用方法、编译与反编译的完整流程,以及常见问题的排查手段。
核心要点:DTC 是设备树生态的“翻译官”。掌握其编译与反编译操作,是调试设备树、验证语法正确性、逆向分析现有 DTB 文件的必备技能。
3.1 DTC 工具概述
DTC 是设备树编译器的标准实现,通常作为 Linux 内核源码树的一部分发布,路径为 scripts/dtc/。它支持三种文件格式的相互转换:
| 文件扩展名 | 格式说明 | 可读性 | 典型用途 |
|---|---|---|---|
.dts |
设备树源文件(文本) | 高 | 开发、修改、版本管理 |
.dtb |
设备树二进制 Blob | 低 | 内核引导加载、固件传递 |
.dtsi |
设备树包含文件(文本) | 高 | 公共定义、SoC 级描述 |
DTC 工具的核心命令行格式如下:
# 编译:DTS → DTB
dtc -I dts -O dtb -o output.dtb input.dts
# 反编译:DTB → DTS
dtc -I dtb -O dts -o output.dts input.dtb
# 常用调试选项
dtc -I dts -O dtb -o output.dtb -@ input.dts # 保留符号(用于插件)
dtc -I dts -O dtb -o output.dtb -Wno-unit_address_vs_reg input.dts # 忽略特定警告
版本检查:执行
dtc --version 确认工具版本。内核 5.x 及以上版本推荐使用 DTC 1.6.0+,以支持更严格的语法检查和 overlay 特性。
3.2 DTS 到 DTB 的编译过程
编译过程并非简单的文本替换,而是经过词法分析、语法解析、节点合并、属性校验、二进制编码等多个阶段。下图展示了完整的编译流水线:
编译过程中,DTC 会执行以下关键步骤:
- 预处理:调用 C 预处理器(cpp)处理
#include、#define等指令,将.dtsi文件内容展开到主文件中。 - 词法与语法分析:解析 DTS 语法,构建抽象语法树(AST)。此阶段会报告格式错误,如缺少分号、花括号不匹配等。
- 节点合并与覆盖:根据标签(label)和路径规则,合并同名节点,处理属性覆盖。例如,多个
&uart1引用会被合并为一个节点。 - 属性校验:检查
reg、interrupts等属性的格式、长度和范围,输出警告或错误。 - 二进制编码:将校验通过的结构扁平化为 FDT(Flattened Device Tree)格式,生成
.dtb文件。
常见编译错误:
ERROR: Input tree has errors— 语法错误,需检查 DTS 文件。WARNING: unit_address_vs_reg— 节点名中的地址与reg属性不匹配。ERROR: label or path not found— 引用的标签或路径不存在。
3.3 DTB 反编译为 DTS
反编译是将二进制 DTB 还原为可读的 DTS 文本,常用于分析现有固件、验证编译结果、或从运行中的系统提取设备树。反编译过程是编译的逆过程,但会丢失部分信息:
| 信息类型 | 编译时保留 | 反编译后状态 | 说明 |
|---|---|---|---|
| 注释 | 否 | 丢失 | DTC 不保留注释信息 |
| 宏定义 | 否 | 展开为常量 | #define 在预处理阶段已展开 |
| 标签(label) | 是 | 保留 | 二进制中编码了符号信息 |
| 包含文件结构 | 否 | 合并为单文件 | 所有 .dtsi 内容已内联 |
| 节点顺序 | 部分 | 可能重排 | 按地址排序,非原始顺序 |
反编译的基本命令:
# 标准反编译
dtc -I dtb -O dts -o decompiled.dts original.dtb
# 反编译并保留符号(用于调试 overlay)
dtc -I dtb -O dts -o decompiled.dts -@ original.dtb
# 从 /sys/firmware/devicetree 反编译运行中的系统
dtc -I fs -O dts -o running.dts /sys/firmware/devicetree/base
实用技巧:使用
dtc -I fs -O dts /sys/firmware/devicetree/base 可以直接从运行中的 Linux 系统提取当前生效的设备树,无需重启或访问原始 DTS 文件。这对于调试驱动匹配问题极为方便。
3.4 编译与反编译的典型应用场景
下表总结了 DTC 工具在不同开发阶段的具体用途:
| 场景 | 操作 | 命令示例 | 目的 |
|---|---|---|---|
| 开发新板级支持 | 编译 | dtc -I dts -O dtb -o myboard.dtb myboard.dts |
将编写的 DTS 转换为内核可加载的 DTB |
| 验证 DTS 语法 | 编译(带警告) | dtc -I dts -O dtb -o /dev/null myboard.dts |
仅检查语法,不生成文件 |
| 分析第三方固件 | 反编译 | dtc -I dtb -O dts -o vendor.dts vendor.dtb |
逆向分析闭源 DTB 的节点结构 |
| 调试驱动匹配 | 反编译运行中系统 | dtc -I fs -O dts -o live.dts /sys/firmware/devicetree/base |
确认实际生效的设备树内容 |
| 设备树插件开发 | 编译(保留符号) | dtc -I dts -O dtb -@ -o overlay.dtbo overlay.dts |
生成可动态加载的 overlay 文件 |
3.5 反编译结果与原始 DTS 的差异分析
反编译得到的 DTS 与原始 DTS 并非完全一致。下图直观展示了从原始 DTS 到 DTB 再到反编译 DTS 的信息变化:
反编译最佳实践:
- 反编译结果应作为参考而非原始源码的替代品。修改时请基于原始 DTS 进行。
- 如果反编译后需要重新编译,务必添加
-@选项保留符号,否则标签信息会丢失。 - 对比原始 DTS 与反编译 DTS 时,重点关注
reg、interrupts、clocks等关键属性值是否一致。
3.6 实战:完整编译与反编译示例
以下是一个完整的操作流程,从编写 DTS 到验证反编译结果:
# 1. 编写一个简单的 DTS 文件(example.dts)
/dts-v1/;
/ {
compatible = "acme,myboard";
#address-cells = <1>;
#size-cells = <1>;
uart0: serial@10000000 {
compatible = "ns16550";
reg = <0x10000000 0x1000>;
interrupts = <0 33 4>;
};
};
# 2. 编译为 DTB
dtc -I dts -O dtb -o example.dtb example.dts
# 3. 反编译回 DTS
dtc -I dtb -O dts -o example_decompiled.dts example.dtb
# 4. 对比差异(使用 diff 工具)
diff -u example.dts example_decompiled.dts
执行 diff 后,你会看到反编译版本丢失了注释(如果有),且节点顺序可能按地址重新排列,但核心属性值完全一致。
内核内置编译支持:在 Linux 内核源码树中,可以通过
make dtbs 命令批量编译所有 .dts 文件。编译后的 .dtb 文件位于 arch/<arch>/boot/dts/ 目录下。使用 make dtbs_check 还可以调用 DTC 的额外校验规则,确保设备树符合绑定规范。