3、设备树编译器:DTC工具安装、编译与反编译、反汇编技巧

设备树写好了,怎么变成机器能懂的东西?

答案就是 DTC——Device Tree Compiler。说白了,它就是把 .dts 文本文件,编译成 .dtb 二进制文件的工具。反过来,也能把 .dtb 反编译回 .dts,方便调试。

我个人习惯,每次拿到一块新板子,第一件事就是反编译它的 .dtb,看看厂商到底配了啥。这比看文档快多了。

3.1 DTC 工具的安装

DTC 是设备树开发的基础工具。大部分 Linux 发行版都自带,但版本可能比较老。我建议自己编译最新版,因为新版本支持更多语法检查,能帮你提前发现坑。

3.1.1 从源码编译安装

这是最推荐的方式。你想想看,嵌入式开发环境千奇百怪,源码编译最可控。

# 下载 DTC 源码
git clone git://git.kernel.org/pub/scm/utils/dtc/dtc.git
cd dtc

# 编译
make

# 安装到系统路径(需要 root 权限)
sudo make install

安装完成后,验证一下:

dtc --version
# 输出类似:Version: DTC 1.7.0-g8c5a0220
我的小技巧: 如果你在嵌入式开发板上编译,记得先确认交叉编译工具链。我曾在 ARM 板子上直接 make,结果编译出来的 dtc 跑不起来——因为本地 gcc 和目标架构不匹配。后来老老实实用了交叉编译。

3.1.2 通过包管理器安装

如果你只是临时用一下,或者不想折腾源码,包管理器也能装:

发行版 命令
Ubuntu/Debian sudo apt-get install device-tree-compiler
Fedora/RHEL sudo dnf install dtc
Arch Linux sudo pacman -S dtc
注意: 包管理器安装的版本可能比较旧。比如 Ubuntu 20.04 自带的 DTC 是 1.5.0,而最新版已经到 1.7.x 了。旧版本对某些新语法(比如 /delete-node/)支持不好。我曾经因为这个排查了半天,最后发现是 DTC 版本太老,不认新语法。

3.2 编译:从 .dts 到 .dtb

编译是最常用的操作。命令很简单:

dtc -I dts -O dtb -o output.dtb input.dts

参数说明:

  • -I dts:输入格式是 .dts 文本
  • -O dtb:输出格式是 .dtb 二进制
  • -o output.dtb:指定输出文件名

实际项目中,我经常加几个额外选项:

dtc -I dts -O dtb -o myboard.dtb -@ -H epapr myboard.dts
  • -@:生成符号表,方便后续 overlay 加载。这个在模块化设计中特别有用。
  • -H epapr:指定 phandle 格式为 ePAPR 标准。大部分 ARM 平台都用这个。
避坑指南: 我曾经在编译时忘记加 -@ 选项,结果后面做设备树 overlay 时,内核死活加载不上。查了两天才发现是符号表没生成。嗯,从那以后,我写 Makefile 都会默认加上 -@。

3.3 反编译:从 .dtb 回到 .dts

反编译是调试利器。拿到一块新板子,或者想看看内核实际加载了啥,反编译一下全明白了。

dtc -I dtb -O dts -o output.dts input.dtb

举个例子,假设你有一个 kernel.dtb:

dtc -I dtb -O dts -o kernel.dts kernel.dtb

打开 kernel.dts,你就能看到完整的设备树结构。包括那些被 #include 展开后的内容,以及所有默认值。

我的习惯: 每次拿到厂商的 BSP,我第一件事就是反编译它的 .dtb,然后对比官方文档。经常能发现厂商偷偷改了某些参数,或者加了未文档化的节点。说白了,反编译就是你的「照妖镜」。

3.4 反汇编:查看二进制布局

反汇编比反编译更底层。它直接展示 .dtb 文件的二进制结构,包括每个 token、每个字符串、每个数值的原始表示。

dtc -I dtb -O dts -o output.dts -H both input.dtb

或者用 fdtdump 工具(DTC 自带):

fdtdump input.dtb

输出会显示类似这样的内容:

// magic:               0xd00dfeed
// totalsize:           0x1a4 (420)
// off_dt_struct:       0x48
// off_dt_strings:      0x180
// off_mem_rsvmap:      0x28
// version:             17
// last_comp_version:   16
// boot_cpuid_phys:     0x0
// size_dt_strings:     0x24
// size_dt_struct:      0x138

这些字段的含义:

字段 含义
magic 魔数,固定为 0xd00dfeed,用于识别 .dtb 文件
totalsize 文件总大小(字节)
off_dt_struct 结构体块偏移
off_dt_strings 字符串块偏移
version 设备树版本号
什么时候需要反汇编? 说实话,日常开发中很少用到。但如果你在做 bootloader 或者内核早期启动调试,需要手动解析 .dtb 时,反汇编就派上用场了。我记得有一次,uboot 加载 .dtb 后系统崩溃,我通过反汇编发现是 totalsize 字段被 uboot 修改了,导致内核解析出错。

3.5 常用编译选项速查

这里整理了我常用的 DTC 选项,方便你快速查阅:

选项 说明 使用场景
-I 指定输入格式(dts/dtb/fs) 编译或反编译
-O 指定输出格式(dts/dtb/asm) 编译或反编译
-o 指定输出文件 所有场景
-@ 生成符号表(phandle 信息) 需要 overlay 支持时
-H 指定 phandle 格式(epapr/legacy/both) 兼容不同内核版本
-W 启用特定警告 代码检查
-E 启用特定错误检查 严格模式编译
推荐组合: 我一般在 Makefile 里这样写:
dtc -I dts -O dtb -o $@ -@ -W no-unit_address_vs_reg -W no-gpios_property $<
这样既生成了符号表,又屏蔽了一些不太重要的警告,让编译输出更清爽。

3.6 知识体系总览

下面这张图,帮你理清 DTC 工具的核心逻辑:

DTC 工具核心工作流 .dts 源文件 文本格式,人类可读 编译 dtc -I dts -O dtb DTC 编译器 词法/语法分析 .dtb 二进制 机器可读,内核加载 反编译 dtc -I dtb -O dts 反汇编 fdtdump 或 -H both 二进制布局信息 magic / totalsize / version off_dt_struct / off_dt_strings 核心逻辑:.dts → 编译 → .dtb → 反编译 → .dts(调试用) 反汇编用于底层调试,查看二进制文件结构

这张图展示了 DTC 的完整工作流。你从左边写 .dts,经过 DTC 编译成 .dtb,内核直接加载。调试时,你可以从 .dtb 反编译回 .dts,或者反汇编查看二进制结构。

嗯,掌握了这些,设备树的编译和调试你就基本入门了。实际项目中,我建议你把 DTC 的常用命令写成 Makefile 目标,一键编译、一键反编译,能省不少事。

可可精品资料库