当前位置: 首页 > news >正文

Linux设备树dtb文件头fdt_header详解:用C代码和二进制视图教你手动解析

Linux设备树dtb文件头fdt_header详解:用C代码和二进制视图教你手动解析

在嵌入式Linux开发中,设备树(Device Tree)已经成为硬件描述的标准方式。但你是否好奇过,那些.dts源文件编译后生成的.dtb二进制文件,内部究竟是如何组织的?本文将带你深入设备树二进制文件的核心结构,通过C代码和二进制编辑器,逐字节解析fdt_header的神秘面纱。

1. 设备树二进制文件基础认知

设备树二进制文件(.dtb)是设备树源文件(.dts)经过dtc编译器处理后的产物。与可读的文本格式不同,dtb文件采用紧凑的二进制格式存储,以便在系统启动时被快速加载和解析。

关键特点

  • 平台无关的硬件描述格式
  • 采用扁平化(flat)存储结构
  • 包含完整的硬件配置信息
  • 由bootloader加载到内存供内核使用

提示:设备树规范由Devicetree.org维护,最新版本可在其GitHub仓库获取。

典型的dtb文件由以下几部分组成:

  1. 头部(fdt_header):包含魔数、版本、偏移量等元信息
  2. 内存保留区:定义系统中需要保留的内存区域
  3. 结构块:存储设备树的结构信息
  4. 字符串块:包含所有属性名称字符串
  5. 数据块:存储属性值数据

2. fdt_header结构详解

fdt_header是dtb文件的起始部分,它定义了文件的基本属性和各部分的偏移位置。在Linux内核源码中,这个结构体定义在scripts/dtc/libfdt/fdt.h文件中。

struct fdt_header { uint32_t magic; /* 魔数 0xd00dfeed */ uint32_t totalsize; /* 整个dtb文件的大小 */ uint32_t off_dt_struct; /* 结构块的偏移量 */ uint32_t off_dt_strings; /* 字符串块的偏移量 */ uint32_t off_mem_rsvmap; /* 内存保留区的偏移量 */ uint32_t version; /* 设备树版本 */ uint32_t last_comp_version; /* 向后兼容的最低版本 */ uint32_t boot_cpuid_phys; /* 启动CPU的物理ID */ uint32_t size_dt_strings; /* 字符串块的大小 */ uint32_t size_dt_struct; /* 结构块的大小 */ };

字段解析表

字段名偏移量大小描述
magic04字节固定值0xd00dfeed,标识dtb文件
totalsize44字节整个dtb文件的大小(字节)
off_dt_struct84字节结构块相对于文件起始的偏移量
off_dt_strings124字节字符串块相对于文件起始的偏移量
off_mem_rsvmap164字节内存保留区相对于文件起始的偏移量
version204字节设备树数据结构的版本号
last_comp_version244字节向后兼容的最低版本号
boot_cpuid_phys284字节启动CPU的物理ID
size_dt_strings324字节字符串块的大小(字节)
size_dt_struct364字节结构块的大小(字节)

3. 手动解析fdt_header实战

让我们通过实际代码来解析一个dtb文件的头部信息。以下是一个简单的C程序,可以读取并显示dtb文件头部的各个字段:

#include <stdio.h> #include <stdint.h> #include <stdlib.h> #define FDT_MAGIC 0xd00dfeed struct fdt_header { uint32_t magic; uint32_t totalsize; uint32_t off_dt_struct; uint32_t off_dt_strings; uint32_t off_mem_rsvmap; uint32_t version; uint32_t last_comp_version; uint32_t boot_cpuid_phys; uint32_t size_dt_strings; uint32_t size_dt_struct; }; void print_header(struct fdt_header *header) { printf("Magic: 0x%x\n", header->magic); printf("Total Size: %d bytes\n", header->totalsize); printf("Structure Block Offset: 0x%x\n", header->off_dt_struct); printf("Strings Block Offset: 0x%x\n", header->off_dt_strings); printf("Memory Reserve Map Offset: 0x%x\n", header->off_mem_rsvmap); printf("Version: %d\n", header->version); printf("Last Compatible Version: %d\n", header->last_comp_version); printf("Boot CPU Physical ID: %d\n", header->boot_cpuid_phys); printf("Strings Block Size: %d bytes\n", header->size_dt_strings); printf("Structure Block Size: %d bytes\n", header->size_dt_struct); } int main(int argc, char *argv[]) { if (argc != 2) { fprintf(stderr, "Usage: %s <dtb_file>\n", argv[0]); return 1; } FILE *fp = fopen(argv[1], "rb"); if (!fp) { perror("Failed to open file"); return 1; } struct fdt_header header; if (fread(&header, sizeof(header), 1, fp) != 1) { perror("Failed to read header"); fclose(fp); return 1; } if (header.magic != FDT_MAGIC) { fprintf(stderr, "Invalid magic number: 0x%x\n", header.magic); fclose(fp); return 1; } print_header(&header); fclose(fp); return 0; }

