☰
uboot bootm启动流程
2026/10/29 2:08:06 网站建设 项目流程

U-Bootbootm跳转内核:镜像解析、验签全流程(U-Boot + Kernel 5.10 / RK3568)

一、先看全貌:从bootm到内核第一行代码

bootm 0x02080000 (用户输入) │ ▼ do_bootm() → bootm_start() │ ① 解析镜像头(legacy uImage 或 FIT) ▼ bootm_find_os / bootm_load_os │ ② 校验 magic / CRC / RSA 签名 │ ③ 拷贝/解压内核到加载地址 ▼ boot_jump_linux() │ ④ 关 MMU/cache,准备 x0=dtb, x1=0, x2=0, x3=MAGIC ▼ arm64 内核 head.S → stext → start_kernel() (内核时间戳 [0.000000] 从这里开始)

arm64 上内核的入口约定(Linux 5.10Documentation/arm64/booting.rst):

x0 = device tree blob 在内存中的物理地址 x1 = 0 x2 = 0 x3 = 0x2 (MAGIC)

U-Boot 做的所有事情,本质就是“找到内核、验证它、搬到正确地址、按这个约定跳过去”。

二、镜像格式:legacy uImage vs FIT(两种解析路径完全不同)

1. legacy uImage(64 字节头)

mkimage -A arm64 -O linux -T kernel -C gzip \ -a 0x200000 -e 0x200000 -d Image.gz uImage

镜像在内存中的布局:

0x02080000: ┌──────────────────────────┐ │ image_header (64B) │ ← bootm 首先解析这里 │ ih_magic = 0x27051956 │ ("uImage" 魔数) │ ih_hcrc │ 头部 CRC32 │ ih_time │ 打包时间 │ ih_size │ 数据长度 │ ih_load = 0x200000 │ 加载地址 │ ih_ep = 0x200000 │ 入口地址 │ ih_os = 5 (linux) │ │ ih_arch = 2 (arm64) │ │ ih_type = 1 (kernel) │ │ ih_comp = 1 (gzip) │ ← 决定解压方式 │ ih_dcrc │ 数据 CRC32 │ ih_name[32] │ ├──────────────────────────┤ 0x02080400: │ (可能的 ramdisk 头+数据) │ 0x02080800: │ (可能的 dtb) │ │ 内核 gzip 数据 ... │ └──────────────────────────┘

bootm的解析动作(common/image.c→image_get_kernel()):

/* 简化的源码逻辑 */ static image_header_t *image_get_kernel(ulong img_addr, ...) { hdr = (image_header_t *)img_addr; if (!image_check_magic(hdr)) /* ① 检查 ih_magic == 0x27051956 */ return NULL; /* "Bad Magic Number" 就是这里报的 */ if (!image_check_hcrc(hdr)) /* ② 头部 CRC32 */ return NULL; /* "Bad Header Checksum" */ show_image_info(hdr); /* 打印 Image Name/Created/Type/Size */ if (!image_verify_dcrc(...)) /* ③ 数据 CRC32 */ return NULL; /* "Bad Data CRC"(最常见的报错) */ /* legacy 镜像不携带公钥,签名校验需要 CONFIG_FIT 相关, 实际上 legacy 格式只有 CRC,没有防篡改能力 */ return hdr; }

串口上你会看到(有 print 时间意识的 U-Boot):

## Booting kernel from Legacy Image at 02080000 ... Image Name: Linux 5.10.110 Image Type: AArch64 Linux Kernel Image (gzip compressed) Data Size: 12845312 Bytes = 12.3 MiB Load Address: 00200000 Entry Point: 00200000 Verifying Checksum ... OK ← CRC 校验通过

2. FIT(Flattened Image Tree,U-Boot 官方推荐,RK3568 主力格式)

FIT =把内核、dtb、ramdisk、签名打包成一棵设备树结构,一个文件描述一切。

