泰晓科技 -- 聚焦 Linux - 追本溯源,见微知著!
网站地址:https://tinylab.org

泰晓Linux系统盘,不用安装,即插即跑
请稍侯

扁平化设备树(DTB)格式剖析之三:扁平化设备树示例

iOSDevLog 创作于 2022/10/25

Corrector: TinyCorrect v0.1-rc3 - [tables epw] Author: iOSDevLog iosdevlog@iosdevlog.com Date: 2022/08/22 Revisor: Falcon falcon@tinylab.org Project: RISC-V Linux 内核剖析 Sponsor: PLCT Lab, ISCAS

前几篇文章介绍了扁平化设备树(DTB)标准,这一篇我们将以一个真实的 DTB 文件的部分内容为例,使用图文结合的方式,详细介绍 DTB 格式展开后生成的设备树结构。

DTB 文件准备

我们以 Linux v5.19 RISC-V 默认生成的 arch/riscv/boot/dts/sifive/hifive-unmatched-a00.dtb 举例。

不过这个 dtb 文件有点大,我们先用 fdtdump 反编译成 dts 文件,然后删除大部分的内容,只保留部分有代表性的子节点和部分属性。

最后留下以下内容。

/dts-v1/;
// magic:               0xd00dfeed
// totalsize:           0x20e (526)
// off_dt_struct:       0x38
// off_dt_strings:      0x1c4
// off_mem_rsvmap:      0x28
// version:             17
// last_comp_version:   16
// boot_cpuid_phys:     0x0
// size_dt_strings:     0x4a
// size_dt_struct:      0x18c

/ {
    #address-cells = <0x00000002>;
    #size-cells = <0x00000002>;
    compatible = "sifive,hifive-unmatched-a00", "sifive,fu740-c000", "sifive,fu740";
    model = "SiFive HiFive Unmatched A00";
    chosen {
        stdout-path = "serial0";
    };
    soc {
        #address-cells = <0x00000002>;
        #size-cells = <0x00000002>;
        compatible = "simple-bus";
        ranges;
        serial@10011000 {
            compatible = "sifive,fu740-c000-uart", "sifive,uart0";
            reg = <0x00000000 0x10011000 0x00000000 0x00001000>;
            status = "okay";
        };
    };
};

精简后的 DTB 文件,足够我们分析它主要的结构。

工具介绍

准备好示例文件后,我们使用一个专门分析二进制文件的工具 ImHex 来高亮显示 DTB 文件的结构。

ImHex 是一款功能强大的十六进制编辑器,该工具专为逆向工程分析师、编程开发人员以及那些想好好保护自己眼睛的安全人员所设计。

哪怕你每天工作到凌晨三点(虽然不建议),也不会伤害你的眼睛!

ImHex

