第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

小技巧: 如果你只是想快速查看.dtb里的内容,可以用 dtc -I dtb -O dts /proc/device-tree 来查看当前系统正在使用的设备树。这在调试时特别有用。

2.4 二进制文件分析:.dtb的结构

.dtb文件不是随便的二进制数据,它有严格的格式。我当年第一次用十六进制编辑器打开.dtb时,完全看不懂。后来研究了一下,发现它其实挺有规律的。

.dtb文件主要由四部分组成:

  1. 头部(Header):固定长度,包含魔数、大小、版本等信息
  2. 内存预留块(Memory Reservation Block):记录哪些内存区域不能被内核使用
  3. 结构块(Structure Block):节点和属性的实际数据,以token分隔
  4. 字符串块(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:设备树格式版本,目前主流是17
  • off_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这些核心概念。到时候我会分享一些我在项目中踩过的坑,保证让你少走弯路。