4、设备树编译与反编译:dtc工具使用、反编译查看硬件拓扑

设备树源文件(.dts / .dtsi)必须经过编译生成二进制设备树 Blob(.dtb),内核启动时才能解析。反之,从 .dtb 反编译回 .dts 是调试硬件拓扑、验证裁剪结果的关键手段。本节围绕 dtc(Device Tree Compiler)工具,系统讲解编译、反编译、查看与校验方法。

核心原则: 编译时保持源文件结构完整,反编译时关注节点层级与属性值,避免因格式差异导致内核解析失败。

4.1 dtc 工具概述

dtc 是设备树的官方编译器,集成在 Linux 内核源码 scripts/dtc/ 目录下,也可通过系统包管理器独立安装。其核心功能包括:

  • 编译(Compile): .dts → .dtb,支持预处理(cpp)和包含文件。
  • 反编译(Decompile): .dtb → .dts,还原可读的硬件描述。
  • 校验(Check): 检查语法、节点唯一性、属性合法性。
  • 输出格式控制: 支持 .dtb、.dts、.dtbo(overlay)等。
命令 功能 典型用法
dtc -I dts -O dtb 编译 .dts 为 .dtb dtc -I dts -O dtb -o output.dtb input.dts
dtc -I dtb -O dts 反编译 .dtb 为 .dts dtc -I dtb -O dts -o output.dts input.dtb
dtc -I dtb -O dts -V 反编译并显示版本信息 dtc -I dtb -O dts -V 17 -o output.dts input.dtb
dtc -I dts -O dtb -@ 编译时保留 overlay 符号 dtc -I dts -O dtb -@ -o output.dtb input.dts
安装提示: 在 Ubuntu/Debian 上执行 sudo apt install device-tree-compiler;在 Yocto/Buildroot 中通常已内置。

4.2 编译流程与参数详解

编译过程本质上是将文本化的设备树源文件解析为二进制结构,包含节点树、属性键值对、字符串表、内存保留映射等。以下是一个标准编译示例:

# 基本编译(无依赖)
dtc -I dts -O dtb -o myboard.dtb myboard.dts

# 带预处理(支持 #include 和宏)
cpp -nostdinc -I include -undef -x assembler-with-cpp myboard.dts myboard_preprocessed.dts
dtc -I dts -O dtb -o myboard.dtb myboard_preprocessed.dts

# 一步到位(推荐)
dtc -I dts -O dtb -o myboard.dtb -@ -W no-unit_address_vs_reg myboard.dts

常用编译选项说明:

  • -I / -O: 指定输入/输出格式(dts / dtb / fs)。
  • -o: 输出文件路径。
  • -@: 生成 __symbols__ 节点,用于设备树 Overlay。
  • -W / -E: 启用/禁用特定警告或错误检查。
  • -V: 指定设备树 Blob 版本(默认 17)。
  • -H: 指定 phandle 格式(legacy / epapr / both)。
常见编译错误: 节点名包含非法字符、reg 属性长度与父节点 #address-cells 不匹配、中断控制器引用缺失。建议编译时始终加上 -W error 将警告视为错误。

4.3 反编译:还原硬件拓扑

反编译是调试和逆向分析的核心技能。通过 dtc -I dtb -O dts 可将二进制 .dtb 还原为可读的 .dts,但反编译结果与原始源文件存在差异:

对比项 原始 .dts 反编译 .dts
注释 保留所有注释 全部丢失
宏/包含 保留 #include 和宏 展开为具体值
标签(label) 保留标签引用 转换为 phandle 数字
节点顺序 按源文件顺序 按节点路径排序
属性格式 保留原始格式 标准化输出

反编译典型场景:

# 基本反编译
dtc -I dtb -O dts -o decompiled.dts board.dtb

# 反编译并显示 phandle 值(便于追踪引用)
dtc -I dtb -O dts -o decompiled.dts -@ board.dtb

# 反编译为“伪源文件”格式(保留更多原始信息)
dtc -I dtb -O dts -o decompiled.dts -H epapr board.dtb
调试技巧: 反编译后使用 diff 对比原始 .dts 和反编译 .dts,可快速发现编译阶段丢失的注释或格式问题。对于裁剪验证,反编译能直观展示哪些节点被保留、哪些被删除。

4.4 设备树编译/反编译流程 SVG 图

下图展示了从源文件到二进制、再到反编译还原的完整数据流,以及各阶段的关键操作与产物:

设备树编译与反编译流程 设备树编译与反编译流程 .dts / .dtsi 设备树源文件 预处理 (cpp) 展开宏 / 包含 编译 (dtc) -I dts -O dtb .dtb 二进制设备树 反编译 (dtc) -I dtb -O dts 反编译 .dts 可读拓扑 二进制输入 反编译丢失注释与宏结构

4.5 查看硬件拓扑:从反编译结果分析节点结构