功能介绍

  1. 功能丰富的十六进制数据界面:字节修复、修复管理、字节拷贝(字节、十六进制字符串、C、C++、C#、Rust、Python、Java 和 JavaScript 数组、HTML 自包含 div 等)。
  2. 字符串、十六进制搜索。
  3. 自定义 C++ 类模式语言,支持对文件内容进行解析和高亮显示。
  4. 数据导入:支持 Base64 文件、IPS 和 IPS32。
  5. 数据导出:IPS 和 IPS32。
  6. 数据检查器允许解释多种不同类型的数据(小端和大端)。
  7. 大文件支持和快速有效的加载。
  8. 文件哈希支持:CRC16、CRC32、MD4、MD5、SHA-1、SHA-224、SHA-256、SHA-384 和 SHA-512。
  9. 反汇编程序支持多种不同的体系结构:ARM32 (ARM, Thumb, Cortex-M, AArch32)、ARM64、MIPS (MIPS32, MIPS64, MIPS32R6, Micro)、x86 (16-bit, 32-bit, 64-bit)、PowerPC (32-bit, 64-bit)、SPARC、IBM SystemZ、xCORE、M68K、TMS320C64X、M680X 和 Ethereum。
  10. 支持书签、区域突出显示和注释。
  11. 数据分析:提供文件解析器和 MIME 类型数据库、字节分布图、熵图、最高平均熵、加密/压缩文件检测。
  12. 其他实用工具:ASCII 表、正则表达式替换、数学表达式计算器、十六进制颜色选择器。
  13. 在深夜使用时不会“烧坏”你的视网膜。

模式语言

ImHex 所使用的开发基于自定义类 C++ 模式语言,易于阅读、理解和学习。

感兴趣的同学可以在 ImHex 中点击 Help -> Pattern Language Cheat Sheet 来了解更多。

DTB 文件分析

根据前两篇文章的介绍,我们知道 DTB 主要由以下 4 块组成:

  • 报头
  • 内存保留块
  • 结构体块
  • 字符串块

Devicetree .dtb Structure

我们先将 4 个不同的块用 ImHex 高亮显示出来。

dtb_block

通过上图,我们很容易看出 DTB 文件中 4 种颜色代表 4 种不同的块,接下来我们按照 4 个块的顺序,依次分析每块的结构。

报头

我们首先分析一下 DTB 报头,将 scripts/dtc/libfdt/fdt.h 中报头相关代码贴出来。

struct fdt_header {
	fdt32_t magic;                   /* magic word FDT_MAGIC */
	fdt32_t totalsize;               /* total size of DT block */
	fdt32_t off_dt_struct;           /* offset to structure */
	fdt32_t off_dt_strings;          /* offset to strings */
	fdt32_t off_mem_rsvmap;          /* offset to memory reserve map */
	fdt32_t version;                 /* format version */
	fdt32_t last_comp_version;       /* last compatible version */

	/* version 2 fields below */
	fdt32_t boot_cpuid_phys;         /* Which physical CPU id we're booting on */
	/* version 3 fields below */
	fdt32_t size_dt_strings;         /* size of the strings block */

	/* version 17 fields below */
	fdt32_t size_dt_struct;          /* size of the structure block */
};

#define FDT_MAGIC       0xd00dfeed      /* 4: version, 4: total size */
#define FDT_TAGSIZE     sizeof(fdt32_t)

我们整理一个表格将报头的字段和 DTB 中对应的值列出来。

字段说明
magic0xd00dfeed值必须为 0xd00dfeed(大端)
totalsize0x20e (526)设备树数据结构体的总大小
off_dt_struct0x38结构体块相对报头开始的的偏移量
off_dt_strings0x1c4字符串块相对报头开始的的偏移量
off_mem_rsvmap0x28内存保留块相对报头开始的内存保留块的字节偏移量
version17版本
last_comp_version16向下兼容的设备树数据结构的最低版本
boot_cpuid_phys0x0系统引导 CPU 的物理 ID
size_dt_strings0x4a字符串块的字节长度
size_dt_struct0x18c结构体块的字节长度

其中,在 DTB 的报头中定义了后面 3 块的偏移量。

结构体块和字符串块的长度是不定的,在报头中不仅要定义偏移量,还需要定义块的长度。

内存保留块

内存保留块由一组 64 位大端整数对的列表组成,每对用以下 C 结构体表示。

struct fdt_reserve_entry {
	uint64_t address;
	uint64_t size;
};

每对都给出了保留内存区域的物理地址和大小(以字节为单位),这些给定区域不应相互覆盖。

保留块列表应以地址和大小均等于 0 的输入(结构体)结束。

这说明我们可以定义个多内存保留块,只不过我们这里一块都没有定义。

注意地址和大小值始终为 64 位。

在 32 位 CPU 上,值的高 32 位被忽略。

内存预留块中的每个 uint64_t 以及整个内存预留块都应位于距设备树 blob 开头的 8 字节对齐偏移处对齐。

结构体块

这应该是 dtb 文件里面最重要的内容了。

我们首先把结构体块相关代码贴出来。

struct fdt_node_header {
        fdt32_t tag;
        char name[];
};

struct fdt_property {
        fdt32_t tag;
        fdt32_t len;
        fdt32_t nameoff;
        char data[];
};

#define FDT_BEGIN_NODE  0x1             /* Start node: full name */
#define FDT_END_NODE    0x2             /* End node */
#define FDT_PROP        0x3             /* Property: name off, size, content */
#define FDT_NOP         0x4             /* nop */
#define FDT_END         0x9

为了便于分析结构体块,我们直接把 ImHex 生成的高亮图先放出来。

dtb_details

ImHex 有非常多专业的功能,我们这里使用的是:自定义 C++ 类模式语言,支持对文件内容进行解析和高亮显示

通过上图可看出,在 ImHex 右上角的 Pattern editor 区域,我们可以定义 C/C++ 语言类似的模式语言。

这里简单的介绍一下 Pattern editor 的使用方法:

  1. 先声明使用大端模式,在算偏移量的时候会用到
  2. fdt32_t 的类型当成 u32
  3. 定义结构体:直接将 struct fdt_header 粘贴进来
  4. 报头结构体变量及地址:fdt_header header @ 0x00; 再加上这一句就可以把报头高亮显示出来了
  5. 还可以通过左下文的 Pattern Data 区域看到结构化的高亮数据

根据前两篇的文章,我们先把结构体块的存储分布总结一下:

  • 节点
    • FDT_BEGIN_NODE 开头
    • 接着是节点名
    • FDT_END_NODE 结束
    • 可以包含子节点
    • 可以包含属性
  • 属性
    • FDT_PROP 开头
    • 接着指定属性值的长度
    • 接着是属性名在字符串块的偏移量
    • 接着是属性值,其长度在前面已指定
  • 结尾
    • 结构体块最后以 FDT_END 结束

这样得到的设备树是扁平的,我们后面会将扁平设备树展开成一颗真正的树。

字符串块

字符串块包含表示树中使用的所有属性名称的字符串。

这些空终止字符串在本块中简单地连接在一起,并从结构体块中通过偏移量引用到字符串块中。

字符串块没有对齐约束,可以出现在距设备树 blob 开头的任何偏移处。

总结

通过 ImHex 辅助的高亮显示,我们很容易的看到扁平设备树的结构。

我们再将扁平的树展开,就得到下图所示的树状结构。

DTB

通过上图,我们能更清楚的看到设备树 blob 的结构:

  1. DTB 文件分为报头,内存保留块,结构体块和字符串块
  2. 报头
    • 一共有 10 个字段,每个字段 4 个字节
    • 占 40 个字节
    • 第 1 个魔幻字段内容固定为 0xd00dfeed
    • 有 1 个 totalsize 字段定义了整个文件的长度
    • 有 3 个字段定义了其他 3 个块的偏移量
    • 有 2 个字段定义了结构体块和字符串块的大小
  3. 内存保留块
    • 可以定义多个内存定义块
    • 以地址和大小均等于 0 的输入(结构体)结束
  4. 结构体块
    • 节点可以有子节点
    • 节点可以有属性
    • 节点和属性令牌后紧跟着名字在字符串块的偏移
    • 最后以 FDT_END 结束结构体块
  5. 字符串块
    • 属性的名称可能会重复
    • 属性中指定在字符串块的偏移就可能在字体串块找到属性的名称

本文以一个真实 DTB 文件的部分内容为例,使用图文结合的方式,详细介绍 DTB 格式展开后生成的设备树结构。

接下来,我们将深入设备树的源码,具体分析以下 3 个问题:

  1. DTB 文件如何生成 device_node
  2. device_node 树如何生成平台设备 platform_device
  3. DTB 中的什么节点才能生成平台设备 platform_device

参考资料



Read Album:

Read Related:

Read Latest: