2. 设备树编译与反编译:DTC工具的使用

设备树源文件(.dts / .dtsi)必须经过编译生成二进制设备树 Blob(.dtb),才能被引导加载程序(如 U-Boot)或内核解析。DTC(Device Tree Compiler)是完成这一转换的标准工具,同时它也支持将 .dtb 反编译回可读的 .dts,用于调试、逆向或验证编译结果。

2.1 DTC 工具概述

DTC 是 Linux 内核源码树中维护的设备树编译器,通常位于 scripts/dtc/ 目录下。它支持三种主要操作模式:

操作模式 输入 输出 典型用途
编译 .dts / .dtsi .dtb 将可读的源文件转换为内核可解析的二进制格式
反编译 .dtb .dts 将二进制 blob 还原为可读源文件,用于调试或验证
输出 DTS 源码 .dts .dts(格式化) 检查预处理后的完整设备树,包含所有 #include 展开
提示:在嵌入式 Linux 项目中,DTC 通常由内核构建系统(Kbuild)自动调用。但手动使用 DTC 进行编译和反编译,是排查设备树问题的必备技能。

2.2 编译:从 DTS 到 DTB

编译设备树源文件的基本命令格式如下:

dtc -I dts -O dtb -o <输出文件.dtb> <输入文件.dts>

其中:

  • -I dts:指定输入格式为 DTS 源文件
  • -O dtb:指定输出格式为 DTB 二进制
  • -o:指定输出文件名

实际示例:

# 基本编译
dtc -I dts -O dtb -o myboard.dtb myboard.dts

# 启用空格优化(减小 DTB 体积)
dtc -I dts -O dtb -o myboard.dtb -@ myboard.dts

# 输出编译过程的详细信息
dtc -I dts -O dtb -o myboard.dtb -V myboard.dts
注意:如果源文件使用了 #include 预处理指令(如 #include "skeleton.dtsi"),必须先通过 C 预处理器(cpp)展开,再送入 DTC。内核构建系统会自动处理这一步骤,但手动编译时需使用 -E 选项或先运行 cpp。

2.3 反编译:从 DTB 到 DTS

反编译用于将二进制 DTB 还原为可读的 DTS 源文件,这在以下场景中非常有用:

  • 验证编译结果是否符合预期
  • 从已部署的设备中提取设备树配置
  • 分析第三方或闭源设备树的布局

基本命令格式:

dtc -I dtb -O dts -o <输出文件.dts> <输入文件.dtb>

实际示例:

# 基本反编译
dtc -I dtb -O dts -o myboard_reverse.dts myboard.dtb

# 反编译并保留所有标签(label)
dtc -I dtb -O dts -o myboard_reverse.dts -@ myboard.dtb

# 反编译时禁用符号表展开(更接近原始源文件)
dtc -I dtb -O dts -o myboard_reverse.dts -s myboard.dtb
关键区别:反编译生成的 DTS 文件会丢失原始源文件中的注释、宏定义和部分格式信息,但节点结构、属性值和寄存器地址完全保留。反编译结果适合用于分析,不适合直接作为源文件回编。

2.4 编译与反编译流程

下图展示了 DTC 在设备树开发流程中的核心位置,以及编译与反编译之间的双向转换关系:

DTC 编译与反编译流程 DTS 源文件 myboard.dts 编译 (Compile) DTB 二进制 myboard.dtb 反编译 (Decompile) 反编译 DTS myboard_reverse.dts 说明:编译是开发阶段的主要操作;反编译主要用于调试、验证和逆向分析已部署的设备树

2.5 常用 DTC 选项速查

以下表格列出了 DTC 最常用的命令行选项,帮助你在不同场景下快速选择正确的参数:

选项 说明 使用场景
-I <格式> 指定输入格式:dts、dtb、fs(文件系统) 编译时用 -I dts,反编译时用 -I dtb
-O <格式> 指定输出格式:dts、dtb、asm(汇编) 编译时用 -O dtb,反编译时用 -O dts
-o <文件> 指定输出文件名 所有操作模式
-@ 生成符号表(symbols),支持设备树覆盖 需要动态覆盖功能时必须启用
-s 反编译时简化输出,不展开符号引用 反编译时保留原始节点引用结构
-V 显示详细的编译/反编译过程信息 调试编译错误或验证文件路径
-E 仅执行预处理(展开 #include),不编译 检查预处理后的完整源文件
-H 指定 phandle 格式:legacy、epapr 兼容旧版内核或特定引导程序

2.6 编译与反编译的常见问题

常见错误 1:未启用覆盖支持
如果编译时未使用 -@ 选项,生成的 DTB 将不包含符号表。后续加载设备树覆盖(DT Overlay)时,内核无法解析覆盖目标,导致覆盖失败。
解决方案:在编译主 DTB 和覆盖 DTS 时,始终添加 -@ 选项。
常见错误 2:反编译结果与源文件不一致
反编译生成的 DTS 文件会丢失原始源文件中的注释、宏定义(如 #define)和部分格式排版。这是正常现象,因为 DTB 二进制格式不保留这些信息。
解决方案:反编译结果仅用于分析节点结构和属性值,不要直接作为源文件回编。
最佳实践:在嵌入式项目中,建议将 DTC 集成到 Makefile 或构建脚本中,并统一使用 -@ 选项。同时,保留原始 .dts 源文件作为唯一真相来源,反编译仅用于调试和验证。

2.7 编译与反编译的对比总结

下表从多个维度对比了 DTC 编译与反编译操作的核心差异:

对比维度 编译(DTS → DTB) 反编译(DTB → DTS)
主要用途 生成内核可解析的二进制设备树 调试、验证、逆向分析
信息保留 保留所有节点、属性、标签、符号表 保留节点和属性,丢失注释和宏定义
输出可读性 二进制,不可直接阅读 文本格式,可直接阅读和编辑
依赖预处理 需要先展开 #include 不需要,DTB 已包含完整信息
覆盖支持 需加 -@ 生成符号表 反编译时 -@ 保留符号引用
典型文件大小 几 KB 到几十 KB 几十 KB 到几百 KB