3、设备树编译器:DTC工具的使用与反编译

设备树写好了,怎么变成内核能认的东西?

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

我最早接触 DTC 的时候,觉得这玩意儿不就是个编译器嘛,有啥好学的?后来踩了几个坑才发现,它的参数、选项、甚至版本差异,都能让你折腾半天。今天咱们就把它彻底搞明白。

3.1 DTC 是什么?

DTC 是内核源码里自带的工具。路径在 scripts/dtc/ 下面。你编译内核的时候,它会被自动编译出来。

它的工作流程很简单:

  • 输入:.dts 文件(文本)
  • 输出:.dtb 文件(二进制)
  • 或者反过来:.dtb → .dts

嗯,这里要注意:DTC 还支持 .dtsi 文件,那是设备树的头文件,用 #include 包含进来。我见过有人把 .dtsi 直接当 .dts 编译,结果报错半天找不到原因。

核心命令格式:

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

参数说明:

  • -I:输入格式(dts/dtb/fs)
  • -O:输出格式(dts/dtb)
  • -o:输出文件名

3.2 编译:从 .dts 到 .dtb

假设你写了一个 myboard.dts,想编译成内核能加载的二进制文件。命令如下:

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

如果一切顺利,你会得到一个 .dtb 文件。但现实往往没那么美好。我遇到过最常见的错误是:

  • 语法错误:少了个分号,或者花括号不匹配
  • 引用错误&phandle 引用的节点不存在
  • 属性值类型错误:比如把 reg 写成了字符串

我的调试习惯:

先用 -V 参数指定版本。比如 -V 17 是较新的格式。有些旧版 bootloader 只认老版本,这时候就得用 -V 16。我曾经在某个项目里,因为版本不匹配,uboot 死活加载不了设备树,折腾了两天才发现是版本问题。

3.3 反编译:从 .dtb 到 .dts

反编译是调试利器。你拿到一个 .dtb 文件,想看看里面到底定义了啥?直接反编译:

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

反编译出来的 .dts 文件,和原始 .dts 可能不完全一样。比如:

  • 注释会被丢掉
  • 节点顺序可能变化
  • 一些宏定义会被展开

但这不影响你理解它的结构。我经常用反编译来验证:我写的 .dts 编译后,是不是我想要的样子。

注意:

反编译出来的 .dts 不能直接拿来再编译回去。因为丢失了注释和格式信息。如果你需要修改,建议还是改原始 .dts 文件。

3.4 常用选项与技巧

DTC 的选项不少,但常用的就这几个:

选项 说明 我的用法
-@ 生成插件支持信息 做设备树插件时必加
-H 指定 phandle 格式 默认就行,别乱改
-W 启用警告 我习惯加 -Wno-unit_address_vs_reg 屏蔽一些无关警告
-E 启用错误检查 调试时必开

举个例子,我常用的调试命令:

dtc -I dts -O dtb -o test.dtb test.dts -Wno-unit_address_vs_reg -@

为什么加 -Wno-unit_address_vs_reg?因为很多老设备树里,reg 属性和 unit-address 不匹配,内核其实能处理,但 DTC 会报 warning。看着烦,干脆屏蔽掉。

3.5 反编译实战:分析一个 .dtb

假设你拿到一个 kernel.dtb,想看看里面定义了哪些外设。反编译后,你会看到类似这样的结构:

/dts-v1/;

/ {
    model = "MyBoard";
    compatible = "vendor,myboard";

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

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

    uart@10000000 {
        compatible = "ns16550";
        reg = <0x10000000 0x1000>;
        interrupts = <0 33 4>;
    };
};

看到没?chosen 节点里定义了启动参数,memory 节点定义了内存范围,uart 节点定义了串口信息。这就是内核启动时,从设备树里读到的硬件信息。

我有个习惯:拿到一块新板子,第一件事就是反编译它的 .dtb,看看里面到底配了啥。这比看原理图快多了。

3.6 常见问题与避坑

  • 编译出来的 .dtb 比预期大? 检查是不是包含了不必要的 #include。我曾经因为包含了一个巨大的 .dtsi,导致 .dtb 体积翻倍。
  • 反编译后节点顺序乱了? 正常现象。DTC 内部会重新排序。不影响功能。
  • 编译报错说 phandle 重复? 检查是不是两个节点用了相同的 label。每个 label 必须唯一。
  • uboot 加载 .dtb 失败? 试试 -V 16 编译。有些老版本 uboot 不兼容新格式。

我的工作流:

写 .dts → 编译 → 反编译验证 → 确认无误后烧录。反编译这一步,我几乎每次都做。它能帮你发现很多隐藏问题,比如属性值写错了、节点路径不对等等。

3.7 本章小结

DTC 工具,说白了就是设备树的翻译官。把人类可读的文本,翻译成机器可读的二进制。反过来也能把二进制翻译回文本,方便我们调试。

记住几个关键点:

  • 编译用 -I dts -O dtb
  • 反编译用 -I dtb -O dts
  • 调试时加 -W-E 选项
  • 注意版本兼容性

下一章,咱们会深入设备树的语法细节。到时候你会发现,DTC 只是第一步,真正的大头还在后面。


公众号:蓝海资料掘金营,微信deep3321