反编译后的 .dts 文件以树形缩进展示所有节点。通过分析节点层级、compatible 属性、reg 地址范围、中断连接关系,可以快速掌握硬件拓扑。以下是一个典型 SoC 反编译片段:

/dts-v1/;

/ {
    model = "TI AM335x BeagleBone Black";
    compatible = "ti,am335x-bone-black", "ti,am335x-bone", "ti,am33xx";
    
    chosen {
        stdout-path = &uart0;
    };
    
    ocp {
        compatible = "simple-bus";
        #address-cells = <1>;
        #size-cells = <1>;
        ranges;
        
        uart0: serial@44e09000 {
            compatible = "ti,am3352-uart", "ns16550a";
            reg = <0x44e09000 0x2000>;
            interrupts = <72>;
            clocks = <&clk_uart0>;
            status = "okay";
        };
        
        i2c0: i2c@44e0b000 {
            compatible = "ti,am3352-i2c", "ti,omap4-i2c";
            reg = <0x44e0b000 0x1000>;
            interrupts = <70>;
            #address-cells = <1>;
            #size-cells = <0>;
            status = "okay";
            
            eeprom@50 {
                compatible = "atmel,24c32";
                reg = <0x50>;
            };
        };
    };
};

拓扑分析要点:

  • 根节点: 包含 model 和 compatible,用于内核匹配板级。
  • 总线节点: ocp 作为简单总线,包含 #address-cells、#size-cells、ranges 定义地址映射。
  • 外设节点: uart0、i2c0 等,通过 reg 描述基地址和大小,interrupts 描述中断号。
  • 子节点: eeprom@50 挂在 I2C 总线下,通过 reg 表示从设备地址。
  • 引用: &uart0 是标签引用,反编译后保留为 phandle 或路径。
裁剪验证方法: 在裁剪前后分别反编译 .dtb,使用 diff -u before.dts after.dts 对比节点差异。重点关注 status = "disabled" 的节点是否被移除,以及 reg 地址范围是否因裁剪而改变。

4.6 设备树节点拓扑关系 SVG 图

下图以树形结构展示反编译后设备树的节点层级与连接关系,突出根节点、总线、外设和子设备之间的拓扑:

设备树节点拓扑关系 设备树节点拓扑关系(反编译视图) / (根节点) model / compatible chosen ocp (simple-bus) memory uart0 i2c0 gpio0 mmc0 eeprom@50 rtc@68 反编译后节点按路径排序,子节点通过 reg 属性描述地址,interrupts 描述中断连接 裁剪时重点关注 status 属性与 reg 地址范围

4.7 编译与反编译的常见问题与避坑

问题 原因 解决方案
编译报错“syntax error” 缺少分号、括号不匹配、属性值格式错误 使用 dtc -I dts -O dtb -o /dev/null input.dts 仅做语法检查
反编译后节点顺序混乱 dtb 按路径排序存储,反编译按字母序输出 使用 dtc -I dtb -O dts -o output.dts -f 强制保留原始顺序(部分版本支持)
反编译后 phandle 丢失 反编译默认不输出 __symbols__ 节点 加 -@ 参数保留符号表
编译后内核无法识别设备 compatible 字符串拼写错误或大小写不匹配 反编译后检查 compatible 值是否与驱动匹配
裁剪后 .dtb 体积未减小 节点被 status = "disabled" 但未物理删除 使用 dtc -I dtb -O dtb -o trimmed.dtb -H epapr 重新打包压缩
重要提醒: 反编译结果不可直接用于重新编译!由于注释丢失、宏展开、标签转 phandle,反编译后的 .dts 通常无法通过编译。反编译仅用于查看和调试,修改硬件拓扑应始终基于原始 .dts 源文件。

4.8 实战:裁剪验证中的编译与反编译流程

在驱动模块化裁剪实践中,编译与反编译是验证裁剪正确性的最后关卡。推荐按以下步骤操作:

  1. 备份原始 .dtb: cp board.dtb board_original.dtb
  2. 反编译原始 .dtb: dtc -I dtb -O dts -o board_original.dts board_original.dtb
  3. 修改源文件进行裁剪: 删除或禁用不需要的节点。
  4. 编译裁剪后的 .dts: dtc -I dts -O dtb -o board_trimmed.dtb board_trimmed.dts
  5. 反编译裁剪后的 .dtb: dtc -I dtb -O dts -o board_trimmed.dts board_trimmed.dtb
  6. 对比差异: diff -u board_original.dts board_trimmed.dts | less
  7. 检查节点完整性: 确保关键外设(如串口、网卡、存储)未被误删。
  8. 部署测试: 将裁剪后的 .dtb 替换到启动分区,重启验证。
效率提升: 编写 Makefile 或 shell 脚本自动化上述流程,每次裁剪后自动执行编译→反编译→diff 对比,快速定位差异。