第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
我的建议: 尽量使用 1.5.0 以上版本。1.4.x 版本对 #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
注意: 交叉编译的 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) ... —— 节点名重复。同一个层级下不能有两个同名节点。
避坑指南: 我曾经在编译一个大型设备树时,DTC 报 Segmentation fault。排查了半天,发现是 .dtsi 文件里有一个 #include 形成了循环引用。DTC 不会检测循环包含,直接递归到栈溢出。所以,写 #include 时一定要小心,别搞成死循环。

3.7 本章知识体系

下面这张图,帮你把 DTC 的核心知识点串起来:

DTC 知识体系图 DTC 知识体系 DTC 编译器 输入格式 (-I) 输出格式 (-O) 常用参数 dts(源文件) dtb(二进制) fs(文件系统) dts(反编译) dtb(编译) asm(汇编) -@(overlay) -H(phandle) -V(版本) -S / -p 核心流程:.dts/.dtsi → DTC 编译 → .dtb → 内核加载 调试流程:.dtb → DTC 反编译 → .dts → 分析/修改

这张图把 DTC 的输入、输出、常用参数和核心流程都串起来了。你写设备树时,脑子里要有这张图——知道自己在哪个环节,该用什么参数。

3.8 小结

DTC 是设备树开发的基础工具。安装很简单,但用好它需要理解每个参数的含义。我个人建议你多动手,找几个现成的 .dts 和 .dtb 文件,反复编译、反编译几次。嗯,熟能生巧。