编译与运行

gcc -o dtb_parser dtb_parser.c ./dtb_parser example.dtb

4. 二进制视图对照分析

为了更直观地理解fdt_header的结构,我们可以使用二进制编辑器(如xxd、hexdump或010 Editor)查看dtb文件。以下是一个示例dtb文件的头部内容:

00000000: d00d feed 00 00 01 5c 00 00 00 28 00 00 01 34 00 00 .....\.(...4.. 00000010: 00 00 00 00 00 11 00 00 00 10 00 00 00 00 00 00 ................ 00000020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................

逐字节解析

  1. 0x00000000-0x00000003:d0 0d fe ed- 魔数(FDT_MAGIC)
  2. 0x00000004-0x00000007:00 00 01 5c- 总大小(小端序) = 0x15c = 348字节
  3. 0x00000008-0x0000000b:00 00 00 28- 结构块偏移 = 0x28 = 40字节
  4. 0x0000000c-0x0000000f:00 00 01 34- 字符串块偏移 = 0x134 = 308字节
  5. 0x00000010-0x00000013:00 00 00 00- 内存保留区偏移 = 0
  6. 0x00000014-0x00000017:00 00 00 11- 版本 = 17
  7. 0x00000018-0x0000001b:00 00 00 10- 最低兼容版本 = 16
  8. 0x0000001c-0x0000001f:00 00 00 00- 启动CPU物理ID = 0
  9. 0x00000020-0x00000023:00 00 00 00- 字符串块大小 = 0
  10. 0x00000024-0x00000027:00 00 00 00- 结构块大小 = 0

注意:所有多字节字段都采用小端字节序(little-endian)存储,在解析时需要注意字节序转换。

5. 深入理解各字段含义

5.1 魔数(magic)

魔数是识别dtb文件的关键标识,固定值为0xd00dfeed。这个值有两个作用:

  • 验证文件确实是有效的dtb文件
  • 帮助确定文件的字节序(大端或小端)

在代码中检查魔数是最基本的验证步骤:

if (header->magic != FDT_MAGIC) { fprintf(stderr, "Invalid DTB file: bad magic number\n"); return -1; }

5.2 总大小(totalsize)

这个字段表示整个dtb文件的大小,包括头部、内存保留区、结构块、字符串块和数据块。这个值对于内存分配和完整性检查非常重要。

实际应用场景

  • 加载dtb文件时,需要分配足够的内存空间
  • 验证dtb文件是否完整(文件大小应等于totalsize)
  • 计算各部分的边界和大小

5.3 偏移量字段

fdt_header包含三个关键的偏移量字段:

  • off_mem_rsvmap:指向内存保留区
  • off_dt_struct:指向结构块
  • off_dt_strings:指向字符串块

这些偏移量都是从文件起始位置计算的绝对偏移量。通过它们,可以定位到dtb文件的各个部分。

偏移量计算示例

// 读取结构块 fseek(fp, header->off_dt_struct, SEEK_SET); char *structure_block = malloc(header->size_dt_struct); fread(structure_block, 1, header->size_dt_struct, fp);

5.4 版本控制

设备树规范经历了多个版本的演进,fdt_header中包含两个版本相关字段:

  • version:当前dtb文件使用的版本
  • last_comp_version:向后兼容的最低版本

版本兼容性检查

if (header->version < 2 || header->version > 17) { fprintf(stderr, "Unsupported version: %d\n", header->version); return -1; } if (header->last_comp_version > header->version) { fprintf(stderr, "Invalid compatibility version\n"); return -1; }

6. 实际应用与调试技巧

理解fdt_header的结构不仅有助于深入理解设备树的工作原理,还能在实际开发中提供强大的调试手段。

常见应用场景

  • 验证dtb文件完整性:检查魔数、总大小和各部分偏移量是否合理
  • 手动修复损坏的dtb文件:通过修改头部字段恢复损坏的文件
  • 性能优化:分析dtb文件结构,优化内存布局
  • 安全审计:检查dtb文件是否被篡改

调试技巧

  1. 使用fdtdump工具快速查看dtb文件内容:

    fdtdump example.dtb
  2. 比较不同版本的dtb文件:

    hexdump -C v1.dtb > v1.hex hexdump -C v2.dtb > v2.hex diff -u v1.hex v2.hex
  3. 使用libfdt库编程解析:

    #include <libfdt.h> void *dtb = load_dtb_file("example.dtb"); if (fdt_check_header(dtb) != 0) { fprintf(stderr, "Invalid DTB header\n"); return -1; } printf("DTB version: %d\n", fdt_version(dtb));

7. 高级话题:设备树加载过程

了解fdt_header的结构后,我们可以更深入地理解Linux内核如何加载和解析设备树。整个过程大致分为以下几个步骤:

  1. Bootloader阶段

    • Bootloader(如U-Boot)将dtb文件加载到内存
    • 验证dtb文件的基本完整性(魔数、大小等)
    • 将内存地址传递给内核
  2. 内核早期启动

    • 内核验证dtb头部信息
    • 保留内存保留区指定的内存区域
    • 调用unflatten_device_tree()将扁平结构转换为树形结构
  3. 设备树解析

    • 遍历结构块,构建设备节点树
    • 解析属性值,关联字符串块中的字符串
    • 注册设备到内核设备模型

关键内核代码路径

  • drivers/of/fdt.c:设备树扁平格式处理
  • drivers/of/base.c:设备树核心操作
  • include/linux/of_fdt.h:相关头文件

通过本文的深入解析,相信你已经掌握了设备树二进制文件头的关键结构和解析方法。在实际开发中,这些知识将帮助你更好地理解设备树的工作原理,并在遇到问题时能够进行有效的调试和分析。

http://www.cnnetsun.cn/news/2739793.html

相关文章:

  • 告别官方镜像!在Debian 12桌面版上手动搭建Proxmox VE 8.0,保留GUI还能玩转显卡
  • 告别盲猜!用海德汉PWT101/PWM21深度解读Endat信号,排查机床位置报警(保姆级指南)
  • 海德汉PWM21/PWT101选购指南:不同型号怎么选?Endat、1VPP、TTL信号检测全解析
  • 从BA采购申请到FE生产订单:手把手拆解SAP MRP元素如何驱动你的供应链
  • 告别寄存器恐惧:用SX1261/2的‘命令’模式玩转LoRa数据收发(附完整代码片段)
  • AI 电动玩具遥控车智能功率 MOSFET 高性能选型方案
  • 大模型长期记忆机制中长上下文记忆管理面临的工程化挑战与应对方案
  • 5分钟终极指南:使用applera1n免费绕过iPhone激活锁的完整方案
  • QT+Halcon拖拽式视觉流程搭建工具,含完整工程源码与即用模块
  • 命令行版校园步行导航工具:纯Python实现,带地图数据和用户偏好存储
  • 从3D打印到CAD设计:stltostp让你的STL模型实现无缝格式转换
  • Moneta Markets亿汇:“网络安全新盾快速登场”
  • Dreamweaver CS6 AP元素面板全解析:从防止层重叠到Z轴排序,一篇文章搞定
  • TouchDevelop:触控编程如何革新编程教育与学生创造力
  • 从Metaphlan结果到LEfSe差异物种图:一份完整的宏基因组Biomarker挖掘流程
  • 产学研深度融合:信息技术如何成为科学发现的新引擎
  • 微软研究院开放获取政策解析:金色OA模式、CC BY协议与学术传播变革
  • 新能源企业高管进阶优选:香港EMBA项目深度解析
  • 别再只画二维图了!用Python的Matplotlib给你的K-means聚类结果做个酷炫的3D可视化
  • 认识 Node.js——从历史到你的第一个程序
  • PaperPass 查重准吗,2026 年四大主流检测系统横评与避坑指南
  • 2001–2017年USACO完整赛季资源包:测试数据+题面+标程+题解
  • 【企业AI成熟度诊断工具包】:含智能等级自测表、工具匹配矩阵与ROI预估模型
  • 避开这些坑,你的Nature Communications投稿就成功了一半:从格式到图表的保姆级自查清单
  • 2026乡镇同城服务创业攻略:从选址到落地全流程搭建方案
  • STM32在线升级时中断卡死?手把手教你用RAM运行中断函数(F0/F1通用)
  • 遥感新手必看:用Python+ENVI快速识别植被、水体、裸土(附光谱曲线对比图)
  • 别再只重启服务器了!深度解析百度云加速522错误的三种根源与长效优化方案
  • 量子不变量与带链表面的数学基础及应用
  • R5F100LG开发板实操代码包:LCD显示、定时器LED、蜂鸣器发声、ADC与看门狗全功能验证