3. 设备树编译与反编译:DTC工具使用、dtb与dts互转、反编译调试技巧
设备树源文件(.dts)是文本格式的硬件描述,但内核引导时加载的是二进制设备树块(.dtb)。DTC(Device Tree Compiler)是两者之间转换的核心工具。掌握编译与反编译操作,是调试设备树配置、验证语法正确性、逆向分析现有系统硬件布局的必备技能。
3.1 DTC工具概述与安装
DTC 是设备树编译器的标准实现,通常随内核源码一起发布,也可独立安装。
| 获取方式 | 命令/路径 | 说明 |
|---|---|---|
| 内核源码 | scripts/dtc/dtc |
编译内核后自动生成,版本与内核匹配 |
| 独立安装(Ubuntu) | sudo apt install device-tree-compiler |
系统级安装,方便快捷 |
| 独立安装(Fedora) | sudo dnf install dtc |
Red Hat 系发行版 |
| 从源码编译 | make(dtc 目录下) |
可获取最新特性与修复 |
调试建议: 在开发板上调试时,优先使用内核源码自带的 DTC 版本,避免因版本差异导致编译警告或错误。
3.2 dts 编译为 dtb:基础流程
将文本格式的 .dts 转换为二进制 .dtb,是设备树开发中最频繁的操作。
3.2.1 基本编译命令
# 基本编译
dtc -I dts -O dtb -o output.dtb input.dts
# 常用参数说明
# -I dts : 指定输入格式为 dts
# -O dtb : 指定输出格式为 dtb
# -o : 指定输出文件名
# -@ : 生成 __symbols__ 节点,支持插件覆盖
# -H epapr : 生成 ePAPR 标准 dtb(默认)
3.2.2 编译过程中的常见警告与处理
| 警告信息 | 含义 | 处理方式 |
|---|---|---|
Warning (unit_address_vs_reg) |
节点名中的地址与 reg 属性不匹配 | 检查节点名格式,确保与 reg 首地址一致 |
Warning (simple_bus_reg) |
simple-bus 子节点缺少 reg 或 ranges | 为子节点添加正确的 reg 属性 |
Warning (avoid_unnecessary_addr_size) |
不必要的 #address-cells / #size-cells | 移除无总线子节点的地址单元声明 |
Error (syntax) |
语法错误,如缺少分号、括号不匹配 | 检查行号附近,修正语法 |
生产环境建议: 在 Makefile 或构建脚本中添加
-Wno-unit_address_vs_reg 等选项,屏蔽已知无害的警告,保持编译输出干净。
3.3 dtb 反编译为 dts:逆向分析与调试
反编译是将二进制 .dtb 还原为可读的 .dts 文本,常用于分析现有系统、验证编译结果、提取硬件配置。
3.3.1 基本反编译命令
# 基本反编译
dtc -I dtb -O dts -o output.dts input.dtb
# 保留所有信息(包括 phandle 和 labels)
dtc -I dtb -O dts -o output.dts -@ input.dtb
# 输出到标准输出(快速查看)
dtc -I dtb -O dts input.dtb | less
3.3.2 反编译输出结构解析
反编译后的 .dts 文件会包含原始 .dtsi 展开后的所有内容,以及编译器自动生成的 phandle 和 linux,phandle 属性。
/dts-v1/;
/ {
#address-cells = <2>;
#size-cells = <2>;
soc {
#address-cells = <2>;
#size-cells = <2>;
compatible = "simple-bus";
ranges;
uart0: serial@ff000000 {
compatible = "arm,pl011", "arm,primecell";
reg = <0x0 0xff000000 0x0 0x1000>;
interrupts = <0 99 4>;
clocks = <&uart_clk>;
status = "okay";
};
};
uart_clk: clock@100 {
compatible = "fixed-clock";
#clock-cells = <0>;
clock-frequency = <24000000>;
};
};
注意: 反编译输出的
phandle 值是编译器自动分配的,与原始 .dts 中的 &label 引用方式不同。反编译结果主要用于调试分析,不建议直接修改后重新编译。
3.4 设备树编译与反编译流程
3.5 高级反编译调试技巧
3.5.1 保留符号信息
使用 -@ 参数反编译时,会保留 __symbols__ 节点,便于追踪原始标签引用关系。
dtc -I dtb -O dts -@ -o debug.dts board.dtb
# 输出中会包含类似以下内容
__symbols__ {
uart0 = "/soc/serial@ff000000";
uart_clk = "/clock@100";
};
3.5.2 仅输出特定节点
通过 -s 参数可以只输出节点结构,不输出属性值,快速查看拓扑。
dtc -I dtb -O dts -s -o skeleton.dts board.dtb
3.5.3 比较两个 dtb 的差异
反编译后使用 diff 工具对比,快速定位配置变更。
# 反编译两个版本
dtc -I dtb -O dts -o v1.dts v1.dtb
dtc -I dtb -O dts -o v2.dts v2.dtb
# 对比差异
diff -u v1.dts v2.dts
3.5.4 从运行系统中提取 dtb
在目标板上运行时,可以从 /sys/firmware/devicetree 或 /proc/device-tree 提取当前使用的设备树。
# 方法一:从 /sys 导出
dtc -I fs -O dtb -o extracted.dtb /sys/firmware/devicetree/base
# 方法二:从 /proc 导出(某些内核版本)
cat /proc/device-tree > extracted.dtb
# 然后反编译查看
dtc -I dtb -O dts -o extracted.dts extracted.dtb
实用技巧: 在开发板上执行
dtc -I fs -O dts /sys/firmware/devicetree/base 可以直接输出当前内核使用的完整设备树文本,无需中间 dtb 文件。
3.6 常见编译错误与排查
| 错误类型 | 典型信息 | 排查方向 |
|---|---|---|
| 语法错误 | syntax error before '...' |
检查分号、花括号、引号是否匹配 |
| 引用错误 | Reference to non-existent node or label |
确认 &label 对应的节点已定义 |
| 属性值错误 | Bad cell value |
检查 reg、interrupts 等数组长度 |
| 包含文件错误 | Couldn't open "xxx.dtsi" |
确认 #include 路径正确,使用 -i 指定搜索路径 |
| 版本不兼容 | FATAL: Input has errors |
检查 /dts-v1/; 版本声明,更新 DTC 版本 |
3.7 编译与反编译的调试工作流
3.8 实战案例:反编译分析现有系统
假设你拿到一块开发板,需要分析其 I2C 控制器配置。
# 步骤1:从运行系统提取 dtb
dtc -I fs -O dtb -o current.dtb /sys/firmware/devicetree/base
# 步骤2:反编译为可读文本
dtc -I dtb -O dts -o current.dts current.dtb
# 步骤3:搜索 I2C 相关节点
grep -n "i2c\|I2C" current.dts
# 步骤4:提取特定节点内容
sed -n '/i2c@/,/^[a-z]/p' current.dts | head -50
调试要点: 反编译后的
current.dts 中,所有 phandle 值都是数字形式。要理解引用关系,可以查找 phandle = <数字> 的定义位置,或者使用 -@ 参数保留符号表。
通过本节的学习,你已经掌握了 DTC 工具的核心用法,能够熟练进行 .dts 与 .dtb 之间的双向转换,并利用反编译技术进行设备树调试与逆向分析。这些技能将在后续的 platform 驱动开发中发挥关键作用。