3. 设备树编译与反编译:DTC工具使用、DTS编译为DTB、DTB反编译为DTS
设备树源文件(.dts)是给人阅读和编辑的文本格式,而内核引导时实际加载的是二进制格式的设备树 blob(.dtb)。从 .dts 到 .dtb 的转换由设备树编译器(Device Tree Compiler,DTC)完成,反向过程称为反编译。掌握 DTC 工具的使用是嵌入式开发者的基本功。
核心要点:DTC 是设备树生态的“翻译官”,负责在文本描述与二进制表示之间双向转换。编译时检查语法错误,反编译时用于调试和逆向分析。
3.1 DTC 工具概述
DTC 是 Linux 内核源码树中自带的工具,位于 scripts/dtc/ 目录下。编译内核时会自动编译出 dtc 可执行文件。也可以单独安装:
# Ubuntu/Debian
sudo apt-get install device-tree-compiler
# Fedora/RHEL
sudo dnf install dtc
# 验证安装
dtc --version
# 输出示例:Version 1.6.1
DTC 支持的主要操作模式:
| 操作 | 命令格式 | 说明 |
|---|---|---|
| 编译 | dtc -I dts -O dtb -o output.dtb input.dts |
将 DTS 文本编译为 DTB 二进制 |
| 反编译 | dtc -I dtb -O dts -o output.dts input.dtb |
将 DTB 二进制反编译为 DTS 文本 |
| 转储 | dtc -I dtb -O dts -o - input.dtb |
输出到标准输出,快速查看 |
| 检查 | dtc -I dts -O dtb -o /dev/null input.dts |
仅检查语法,不生成文件 |
提示:DTC 还支持
-@ 选项生成插件符号节点,用于设备树覆盖(overlay);-H 选项可指定 phandle 格式(legacy/epapr)。
3.2 DTS 编译为 DTB:完整流程
编译过程分为词法分析、语法解析、符号解析和二进制输出四个阶段。以下是一个完整的编译示例:
# 1. 准备一个简单的 DTS 文件(example.dts)
/dts-v1/;
/ {
compatible = "acme,custom-board";
#address-cells = <1>;
#size-cells = <1>;
cpu@0 {
compatible = "arm,cortex-a9";
reg = <0x0 0x100>;
};
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x10000000>;
};
serial@ff000000 {
compatible = "ns16550";
reg = <0xff000000 0x1000>;
interrupts = <0 33 4>;
};
};
# 2. 编译为 DTB
dtc -I dts -O dtb -o example.dtb example.dts
# 3. 查看生成的二进制大小
ls -lh example.dtb
# 输出示例:-rw-r--r-- 1 user user 312 Mar 15 10:30 example.dtb
# 4. 使用 hexdump 查看二进制头部
hexdump -C example.dtb | head -5
# 00000000 d0 0d fe ed 00 00 01 38 00 00 00 38 00 00 00 10 |.......8...8....|
# 00000010 00 00 00 0c 00 00 00 0c 00 00 00 00 00 00 00 00 |................|
# 00000020 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 03 |................|
DTB 文件头部魔数为 0xd00dfeed(大端序),前 4 字节固定标识这是一个有效的设备树二进制文件。
常见编译错误:
- 语法错误:缺少分号、花括号不匹配、属性值格式错误。
- 标签重复:同一个节点名出现两次,或 phandle 值冲突。
- 引用未定义:使用
&label引用但标签未定义。 - 地址/大小不匹配:
#address-cells与reg属性中的地址数量不一致。
3.3 DTB 反编译为 DTS
反编译用于调试、逆向分析或从运行中的系统提取设备树。内核在 /sys/firmware/devicetree/base 下以文件系统形式暴露了当前使用的设备树,可以从中重建 DTB 再反编译。
# 方法一:从 /sys 导出并反编译
# 1. 从 sysfs 导出 DTB
dtc -I fs -O dtb -o extracted.dtb /sys/firmware/devicetree/base
# 2. 反编译为 DTS
dtc -I dtb -O dts -o extracted.dts extracted.dtb
# 方法二:直接反编译已有的 DTB 文件
dtc -I dtb -O dts -o board.dts board.dtb
# 方法三:反编译并保留所有信息(包括空节点、标签)
dtc -I dtb -O dts -o full.dts -@ board.dtb
反编译注意事项:
- 反编译结果会丢失原始 DTS 中的注释和宏定义(如
#include展开后的内容)。 - 标签(label)在编译时被解析为 phandle,反编译时会生成
phandle = <数字>属性,但原始标签名可能丢失。 - 使用
-@选项可以保留插件符号信息,便于 overlay 开发。 - 反编译输出的格式与原始 DTS 可能存在缩进和命名差异,但语义等价。
3.4 DTC 高级选项与技巧
| 选项 | 功能 | 使用场景 |
|---|---|---|
-@ |
生成 __symbols__ 和 __fixups__ 节点 |
设备树覆盖(overlay)开发 |
-H epapr |
使用 ePAPR 标准的 phandle 格式 | 兼容老旧 bootloader |
-W no-unit_address_vs_reg |
关闭特定警告 | 忽略已知的非关键警告 |
-E |
生成依赖文件(类似 gcc -M) | Makefile 自动化构建 |
-O asm |
输出汇编格式 | 汇编级调试 |
# 实战:使用 -@ 生成支持 overlay 的 DTB
dtc -@ -I dts -O dtb -o base.dtb base.dts
# 查看生成的符号节点
dtc -I dtb -O dts base.dtb | grep -A 10 "__symbols__"
# 关闭特定警告(例如地址与 reg 不匹配)
dtc -W no-unit_address_vs_reg -I dts -O dtb -o output.dtb input.dts
3.5 设备树编译与反编译的典型工作流
在实际的嵌入式开发中,设备树的编译与反编译通常嵌入在构建系统中。以下是一个典型的迭代工作流:
- 编辑阶段:在
arch/arm/boot/dts/或arch/arm64/boot/dts/中修改.dts或.dtsi文件。 - 编译阶段:通过内核 Makefile 或手动调用 DTC 生成
.dtb文件。 - 部署阶段:将
.dtb文件烧录到 boot 分区或与内核镜像打包。 - 验证阶段:系统启动后,通过
/sys/firmware/devicetree/base检查实际加载的设备树。 - 调试阶段:从目标板导出 DTB,反编译为 DTS,与原始源文件对比差异。
效率技巧:在 Makefile 中使用
$(DTC) -E 生成依赖文件,结合 -W 选项控制警告级别,可以实现增量编译和自动化检查。对于大型设备树(如 i.MX8、RK3588 系列),建议使用 dtc -O dtb -@ -W no-unit_address_vs_reg 减少噪音警告。
3.6 常见问题与排查
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 编译报错“FATAL ERROR: Unable to parse input tree” | DTS 文件语法错误 | 检查分号、花括号、引号是否匹配;使用 dtc -I dts -O dtb -o /dev/null 定位错误行 |
| 反编译后节点顺序与原始 DTS 不同 | DTB 不保存节点顺序信息 | 这是正常现象,语义等价即可;使用 diff -w 忽略空白差异 |
反编译结果中出现 phandle = <数字> 而非标签 |
编译时标签被解析为数字 phandle | 原始标签名丢失,需参考原始 DTS 或使用 -@ 保留符号表 |
| 编译警告“unit_address_vs_reg” | 节点名中的地址与 reg 属性不匹配 |
检查节点命名规范,或使用 -W no-unit_address_vs_reg 忽略 |
| DTB 文件过大 | 包含大量字符串或未使用的节点 | 使用 dtc -O dtb -H epapr 压缩 phandle 格式;删除未使用的 status = "disabled" 节点 |
重要提醒:不要在生产环境中直接修改 DTB 二进制文件。始终修改 DTS 源文件后重新编译。直接修改 DTB 可能导致结构损坏、phandle 不一致或内核崩溃。反编译-修改-重新编译的流程才是安全可靠的做法。