2. 设备树编译与反编译:DTC工具的使用,DTS到DTB的编译过程
设备树源文件(.dts)是人类可读的文本描述,而内核引导时实际使用的是二进制格式的设备树二进制文件(.dtb)。从 .dts 到 .dtb 的转换,以及逆向的从 .dtb 到 .dts 的还原,都依赖于 DTC(Device Tree Compiler) 工具。本章将深入解析 DTC 的编译流程、关键选项以及常见操作。
2.1 DTC 工具概述
DTC 是设备树编译器的核心实现,通常作为 Linux 内核源码树的一部分提供(位于 scripts/dtc/ 目录)。它负责将文本格式的 .dts 文件编译为扁平化的二进制 .dtb 文件,同时支持反编译操作。
提示: 在大多数嵌入式 Linux 开发环境中,DTC 已预装在主机系统中。若未安装,可通过
apt-get install device-tree-compiler(Debian/Ubuntu)或 yum install dtc(Fedora/RHEL)快速获取。
2.1.1 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 |
将二进制设备树还原为可读的文本源文件 |
| 生成汇编(DTS → S) | dtc -I dts -O asm -o output.S input.dts |
生成汇编代码,用于早期启动阶段 |
| 检查与调试 | dtc -I dts -O dtb -o /dev/null input.dts |
仅检查语法错误,不输出文件 |
2.2 DTS 到 DTB 的编译流程
编译过程并非简单的文本到二进制的转换,而是经过多个阶段的处理,包括预处理、解析、展开和扁平化。下图展示了完整的编译流水线:
2.2.1 编译阶段详解
- 预处理阶段: DTC 调用 C 预处理器(CPP)处理
#include、#define等指令。所有.dtsi文件在此阶段被展开并嵌入到主文件中。 - 语法解析阶段: DTC 解析器读取预处理后的文本流,构建抽象语法树(AST)。此阶段检查节点、属性、标号等语法正确性。
- 展开与合并阶段: 处理
/include/伪指令(与 CPP 的#include不同),合并多个设备树片段。同时解析&label引用,将覆盖节点与基节点合并。 - 扁平化阶段: 将树形结构转换为扁平设备树(FDT)格式,生成二进制
.dtb文件。此阶段包括内存布局优化、字符串表压缩等操作。
关键概念:
/include/ 与 #include 的区别在于:#include 由 CPP 处理,支持宏定义;/include/ 由 DTC 直接处理,仅做文本插入,不支持宏展开。现代设备树推荐统一使用 #include。
2.3 DTC 常用选项与参数
DTC 提供了丰富的命令行选项,用于控制编译行为、输出格式和调试信息。下表列出了最常用的选项:
| 选项 | 完整参数 | 功能描述 |
|---|---|---|
-I |
--in-format |
指定输入格式:dts、dtb、fs(文件系统) |
-O |
--out-format |
指定输出格式:dts、dtb、asm、yaml |
-o |
--out |
指定输出文件名 |
-@ |
--symbols |
生成符号节点(用于设备树叠加层) |
-H |
--phandle |
指定 phandle 生成策略:epapr、linux、both |
-W |
--warning |
启用特定警告,如 -W unit_address_vs_reg |
-E |
--error |
将特定警告视为错误,如 -E graph_child_address |
-V |
--version |
指定 DTB 版本(16 或 17) |
2.4 编译与反编译实战
2.4.1 基本编译操作
以下命令将 imx6ull.dts 编译为 imx6ull.dtb,并启用所有警告:
# 基本编译
dtc -I dts -O dtb -o imx6ull.dtb imx6ull.dts
# 带警告检查的编译
dtc -I dts -O dtb -o imx6ull.dtb imx6ull.dts -W all
# 生成带符号的 DTB(支持叠加层)
dtc -I dts -O dtb -o imx6ull.dtb imx6ull.dts -@
2.4.2 反编译操作
反编译用于调试或分析已有的 .dtb 文件:
# 基本反编译
dtc -I dtb -O dts -o imx6ull_reverse.dts imx6ull.dtb
# 反编译并保留注释(如果原始 DTB 包含)
dtc -I dtb -O dts -o imx6ull_reverse.dts imx6ull.dtb -H epapr
注意: 反编译生成的
.dts 文件会丢失原始源文件中的注释、宏定义和代码格式。反编译结果仅用于调试,不应作为修改设备树的唯一依据。
2.4.3 使用 Makefile 集成编译
在内核源码树中,设备树编译通常通过 Makefile 自动完成。以下是一个典型的编译流程:
# 在内核源码根目录
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs
# 单独编译某个设备树
make ARCH=arm imx6ull-14x14-evk.dtb
# 查看编译过程详细输出
make ARCH=arm dtbs V=1
2.5 编译错误与调试
DTC 编译过程中常见的错误类型及其解决方法:
| 错误类型 | 典型信息 | 原因与解决 |
|---|---|---|
| 语法错误 | syntax error before '...' |
检查分号、花括号、引号是否匹配;属性值格式是否正确 |
| 引用错误 | Reference to non-existent node or label "..." |
&label 引用的节点未定义;检查 .dtsi 是否包含 |
| 地址格式错误 | reg property size mismatch |
reg 属性中的地址长度与 #address-cells 不匹配 |
| 中断映射错误 | interrupt-map length mismatch |
中断映射表长度与 #interrupt-cells 定义不一致 |
| 重复节点 | Duplicate label "..." |
同一作用域内出现重复的标号定义;检查 .dtsi 包含冲突 |
2.6 DTB 文件结构分析
编译生成的 .dtb 文件遵循 FDT(Flattened Device Tree)格式,其内部结构如下:
2.6.1 头部字段说明
FDT 头部包含 40 字节的元数据,用于解析整个设备树:
| 偏移 | 字段名 | 大小 | 说明 |
|---|---|---|---|
| 0x00 | magic |
4 字节 | 魔数,固定为 0xD00DFEED |
| 0x04 | totalsize |
4 字节 | 整个 DTB 文件的总大小(字节) |
| 0x08 | off_dt_struct |
4 字节 | 结构块相对于文件头的偏移 |
| 0x0C | off_dt_strings |
4 字节 | 字符串块相对于文件头的偏移 |
| 0x10 | off_mem_rsvmap |
4 字节 | 内存预留块相对于文件头的偏移 |
| 0x14 | version |
4 字节 | DTB 版本号(当前为 17) |
| 0x18 | last_comp_version |
4 字节 | 向后兼容的最低版本号(通常为 16) |
| 0x1C | boot_cpuid_phys |
4 字节 | 引导 CPU 的物理 ID |
| 0x20 | size_dt_strings |
4 字节 | 字符串块的大小(字节) |
| 0x24 | size_dt_struct |
4 字节 | 结构块的大小(字节) |
2.7 设备树叠加层编译
设备树叠加层(Device Tree Overlay)允许在运行时动态修改设备树。编译叠加层时需要特殊处理:
# 编译叠加层源文件(.dtso → .dtbo)
dtc -I dts -O dtb -o my_overlay.dtbo my_overlay.dtso -@
# 检查叠加层是否包含必要的符号
dtc -I dtb -O dts my_overlay.dtbo | grep -E "(__symbols__|__fixups__)"
最佳实践: 叠加层文件使用
.dtso 扩展名以区别于普通设备树。编译时必须使用 -@ 选项生成符号表,否则内核无法解析叠加层中的引用。
2.8 本章小结
DTC 工具是设备树开发的核心环节,掌握其编译与反编译操作是嵌入式 Linux 开发的基本功。通过本章学习,应重点理解:
- DTS 到 DTB 的完整编译流水线,包括预处理、解析、展开和扁平化四个阶段
- DTC 命令行选项的灵活运用,特别是
-I、-O、-@等关键参数 - DTB 文件的内部结构,包括头部、内存预留块、结构块和字符串块的组织方式
- 常见编译错误的识别与修复方法
- 设备树叠加层的编译要求与符号生成机制