# its 描述文件(相当于"清单+材料表") cat kernel.its
/dts-v1/; / { description = "RK3568 fit image"; #address-cells = <1>; images { kernel-1 { description = "Linux 5.10.110"; data = /incbin/("Image.lz4"); type = "kernel"; arch = "arm64"; os = "linux"; compression = "lz4"; ← 解压方式写进节点 load = <0x00200000>; entry = <0x00200000>; hash-1 { algo = "sha256"; }; ← 哈希节点(签名的基础) }; fdt-1 { description = "rk3568-evb"; data = /incbin/("rk3568-evb.dtb"); type = "flat_dt"; arch = "arm64"; compression = "none"; load = <0x0a100000>; hash-1 { algo = "sha256"; }; }; }; configurations { default = "conf-1"; conf-1 { kernel = "kernel-1"; fdt = "fdt-1"; /* RK3568 开 CONFIG_FIT_SIGNATURE 后这里可以加签名节点 */ signature-1 { algo = "sha256,rsa2048"; key-name-hint = "dev"; ← 指定用哪个公钥验证 sign-images = "kernel", "fdt"; }; }; }; }; # 打包 mkimage -f kernel.its kernel.itb # .itb = FIT 镜像 # 带签名打包(需要私钥) mkimage -f kernel.its -k keys/ -K u-boot.dtb -r kernel.itb

bootm 0x02080000#conf-1解析过程(common/image-fit.c):

/* 简化逻辑 */ int fit_image_load(...) { /* ① FIT 本身也是 DTB 格式,先验证结构 */ fit_check_format(images, ...); /* fdt_check_header: 魔数 0xd00dfeed */ /* ② 定位 configuration,再找到 kernel/fdt 节点 */ fit_conf_get_kernel(...) / fit_image_select(...); /* ③ ★ 验签(CONFIG_FIT_SIGNATURE=y 时)*/ fit_image_verify_sig(...) { /* 对 hash-1 节点:sha256(data) == hash-1.value → 完整性 */ /* 对 signature-1 节点: a. 用 U-Boot dtb 里 control 目录下嵌入的公钥(key-name-hint 匹配) b. 对 sign-images 声明的各镜像哈希做拼接 c. RSA2048 解签名值,比对拼接哈希 → 防篡改 */ } /* ④ 按 compression 解压、按 load 搬运 */ fit_image Decompress / memmove 到 load 地址; }
## Booting kernel from FIT Image at 02080000 Using 'conf-1' configuration Verifying Hash Integrity ... sha256,rsa2048:dev+ OK ← 验签通过! Trying 'kernel-1' kernel subimage Description: Linux 5.10.110 Type: Kernel Image Compression: lz4 compressed Load Address: 0x00200000 Entry Point: 0x00200000 Verifying Hash Integrity ... sha256+ ← 单镜像哈希再验一次 Loading Kernel from FIT Image at 02080000 ... OK Uncompressing ... Lz4: OK, 25122144 bytes → 0x00200000

验签失败的两种典型报错:

Verifying Hash Integrity ... sha256+ error! ← 数据被改(哈希不匹配) Bad hash value for ... hash-1 ← 镜像与 its 不配套 Verifying Hash Integrity ... sha256,rsa2048:dev+ error! Failed to verify required signature 'conf-1' ← 签名不对/没这个公钥

三、加密(encryption)与签名(signature)的区别,及 U-Boot 的现实做法

签名

加密

目的

防篡改、防替换

防窥探(保密)

U-Boot FIT 支持

✅ 原生完善(RSA/ECDSA + sha256)

⚠️ 非主线;FIT 没有“解密后再验证”的通用流程

主流方案

mkimage 签名 + SoC 信任根

SoC 级 Secure Boot 整体加密(eFuse 密钥 + SoC 解密)

为什么 FIT 不做通用解密:解密需要把明文内核暴露在 DDR 里,且 U-Boot 侧要保存对称密钥,意义有限。行业做法是签名防篡改 + 整个启动链信任根防替换。RK3568 的链条:

① SoC BootROM (只读,内嵌 RSA 公钥哈希 → eFuse 烧入) ↓ 验证 idbloader(前 512B 的 idblock + 签名) ② SPL (trust 镜像,包含 bl31/tee/uboot 的 FIT) ↓ 验证下一级 FIT 签名(SPL 里也有验签能力) ③ U-Boot proper ↓ 验证内核 FIT 签名(本文第二节流程) ④ Linux kernel ↓ (可继续) 验证 initramfs / dm-verity rootfs

每一级用上一级验证过的代码里的公钥验证下一级——这就是信任链。RK3568 关键工具链:

# Rockchip 出包流程(简) ./mkimage -n rk3568 -T rksd -d spl.bin idbloader.img # idblock trust_merger ... # 合成 trust.img(TEE/BL31/UBOOT) mkimage -f its -k keys -K u-boot.dtb -r kernel.itb # 内核 FIT 签名 # 查看一个 FIT 镜像内部(不解密,看结构) dumpimage -l kernel.itb fdtget kernel.itb /images/kernel-1/hash-1 algo # 看用了什么哈希算法 fdtdump kernel.itb | head -50

加密落地场景:如果确实要加密内核/根文件系统,常见做法是:

  1. 内核不做加密,靠签名保证完整;
  2. rootfs 用 dm-crypt/dm-verity:密钥存 TEE(OP-TEE,RK3568 trust 镜像里的 optee),Linux 起来后从 TEE 取密钥解密分区;
  3. RK3568 的 SPL/uboot 本体也可以用 Rockchip 的 secure boot 方案做镜像加密(loader 层),密钥在 eFuse。

四、跳进内核后:5.10 内核侧怎么“接住”控制权

/* arch/arm64/kernel/head.S (5.10) */ SYM_CODE_START(primary_entry) bl preserve_boot_args /* 保存 x0~x3(x0=dtb指针)到 boot_args */ bl el2_setup /* 切 EL2/EL1,配置异常级别 */ bl set_cpu_boot_mode_flag adrp x0, __PHYS_OFFSET bl MMUE0_set bl primary_switch /* 开 MMU(页表是编译期自带的 idmap) */ ... SYM_CODE_START_LOCAL(primary_switched) ... bl start_kernel /* 进入 C 世界,一切 [0.000000] 日志的开始 */

U-Boot 侧跳转代码(arch/arm/lib/bootm.c):

static void boot_jump_linux(struct bootm_images *images) { ... arm_setup_efi_boot(...); /* 清 I/D cache、关 MMU(armv8 下不必须)、设置 x0 */ cleanup_before_linux(); kernel_entry(images->ft_addr /* = dtb地址放入 x0 */, 0, 0, 0); /* 跳到 entry point,一去不回 */ }

所以你在串口上看到的衔接是:

Starting kernel ... ← U-Boot 最后一句话(boot_jump_linux 前) [ 0.000000] Booting Linux on physical CPU 0x0000000000 [0x414fd0b1] [ 0.000000] Linux version 5.10.110 ... ← 内核的 printk 时间零点在这里诞生

排查衔接问题:Starting kernel ...之后没输出,常见原因:

  • dtb 传错/地址错(bootm 0x02080000 - 0x0a100000第三个参数是 dtb 地址,legacy 格式必须传,FIT 会从镜像里取)
  • 内核console=参数没在 dtb 的chosen节点里 → 早期没输出(内核起来了但你看不见),此时可开earlycon(dtb 里加stdout-path或 bootargsearlycon=uart8250,mmio32,0xfe660000)
  • 加载地址覆盖了 dtb 或 ramdisk(load地址重叠)

五、完整流程图(收藏版)

bootm <addr>[#conf] │ ├─ 是 FIT 吗?(fdt magic 0xd00dfeed) │ ├─ 是 → fit_check_format → 选 configuration │ │ → 【验签】RSA/ECDSA + hash 节点(CONFIG_FIT_SIGNATURE) │ │ → 失败且 required → 拒绝启动 │ │ → 按 compression 解压 → memmove 到 load 地址 │ └─ 否 → legacy image_header │ → magic 0x27051956 → 头 CRC → 数据 CRC │ → 按 ih_comp 解压 → 搬到 ih_load │ ├─(bootm 三段式)第二段:内核 / 第三段:ramdtree │ ├─ fdt 可覆盖:fdt chosen bootargs 附加参数 │ ├─ memreserve、memory 节点处理(LMB 内存管理) │ └─ fdt addr = 8 字节对齐,rsv 内存更新 │ └─ boot_jump_linux:cleanup cache/MMU x0 = fdt 地址, x1=x2=0, x3=0x2 → 跳 kernel entry (primary_entry) → el2_setup → 开 MMU → primary_switched → start_kernel() → setup_arch() → printk("[0.000000] Booting Linux ...") ← 时间轴重启零点

六、和看门狗话题的连接点

上一轮我们分析看门狗重启时反复看到U-Boot SPL ... → [0.000000] Booting Linux——现在你知道了这个衔接的机制:

  • reset reason由谁打印:RK3568 的 SPL/U-Boot 启动早期读 PMU_GRF 复位原因寄存器,在切进内核时间体系前打出——所以它不依赖 printk 时间戳,且每次必然出现,是判断重启类型的黄金锚点
  • [0.000000]的零点:就是primary_entry → start_kernel的时刻;U-Boot 阶段耗时(SPL+U-Boot 通常 1~3 秒)不计入内核时间轴——分析“重启周期”时要记得加上这段
  • 如果要做安全启动 + 可靠性的组合,可以在 U-Boot 验签失败时主动喂狗复位,让设备回到已知好的状态

七、动手实验清单

# 1. 在设备上看 U-Boot 怎么解析当前内核 # 串口进 U-Boot 命令行,Ctrl+C 中断自动启动 => iminfo 0x02080000 # 查看镜像头信息 => imls # 列出 flash 中的镜像 => fdt addr 0x02080000; fdt print / # 如果是 FIT,直接 dump 树结构 # 2. 在主机上解包分析 dumpimage -l boot.img / kernel.itb # 3. 生成自己的测试签名(学习用) mkdir keys openssl genrsa -F4 -out keys/dev.key 2048 openssl req -batch -new -x509 -key keys/dev.key -out keys/dev.crt mkimage -f kernel.its -k keys -K u-boot.dtb -r kernel_signed.itb # 4. 篡改实验(加深理解) dd if=/dev/urandom of=kernel.itb bs=1 seek=2048 count=16 conv=notrunc # bootm 时应看到: Verifying Hash Integrity ... error!

总结口诀

bootm 三步走:解包、验签、搬运跳转;
legacy 靠 magic 加 CRC,FIT 靠设备树加 RSA;
公钥嵌在 U-Boot 里,required 强制才拒启;
x0 装 dtb 跳进去,start_kernel 从零开始计时。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询