4. 设备树编译与反编译:DTC工具使用、dtc命令参数、反编译已有dtb文件。
设备树源文件(.dts)写好了,怎么变成二进制文件(.dtb)给内核用?反过来,拿到一个现成的 .dtb,怎么把它还原成可读的源码?
这就是本章要聊的事。说白了,就是 DTC 这个工具的使用。我最早接触 DTC 时,觉得它不就是个编译器嘛,跟 GCC 差不多。后来发现,它的一些参数和坑,真能让你折腾半天。
核心一句话:DTC(Device Tree Compiler)是设备树世界的“翻译官”,负责在人类可读的 .dts 和机器可读的 .dtb 之间来回转换。
4.1 DTC 工具从哪来?
DTC 的源码在 Linux 内核的 scripts/dtc/ 目录下。你编译内核时,它会被自动编译出来。当然,你也可以单独安装:
# Ubuntu / Debian
sudo apt-get install device-tree-compiler
# Fedora / CentOS
sudo dnf install dtc
# 或者从内核源码编译
make scripts/dtc/dtc
我个人习惯直接用系统包管理器安装,省事。但如果你在搞一个很老的内核,或者需要交叉编译给 ARM 板子用,那就得从内核源码里单独编译了。
4.2 基本用法:编译 .dts → .dtb
这是最常用的操作。命令格式很简单:
dtc -I dts -O dtb -o output.dtb input.dts
参数说明:
| 参数 | 含义 | 说明 |
|---|---|---|
-I dts |
输入格式 | dts 表示源文件,dtb 表示二进制文件,fs 表示文件系统节点 |
-O dtb |
输出格式 | 同上,dts/dtb/fs 三种 |
-o |
输出文件名 | 不指定则输出到标准输出 |
-@ |
生成符号节点 | 用于 overlay 场景,后面章节会讲 |
-H |
phandle 格式 | legacy/epapr 等,一般不用管 |
举个例子。我在 Rockchip RK3568 上写过这样一个命令:
dtc -I dts -O dtb -o rk3568-evb.dtb rk3568-evb.dts
编译时如果报错,别慌。大部分错误都是语法问题,比如少了个分号、引号没配对、或者引用了不存在的节点。DTC 的错误信息还算友好,它会告诉你第几行出了什么问题。
小技巧:编译时加上 -Wno-unit_address_vs_reg 可以屏蔽一些关于地址格式的警告。这些警告在严格检查时有用,但日常开发中可以先忽略。
4.3 反编译:从 .dtb 还原 .dts
拿到一块新板子,厂商只给了 .dtb 文件,没有源码?或者你想看看内核实际加载的设备树长什么样?反编译就派上用场了。
dtc -I dtb -O dts -o recovered.dts boot.dtb
就这么简单。但要注意,反编译出来的 .dts 文件,跟原始的 .dts 源文件会有一些差异:
- 注释全没了:DTC 编译时会丢弃所有注释,反编译自然恢复不了。
- 宏定义被展开:源文件里用的
#include和宏,反编译后都变成了具体的值。 - 节点顺序可能变化:DTC 会按地址排序节点,跟源文件顺序不一定一致。
- 标签(label)丢失:源文件里的
&uart0这种引用,反编译后变成了phandle = <0x...>。
我曾经在调试一个 SPI 驱动时,厂商给的 .dtb 里 SPI 节点工作正常,但源码里就是不行。反编译一看,发现厂商在 .dts 里偷偷加了一个 cs-gpios 属性,而源码里没有。嗯,这种“隐藏改动”在商业板卡里并不少见。
避坑指南:反编译得到的 .dts 只能作为参考,不要直接拿它去修改然后重新编译。因为丢失了宏定义和标签,你改起来会非常痛苦,而且容易出错。正确的做法是找到原始的 .dts 源文件来修改。
4.3 进阶参数:-@ 与 overlay
Rockchip 平台经常用到设备树 overlay(叠加层)。比如你想在不修改主 .dtb 的情况下,动态加载一个外设的配置。这时就需要 -@ 参数。
dtc -@ -I dts -O dtb -o base.dtb base.dts
dtc -@ -I dts -O dtb -o overlay.dtbo overlay.dts
-@ 会在编译时生成符号节点(__symbols__),这样 overlay 才能找到主 .dtb 里的节点并修改它们。没有这个参数,overlay 加载时会报“找不到节点”的错误。
我记得第一次用 overlay 时,忘了加 -@,结果内核报了一堆 __symbols__ not found 的错误。折腾了半小时才发现是编译参数的问题。
4.4 查看 dtb 内容:dtc 的另一种用法
有时候你不想生成文件,只想快速看看 .dtb 里有什么。可以用管道:
dtc -I dtb -O dts boot.dtb | less
或者只查看某个节点:
dtc -I dtb -O dts boot.dtb | grep -A 20 "i2c"
更专业的做法是用 fdtdump 工具(也是 DTC 套件的一部分):
fdtdump boot.dtb | less
fdtdump 的输出格式跟 dtc -O dts 略有不同,它更接近二进制结构的原始表示。我个人更喜欢用 dtc 反编译,可读性更好。
4.5 本章知识结构
下面这张图总结了 DTC 工具的核心工作流:
4.6 实战:反编译 Rockchip 的 dtb
假设你有一块 RK3588 的开发板,SD 卡里的 boot.dtb 是厂商编译好的。你想看看它里面关于 i2c2 的配置:
# 反编译整个文件
dtc -I dtb -O dts -o rk3588-evb.dts boot.dtb
# 或者直接过滤
dtc -I dtb -O dts boot.dtb | grep -A 30 "i2c2"
输出大概长这样:
i2c2: i2c@feac0000 {
compatible = "rockchip,rk3588-i2c", "rockchip,rk3568-i2c";
reg = <0x0 0xfeac0000 0x0 0x1000>;
interrupts = <0x0 0x1f9 0x4>;
clocks = <&cru 0x1b9>, <&cru 0x1ba>;
clock-names = "i2c", "pclk";
pinctrl-0 = <&i2c2m0_xfer>;
pinctrl-names = "default";
#address-cells = <0x1>;
#size-cells = <0x0>;
status = "disabled";
};
注意看,这里的 &cru 和 &i2c2m0_xfer 在反编译后变成了 <0x...> 这样的数字引用。这就是我说的“标签丢失”。如果你想修改这个节点,最好找到原始的 .dts 文件,而不是直接改这个反编译结果。
实用技巧:在 Rockchip 平台上,我经常用反编译来对比两个 .dtb 文件的差异。比如,一个能正常启动,一个不行。反编译后用 diff 命令对比,往往能快速定位问题。
4.7 常见错误与解决
| 错误信息 | 原因 | 解决方法 |
|---|---|---|
FATAL ERROR: Unable to parse input tree |
.dts 文件语法错误 | 检查分号、引号、括号是否匹配 |
WARNING: (unit_address_vs_reg) |
节点地址与 reg 属性不匹配 | 加 -Wno-unit_address_vs_reg 忽略 |
Error: phandle 0x... not found |
引用了不存在的节点 | 检查 &xxx 对应的节点是否存在 |
__symbols__ not found |
编译时没加 -@ |
重新用 -@ 参数编译 |
嗯,这些错误我基本都踩过。尤其是 phandle not found,在大型设备树里特别容易出。我的建议是:写 .dts 时,每引用一个节点,就确认一下它确实存在。别等到编译报错再回头找。