3、设备树编译与反编译:dtc工具的使用、dtb与dts的相互转换、反编译调试技巧

设备树源文件(.dts)写完后,不能直接给内核用。你得把它编译成二进制格式——也就是 .dtb 文件。反过来,拿到一个现成的 .dtb,你也得能把它还原成可读的 .dts,才能分析问题。

这个环节的核心工具就是 dtc(Device Tree Compiler)。别看它名字里带个「编译器」,其实它既能编译也能反编译。我这些年调试过的板子,从 ARM 到 RISC-V,几乎都离不开它。

3.1 dtc 工具基础

dtc 是设备树生态的基石。它把人类可读的文本格式转成内核能解析的扁平二进制格式。反过来,它也能把二进制还原成文本。

大部分 Linux 发行版都预装了 dtc。你可以先确认一下:

dtc --version

如果没装,安装也很简单:

# Debian/Ubuntu
sudo apt-get install device-tree-compiler

# Fedora/RHEL
sudo dnf install dtc

我个人习惯用源码编译最新版,尤其是遇到一些老版本 dtc 的 bug 时。从 dtc 官方仓库 拉下来,make 一下就行。

小提示: 内核源码的 scripts/dtc/ 目录下也自带了一份 dtc。如果你在搞内核开发,直接用那套就行,版本匹配度最高。

3.2 dts 与 dtb 的相互转换

这是 dtc 最基础的两个操作。我把它列成一张表,方便你对照:

操作 命令 说明
编译(dts → dtb) dtc -I dts -O dtb -o output.dtb input.dts 将源文件编译成二进制
反编译(dtb → dts) dtc -I dtb -O dts -o output.dts input.dtb 将二进制还原为源文件
编译(带插件) dtc -@ -I dts -O dtb -o output.dtb input.dts 支持插件(overlay)的编译

举个例子。假设你有一个 myboard.dts:

/dts-v1/;

/ {
    compatible = "mycompany,myboard";
    #address-cells = <1>;
    #size-cells = <1>;

    memory@80000000 {
        device_type = "memory";
        reg = <0x80000000 0x10000000>;
    };

    chosen {
        bootargs = "console=ttyS0,115200";
    };
};

编译它:

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

反编译回来看看:

dtc -I dtb -O dts -o myboard_reverse.dts myboard.dtb

打开 myboard_reverse.dts,你会发现内容和原文件几乎一样,只是格式上可能多了一些对齐和注释。这说明 dtc 的反编译质量很高。

注意: 反编译出来的 .dts 会丢失原始文件中的注释。因为注释在编译阶段就被丢弃了。所以,重要信息别只写在注释里。

3.3 反编译调试技巧

反编译不只是「把二进制转回文本」这么简单。在实际调试中,它有几个非常实用的场景。

3.3.1 查看内核实际使用的设备树

板子启动后,内核用的是哪个 .dtb?它和你的源文件一致吗?

从 /sys/firmware/devicetree 里可以直接导出:

dtc -I fs -O dts -o /tmp/current.dts /sys/firmware/devicetree/base

这个命令把内核当前解析的设备树导出为 .dts 文件。我经常用它来对比「我写的」和「内核实际用的」之间有没有差异。有一次我发现某个 GPIO 控制器的中断号对不上,就是靠这招定位的。

3.3.2 反编译时保留更多信息

默认反编译会省略一些细节。如果你需要看到完整的二进制内容,可以用 -s 选项:

dtc -I dtb -O dts -s -o full.dts input.dtb

-s 会保留所有属性,包括那些值为空的属性。这在调试一些奇怪的兼容性问题时很有用。

3.3.3 检查 dtb 文件是否损坏

拿到一个 .dtb,先反编译一下。如果 dtc 报错,说明文件有问题。常见的错误有:

  • FDT_ERR_BADMAGIC:文件头魔数不对,可能根本不是 dtb 文件。
  • FDT_ERR_TRUNCATED:文件被截断了,可能是传输过程中损坏。
  • FDT_ERR_BADVERSION:版本不匹配,老内核可能不认识新格式。

我曾经遇到过一个问题:U-Boot 加载 dtb 后系统卡死。反编译一看,发现 dtb 文件在烧录时被截断了 4 个字节。重新烧录后问题解决。

3.3.4 使用 -O asm 输出汇编

dtc 还能把设备树编译成汇编代码,方便你嵌入到固件中:

dtc -I dts -O asm -o myboard.S myboard.dts

生成的 .S 文件里,每个节点和属性都对应一段汇编数据。这在一些没有文件系统的裸机环境中很有用。

3.4 知识体系图

下面这张图总结了设备树编译与反编译的核心流程和工具链:

设备树编译与反编译流程 设备树编译与反编译流程 .dts 源文件 人类可读的文本格式 编译 dtc 编译器 -I dts -O dtb 生成 .dtb 二进制 内核直接解析 反编译 .dts 反编译结果 用于调试/对比 /sys/firmware/ devicetree/base 导出 dtc -I fs -O dts 从文件系统反编译 关键调试技巧 • 反编译会丢失注释,重要信息不要只写在注释里 • 使用 -s 选项保留空属性,便于排查兼容性问题 • 从 /sys/firmware/devicetree 导出当前内核使用的设备树 • 反编译报错时检查 FDT_ERR_BADMAGIC / TRUNCATED / BADVERSION

3.5 常见问题与避坑

核心要点: 编译和反编译是设备树调试的基础操作。掌握 dtc 的各种选项,能让你在排查问题时事半功倍。

最后分享几个我踩过的坑:

  • 版本不匹配: 老内核(比如 Linux 3.x)可能不认识新版 dtc 生成的 dtb。编译时加上 -b 0 指定版本号可以解决。
  • 大小端问题: dtc 默认使用大端格式。如果你的平台是小端,编译时加 -O dtb -E -l 指定小端输出。
  • 插件编译: 如果你在写设备树插件(overlay),记得加 -@ 选项,否则内核加载时会报错。

嗯,dtc 这个工具看着简单,但用好了能省不少时间。下次你拿到一个陌生的 .dtb,别急着看代码,先反编译出来再说。