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 属性中的地址数量不一致。
DTS 编译为 DTB 流程 .dts 源文件 文本格式 词法分析 Token 化 语法解析 AST 构建 符号解析 phandle/引用 .dtb 二进制 扁平化结构 错误报告与中止

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 设备树编译与反编译的典型工作流

在实际的嵌入式开发中,设备树的编译与反编译通常嵌入在构建系统中。以下是一个典型的迭代工作流:

  1. 编辑阶段:在 arch/arm/boot/dts/ 或 arch/arm64/boot/dts/ 中修改 .dts 或 .dtsi 文件。
  2. 编译阶段:通过内核 Makefile 或手动调用 DTC 生成 .dtb 文件。
  3. 部署阶段:将 .dtb 文件烧录到 boot 分区或与内核镜像打包。
  4. 验证阶段:系统启动后,通过 /sys/firmware/devicetree/base 检查实际加载的设备树。
  5. 调试阶段:从目标板导出 DTB,反编译为 DTS,与原始源文件对比差异。
设备树编译/反编译迭代工作流 编辑 DTS 文本编辑器 DTC 编译 .dts → .dtb 部署 DTB 烧录/打包 系统启动 加载验证 导出 DTB /sys 导出 DTC 反编译 .dtb → .dts 差异对比 diff 工具
效率技巧:在 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 不一致或内核崩溃。反编译-修改-重新编译的流程才是安全可靠的做法。