第2章:设备树编译与反编译:dtc工具使用、编译选项、反编译技巧、二进制文件分析
设备树源文件(.dts)写好了,怎么变成内核能认的二进制(.dtb)?反过来,拿到一个现成的.dtb,怎么把它还原成可读的.dts?
这中间的关键工具就是 dtc(Device Tree Compiler)。
说实话,我见过不少工程师,写dts写得飞起,但一遇到dtc报错就懵了。或者拿到一块新板子的dtb,想看看里面到底定义了哪些外设,却不知道怎么下手。今天这一讲,咱们就把dtc这个工具彻底吃透。
2.1 dtc工具:不只是编译
dtc是设备树生态里的瑞士军刀。它能把.dts编译成.dtb,也能把.dtb反编译回.dts,还能把.dts和.dtb相互比较差异。
大多数Linux发行版都预装了dtc。你可以用下面命令检查一下:
dtc --version
如果没装,在Ubuntu/Debian上:
sudo apt-get install device-tree-compiler
在CentOS/RHEL上:
sudo yum install dtc
我个人习惯用 dtc --version 先确认版本。不同版本的dtc,对某些语法(比如#include的支持)会有差异。我遇到过几次因为dtc版本太老,导致编译不过的坑。
2.2 编译选项:从.dts到.dtb
最基本的编译命令长这样:
dtc -I dts -O dtb -o my_board.dtb my_board.dts
参数解释:
-I dts:输入格式是dts(源文件)-O dtb:输出格式是dtb(二进制)-o my_board.dtb:指定输出文件名- 最后一个参数是输入文件
但实际项目中,我很少用这么简单的命令。下面这几个选项才是真正常用的:
| 选项 | 作用 | 我的使用场景 |
|---|---|---|
-@ |
生成符号节点(symbols),支持插件(overlay) | 做设备树插件时必加 |
-H |
指定phandle格式(legacy/epapr) | 兼容旧内核时用 |
-W |
启用特定警告 | 排查语法问题时打开 |
-E |
启用特定错误检查 | 严格模式时使用 |
-v |
显示编译详细信息 | 调试编译过程 |
举个例子,我经常这样编译:
dtc -I dts -O dtb -@ -W no-unit_address_vs_reg -o my_board.dtb my_board.dts
这里 -@ 是为了后续可能做overlay,-W no-unit_address_vs_reg 是忽略某些不太要紧的警告。嗯,这个警告其实挺常见的——当你的节点有reg属性但没有unit address时,dtc会唠叨你。有时候为了可读性,我选择忽略它。
FATAL ERROR: Unable to parse input tree,别慌。这通常是因为你的.dts文件里有语法错误。dtc的错误信息有时候不太友好,我建议先用 dtc -v 看看详细输出,定位到具体行号。
2.3 反编译技巧:从.dtb到.dts
反编译命令和编译很像,只是方向反了:
dtc -I dtb -O dts -o my_board_recovered.dts my_board.dtb
但这里有个坑——反编译出来的.dts,和原始.dts不完全一样。
为什么会这样?因为编译过程中,dtc会做一些优化和转换。比如:
- 所有标签(label)会被展开成具体的路径
- 所有
#include和/include/会被展开 - 所有宏定义会被替换成实际值
- 节点顺序可能会变
我记得有一次,客户给了一个.dtb,说板子上的某个外设不工作。我反编译一看,好家伙,原始dts里明明有status = "okay",但反编译出来的文件里,这个属性被放在了别的位置。虽然逻辑上没变,但可读性差了很多。
所以我的建议是:反编译主要用于调试和逆向分析,不要指望能完美还原原始dts。
dtc -I dtb -O dts /proc/device-tree 来查看当前系统正在使用的设备树。这在调试时特别有用。
2.4 二进制文件分析:.dtb的结构
.dtb文件不是随便的二进制数据,它有严格的格式。我当年第一次用十六进制编辑器打开.dtb时,完全看不懂。后来研究了一下,发现它其实挺有规律的。
.dtb文件主要由四部分组成:
- 头部(Header):固定长度,包含魔数、大小、版本等信息
- 内存预留块(Memory Reservation Block):记录哪些内存区域不能被内核使用
- 结构块(Structure Block):节点和属性的实际数据,以token分隔
- 字符串块(Strings Block):所有属性名的字符串表
用 dtc -I dtb -O dts -H 可以查看.dtb的头部信息:
dtc -I dtb -O dts -H my_board.dtb
输出大概长这样:
// magic: 0xd00dfeed
// totalsize: 0x1a3c (6724)
// off_dt_struct: 0x48
// off_dt_strings: 0x19c0
// off_mem_rsvmap: 0x28
// version: 17
// last_comp_version: 16
// boot_cpuid_phys: 0x0
// size_dt_strings: 0x7c
// size_dt_struct: 0x1978
这里有几个关键字段:
magic:固定为0xd00dfeed,用来识别这是设备树文件totalsize:文件总大小version:设备树格式版本,目前主流是17off_dt_struct:结构块的偏移量off_dt_strings:字符串块的偏移量
我曾经用十六进制编辑器直接修改过.dtb文件。那次是因为某个属性值写错了,但手头没有源码,只能硬改二进制。嗯,这操作风险很大,但确实能应急。如果你也想这么干,记住:修改后一定要用 dtc -I dtb -O dtb 重新校验一下,否则内核可能不认。
2.5 高级用法:比较与校验
dtc还有一个很实用的功能——比较两个设备树:
dtc -I dtb -O dts -S diff.dts old.dtb new.dtb
这个命令会输出两个.dtb的差异。我在做设备树版本管理时经常用,能快速看出改了哪些节点。
另外,dtc -I dtb -O dtb -H 可以校验.dtb文件的完整性。如果文件损坏,dtc会报错。
- 编译用
-I dts -O dtb,反编译用-I dtb -O dts - 加
-@支持overlay,加-W控制警告 - 反编译结果仅供参考,不能完全还原原始dts
- .dtb有固定格式,可以用十六进制工具分析
- 修改二进制后一定要重新校验
好了,这一讲的内容就到这里。下一讲我们会深入设备树的语法细节,包括节点、属性、标签、phandle这些核心概念。到时候我会分享一些我在项目中踩过的坑,保证让你少走弯路。