第3章 设备树编译器(DTC):安装、使用与反编译实战
设备树编译器(Device Tree Compiler,简称 DTC),是咱们和设备树打交道最频繁的工具。说白了,它就是一座桥——把人类可读的 .dts 文本文件,转成内核能解析的 .dtb 二进制文件。反过来,也能把 .dtb 反编译成 .dts,方便你逆向分析别人的设备树。
我刚开始接触设备树时,总觉得 DTC 就是个黑盒子。直到有一次调试一块新板子,死活起不来,最后发现是 DTC 版本太老,不认识新语法。嗯,从那以后,我对 DTC 的版本和用法就格外上心了。
3.1 DTC 的安装
DTC 的安装方式取决于你的开发环境。我个人习惯在 Ubuntu/Debian 系统上开发,安装最省事。
3.1.1 从包管理器安装(推荐)
这是最快的方式,适合大多数场景:
# Ubuntu / Debian
sudo apt-get install device-tree-compiler
# Fedora / CentOS / RHEL
sudo yum install dtc
# Arch Linux
sudo pacman -S dtc
安装完成后,验证一下版本:
dtc --version
# 输出示例:Version 1.6.1
#include 和 /dts-v1/; 的支持不够完善,我在项目中踩过这个坑。
3.1.2 从源码编译安装
如果你需要最新特性,或者交叉编译给嵌入式环境用,那就得自己编译了。
git clone https://git.kernel.org/pub/scm/utils/dtc/dtc.git
cd dtc
make
sudo make install
交叉编译时,指定 CC 环境变量即可:
export CC=arm-linux-gnueabihf-gcc
make
# 生成的可执行文件在当前目录下:dtc
file dtc 确认架构。我曾经把 x86 的 DTC 扔到 ARM 板子上,结果执行时直接段错误。
3.2 DTC 的核心用法
DTC 的命令行参数不多,但每个都很关键。我整理了一张速查表,你平时用的时候可以对照着看。
| 参数 | 说明 | 示例 |
|---|---|---|
-I |
输入格式:dts / dtb / fs | -I dts |
-O |
输出格式:dts / dtb / asm | -O dtb |
-o |
输出文件名 | -o output.dtb |
-@ |
生成 overlay 支持(符号表) | -@ |
-H |
选择 phandle 格式:legacy / epapr / both | -H epapr |
-V |
设置 DTS 版本(16 / 17) | -V 17 |
-S |
设置 DTB 最小大小(字节) | -S 4096 |
-p |
设置 DTB 填充空间(字节) | -p 1024 |
3.3 文件格式转换实战
咱们直接上手操作。下面这几种转换场景,你在实际开发中几乎天天都会遇到。
3.3.1 .dts → .dtb(编译)
这是最常用的操作。把设备树源文件编译成二进制:
dtc -I dts -O dtb -o myboard.dtb myboard.dts
如果你有 .dtsi 头文件,DTC 会自动通过 #include 展开。举个例子:
// myboard.dts
/dts-v1/;
#include "soc.dtsi"
/ {
model = "MyBoard";
compatible = "mycompany,myboard";
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x10000000>;
};
};
编译时,DTC 会把 soc.dtsi 的内容直接插入进来。我个人习惯用 -@ 参数,方便后面做设备树 overlay:
dtc -I dts -O dtb -@ -o myboard.dtb myboard.dts
3.3.2 .dtb → .dts(反编译)
反编译是调试利器。当你拿到一个 .dtb 文件,想看看里面到底定义了哪些节点,直接反编译:
dtc -I dtb -O dts -o decompiled.dts myboard.dtb
我曾经在项目中遇到过一个问题:板子启动时内核报 OF: fdt: not found /soc。我反编译了内核自带的 .dtb,发现 soc 节点被意外删除了。嗯,原来是 bootloader 在加载设备树时做了修改。反编译让我快速定位了问题。
3.3.3 .dtsi 的单独编译
.dtsi 文件不能单独编译成 .dtb,因为它缺少根节点 /。但你可以把它当作普通文本文件来检查语法:
dtc -I dts -O dtb -o /dev/null -Wno-error myinclude.dtsi 2>&1
这个命令会把 .dtsi 编译到 /dev/null,只输出错误和警告。如果语法有问题,DTC 会直接报错。
3.4 反编译实战:从 .dtb 还原完整设备树
咱们来一个完整的实战案例。假设你有一块开发板,内核启动时加载了 imx6ull.dtb。你想看看这个设备树里到底定义了哪些外设。
第一步:从内核或文件系统获取 .dtb
# 从 /sys/firmware/devicetree 导出(运行时)
dtc -I fs -O dts -o runtime.dts /sys/firmware/devicetree/base
# 从内核源码目录获取
cp arch/arm/boot/dts/imx6ull.dtb .
第二步:反编译
dtc -I dtb -O dts -o imx6ull.dts imx6ull.dtb
第三步:查看反编译结果
打开 imx6ull.dts,你会看到类似这样的结构:
/dts-v1/;
/ {
#address-cells = <1>;
#size-cells = <1>;
model = "Freescale i.MX6 ULL";
compatible = "fsl,imx6ull";
aliases {
gpio0 = &gpio1;
gpio1 = &gpio2;
// ...
};
soc {
#address-cells = <1>;
#size-cells = <1>;
ranges;
gpio1: gpio@2090000 {
compatible = "fsl,imx6ull-gpio", "fsl,imx35-gpio";
reg = <0x0209c000 0x4000>;
interrupts = <0 66 4>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
};
// ... 其他外设节点
};
};
.dts 文件,#include 和宏定义会被展开成具体的值。所以它比原始源文件更冗长,但信息更完整。
3.5 设备树文件格式对比
三种文件格式各有用途,我整理了一个对比表:
| 格式 | 扩展名 | 可读性 | 用途 | 是否可编译 |
|---|---|---|---|---|
| 源文件 | .dts |
高 | 开发、修改、维护 | 是 |
| 头文件 | .dtsi |
高 | 公共定义、SoC 描述 | 否(需被 .dts 包含) |
| 二进制 | .dtb |
低 | 内核加载、bootloader 传递 | 否(可反编译) |
3.6 DTC 的常见错误与避坑
我在项目中遇到过不少 DTC 相关的坑,这里分享几个典型的:
- 错误:
FATAL: Input has errors—— 语法错误。检查括号、分号、引号是否匹配。我建议用dtc -I dts -O dtb -o /dev/null your.dts先做语法检查。 - 错误:
WARNING: (unit_address_vs_reg) ...—— 节点名和reg属性不匹配。比如节点名是uart@1000,但reg = <0x2000 0x100>。DTC 会警告,但不会阻止编译。 - 错误:
ERROR: (phandle_references) ...—— phandle 引用错误。比如你引用了&gpio1,但gpio1节点不存在或没有定义phandle。 - 错误:
ERROR: (duplicate_node_names) ...—— 节点名重复。同一个层级下不能有两个同名节点。
Segmentation fault。排查了半天,发现是 .dtsi 文件里有一个 #include 形成了循环引用。DTC 不会检测循环包含,直接递归到栈溢出。所以,写 #include 时一定要小心,别搞成死循环。
3.7 本章知识体系
下面这张图,帮你把 DTC 的核心知识点串起来:
这张图把 DTC 的输入、输出、常用参数和核心流程都串起来了。你写设备树时,脑子里要有这张图——知道自己在哪个环节,该用什么参数。
3.8 小结
DTC 是设备树开发的基础工具。安装很简单,但用好它需要理解每个参数的含义。我个人建议你多动手,找几个现成的 .dts 和 .dtb 文件,反复编译、反编译几次。嗯,熟能生巧。