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 的编译过程

编译过程并非简单的文本替换,而是经过词法分析、语法解析、节点合并、属性校验、二进制编码等多个阶段。下图展示了完整的编译流水线:

DTS → DTB 编译流水线 .dts 源文件 .dtsi 包含文件 C 预处理器 (cpp) 展开宏与包含 词法 & 语法分析 (lex/yacc) 构建 AST 节点合并 & 覆盖 (标签引用 / 覆盖规则) 属性校验 & 警告 (类型 / 范围 / 必填项) 二进制编码 (扁平化 / 字节对齐) .dtb 二进制文件 关键检查点 ✓ 语法错误 ✓ 节点唯一性 ✓ 属性合规性

编译过程中,DTC 会执行以下关键步骤:

  1. 预处理:调用 C 预处理器(cpp)处理 #include、#define 等指令,将 .dtsi 文件内容展开到主文件中。
  2. 词法与语法分析:解析 DTS 语法,构建抽象语法树(AST)。此阶段会报告格式错误,如缺少分号、花括号不匹配等。
  3. 节点合并与覆盖:根据标签(label)和路径规则,合并同名节点,处理属性覆盖。例如,多个 &uart1 引用会被合并为一个节点。
  4. 属性校验:检查 reg、interrupts 等属性的格式、长度和范围,输出警告或错误。
  5. 二进制编码:将校验通过的结构扁平化为 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 → DTB → 反编译 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 的额外校验规则,确保设备树符合绑定规范。