手撕ARM64启动栈(四):QEMU + TF-A(ATF) BL2 详解
系列文章目录手撕ARM64启动栈一QEMU TF-A → OP-TEE → U-Boot → Linux 全链路总览手撕ARM64启动栈二QEMU TF-A(ATF) BL1 执行上下文——GDB 实测复位到 BL2手撕ARM64启动栈三QEMU TF-A(ATF) BL1 核心职责——bl1_main()逐函数拆解与 BL2 加载手撕ARM64启动栈四QEMU TF-A(ATF) BL2 详解文章目录系列文章目录1. 引言2. BL2 运行特权级S-EL13. BL2 汇编入口GDB 实测入口寄存器对比4. BL2 主流程bl2_main()GDB 实测SMC 重入 BL1 异常向量5. image_desc 列表的来源6. entry_point_info 传递给 BL31GDB 实测三节点入口信息链表7. 本篇小结日志与代码的对应关系1. 引言前两篇第二、三篇走完了 BL1 的全部流程——执行上下文复位向量、异常向量表、退出前状态与核心职责bl1_main()逐函数拆解、加载 BL2。本篇进入BL2 阶段BL2 运行在哪个特权级、它的汇编入口、bl2_main()主流程如何加载 BL31/BL32/BL33 这一串镜像以及加载完成后如何把entry_point_info链表交还给 BL1 再跳往 BL31。和前两篇一样本篇同样使用 GDB 断点方式分析 BL2 的关键节点。涉及关键词ATF / TF-A / ARM Trusted Firmware、QEMU virt aarch64、BL2、S-EL1、bl2_entrypoint、bl2_main、bl2_load_images、OP-TEE 镜像头、parse_optee_header、entry_point_info。2. BL2 运行特权级S-EL1本篇中 BL2 运行在Secure EL1ENABLE_RME1时才改到 EL3本篇未启用。第二篇中介绍BL1交给控制权给BL2时 使用GDB 断点抓到 BL1 构造 BL2 的entry_point_info时next_bl_ep-spsr 0x3c5ERET用它决定 BL2 起跑时的 PSTATE。0x3c5的M[4:0]0b00101即EL1hAArch64、EL1、用 EL1 自己的 SP所以 BL1 是以 EL1 状态把控制权ERET给 BL2 的。3. BL2 汇编入口BL2 也有自己的汇编入口bl2/aarch64/bl2_entrypoint.S角色和第二篇拆解的bl1_entrypoint.S相同——都是CPU/上一级固件跳过来的第一条指令负责在跳进 C 函数之前把运行环境搭好。但它比 BL1 入口简单BL1 被硬件复位向量直接拉起要从复位态初始化搭建 EL3 环境而 BL2 是 BL1 用ERET跳过来的运行在 S-EL1只初始化自己EL1运行环境即可。完整代码trusted-firmware-a/bl2/aarch64/bl2_entrypoint.Sfunc bl2_entrypoint /* 保存 BL1 传来的参数后面转发给 bl2_main */ mov x20, x0 mov x21, x1 mov x22, x2 mov x23, x3 /* 设EL1异常向量表 */ adr x0, early_exceptions msr vbar_el1, x0 isb msr daifclr, #DAIF_ABT_BIT /* 开指令 cache、栈指针对齐检查、数据访问对齐检查 */ mov x1, #(SCTLR_I_BIT | SCTLR_A_BIT | SCTLR_SA_BIT) mrs x0, sctlr_el1 orr x0, x0, x1 bic x0, x0, #SCTLR_DSSBS_BIT msr sctlr_el1, x0 isb /* 清理 BL2 自身 RW 段的cache */ adr x0, __RW_START__ adr x1, __RW_END__ sub x1, x1, x0 bl inv_dcache_range /* .bss 清零 */ adrp x0, __BSS_START__ ... bl zeromem /* 分配栈 */ bl plat_set_my_stack #if STACK_PROTECTOR_ENABLED bl update_stack_protector_canary #endif /* 把保存的 x0-x3 还原为 bl2_main 的四个参数跳转 */ mov x0, x20 mov x1, x21 mov x2, x22 mov x3, x23 bl bl2_main no_ret plat_panic_handler endfunc bl2_entrypointGDB 实测入口寄存器对比bl2_entrypoint断点处bl2_main断点处汇编收尾已跑完SP/VBAR/SCTLR 都已就位两处关键寄存器对比寄存器bl2_entrypoint断点处bl2_main断点处说明$pc0xe05b0000xe05b25c与第三篇 GDB 实测的desc-image_info.image_base 0xe05b000对应——BL2 入口地址$VBAR即当前 EL 的异常向量基址0x00xe060800EL1异常向量初始化 –adr x0, early_exceptions; msr vbar_el1, x0$SCTLR当前 EL 的系统控制寄存器0x30d008000x30d0180aI指令 cachebit12/SA栈指针对齐检查bit3/A数据访问对齐检查bit1位被置上对应mov x1,#(SCTLR_I_BIT|SCTLR_A_BIT|SCTLR_SA_BIT)那几行$sp0x00xe064440从空BL1 未传递 S-EL1 栈指针变为plat_set_my_stack分配好的 BL2 栈顶$x1(arg1)0xe0010000xe001000BL1 传给 BL2 的meminfo_t指针BL2 可用安全 RAM 的 信息4. BL2 主流程bl2_main()bl2_main.c核心序列voidbl2_main(u_register_targ0,u_register_targ1,u_register_targ2,u_register_targ3){plat_setup_early_console();bl2_early_platform_setup2(arg0,arg1,arg2,arg3);bl2_plat_arch_setup();auth_mod_init();next_bl_ep_infobl2_load_images();// ← 核心加载 BL31/BL32/BL33/* 通过 SMC 把 next_bl_ep_info 交还 BL1 */smc(BL1_SMC_RUN_IMAGE,(unsignedlong)next_bl_ep_info,0,0,0,0,0,0);}smc(BL1_SMC_RUN_IMAGE, ...)会触发第二篇看到的 BL1smc_handler64bl1/aarch64/bl1_exceptions.SBL2 加载完 BL31/BL32/BL33 后并不直接跳转而是通过Mac call把执行权交还 BL1由 BL1 跳进 BL31。bl2_main()的主序列以及它调用的bl2_load_images()加载循环流程见下图GDB 实测SMC 重入 BL1 异常向量在smc(...)那行bl2_main.c打断点看即将交还给 BL1 的next_bl_ep_infopc 0xe090000(BL31_BASE)、spsr 0x3cd(EL3h)——链表头正是 BL31 的入口信息与 §6 的 handoff 链一致。args.arg0 0xe064450 是 §6 那条入口信息链表的头指针随 SMC 一起交给 BL31.紧接着单步过smcCPU 会陷入 EL3见下图 – 执行到BL1 的smc_handler64且$x0 0x4BL1_SMC_RUN_IMAGE5. image_desc 列表的来源bl2_load_images()具体要加载哪些镜像是通过平台层查询得到的bl2_load_infoplat_get_bl_image_load_info();// 平台层实现qemu_image_load.cbl2_node_infobl2_load_info-head;while(bl2_node_info!NULL){if(attrIMAGE_ATTRIB_PLAT_SETUP){bl2_platform_setup();}// BL2: Doing platform setupif(attrIMAGE_ATTRIB_SKIP_LOADING){INFO(BL2: Skip loading image id %u\n,...);// 对应 id22}else{INFO(BL2: Loading image id %u\n,...);// 对应 id3/4/21/5load_auth_image(bl2_node_info-image_id,bl2_node_info-image_info);}bl2_node_infobl2_node_info-next_load_info;}而这份列表的静态定义在 QEMU 平台代码里plat/qemu/common/qemu_bl2_mem_params_desc.c是一个bl_mem_params_node_t数组关键条目{.image_idBL31_IMAGE_ID,.ep_info.pcBL31_BASE,.ep_info.spsrSPSR_64(MODE_EL3,MODE_SP_ELX,...),.image_info.image_baseBL31_BASE,.image_info.image_max_sizeBL31_LIMIT-BL31_BASE,.next_handoff_image_idBL32_IMAGE_ID,// 加载完 BL31 后下一个是 BL32},{.image_idBL32_IMAGE_ID,// ← id4OP-TEE header.ep_info.pcBL32_BASE,.image_info.image_max_sizeBL32_LIMIT-BL32_BASE,.next_handoff_image_idBL33_IMAGE_ID,},{.image_idBL32_EXTRA1_IMAGE_ID,// ← id21OP-TEE pager.image_info{...,IMAGE_ATTRIB_SKIP_LOADING},// 初始标记跳过.image_info.image_baseBL32_BASE,.next_handoff_image_idINVALID_IMAGE_ID,},{.image_idBL32_EXTRA2_IMAGE_ID,// ← id22OP-TEE pageable.image_info{...,IMAGE_ATTRIB_SKIP_LOADING},// 初始标记跳过.image_info.image_baseQEMU_OPTEE_PAGEABLE_LOAD_BASE,.next_handoff_image_idINVALID_IMAGE_ID,},id ↔ 宏的对应关系在本系列第一篇有介绍。在bl2_load_images()加载循环bl2_image_load_v2.c打断点展开bl2_node_info链表可以直接看到这份静态数组转成的运行时链表——image_id沿next_load_info依次是0x3 → 0x4 → 0x15(21) → 0x16(22) → 0x5链尾next_load_info 0x0其中 BL31id3的image_info.image_base 0xe090000BL31_BASE、BL32id4的image_base 0xe100000BL32_BASE。关键点BL32_EXTRA1/BL32_EXTRA2在这份静态表里默认都标记IMAGE_ATTRIB_SKIP_LOADING日志里能看到 id21 被加载说明这个标记在运行时被动态清除了——下一篇 OP-TEE header 解析会详细介绍这部分。这份数组通过REGISTER_BL_IMAGE_DESCS()宏注册。需要注意的是OP-TEEBL32在这份列表里并非一个镜像而是占了id4/21/22 三项id4 在本篇是一个 28 字节的 OP-TEE 专用镜像头id21(pager)/id22(paged) 才是真正的镜像体。上面 提到的 id21/22 的 skip 标记之所以能在运行时被动态清除、以及 BL32 的最终入口从哪来都由解析 id4 这个头决定——这部分下一篇会详细拆解本篇不再展开。6. entry_point_info 传递给 BL31BL2 把 BL31/BL32/BL33 全部加载到位后如何告诉下一级这些镜像各自从哪进入执行实现是——BL2 一次性把三个镜像的入口信息entry_point_info串成一条链表交给 BL1BL1 再原样转交 BL31。bl2_load_images()结尾就是这段构造 交接bl2_to_next_bl_paramsplat_get_next_bl_params();// 把静态数组构造成一条入口信息链表/* 把链表头指针塞进头节点 ep_info 的 arg0随后传给 BL31 */if(bl2_to_next_bl_params-head-ep_info-args.arg00)bl2_to_next_bl_params-head-ep_info-args.arg0(u_register_t)bl2_to_next_bl_params;plat_flush_next_bl_params();// 刷 cache确保 BL31 能读到returnbl2_to_next_bl_params-head-ep_info;// 返回链表头BL31 的 ep_infoplat_get_next_bl_params()把 §5 那份静态数组串成一条链表每个节点 image_id 该镜像的ep_infoentry_point_info含 pc/spsr/args 指向下一节点的指针节点顺序按数组里的next_handoff_image_id连成 BL31→BL32→BL33。控制权传递路径 入口信息链表结构见下图GDB 实测三节点入口信息链表在bl2_load_images()的return那行此时 已把链表构造完打断点展开bl2_to_next_bl_params就能把 §6 图里画的三节点链表逐字段对上——沿head→next_params_info依次是 BL31→BL32→BL33链尾next_params_info 0x0节点image_idep_info-pcep_info-spsr对应宏head第 1 个0x30xe0900000x3cdEL3hBL31_BASE第 2 个0x40xe1000000x0BL32OP-TEEpager 入口第 3 个0x50x600000000x3c5EL1h/EL2hNS_IMAGE_OFFSETU-Boot7. 本篇小结日志与代码的对应关系日志行 / 关键事实代码位置NOTICE: BL2: v2.14.0(release)...bl2_main.cINFO: BL2: Doing platform setupbl2_image_load_v2.c调用bl2_platform_setup()INFO: BL2: Loading image id 3/4/21/5bl2_image_load_v2.cload_auth_image()前打印INFO: BL2: Skip loading image id 22bl2_image_load_v2.cINFO: OPTEE header info: ...parse_optee_header()解析 id4 头字段级拆解关注下一篇NOTICE: BL1: Booting BL31BL1 收到 BL2 的 SMC 后打印第五篇会从本篇第 5 节点到的 OP-TEE 专用镜像头id4/21/22继续深入用真实tee-header_v2.bin的十六进制 dump 和实测日志逐字段拆解optee_header_t与parse_optee_header()的三种模式。第六篇BL31 详解会讲 BL31 拿到这条entry_point_info链表之后如何完成 GICv3/运行时服务初始化并通过SPDopteed把控制权切换到 S-EL1 的 OP-TEE以及 OP-TEE 执行完毕后如何再切回 BL33U-Boot。最后留个小问题BL1 跑在 EL3BL2 却被降到了 S-EL1。为啥不让 BL2 也在 EL3运行这样还省掉后面那次 SMC 陷回 EL3 的往返呢你怎么看欢迎评论区聊聊

相关新闻

华为MetaERP Oracle EBS PO 接收全流程会计核算深度解析(含 PPV、IPV、不可抵扣进项税、汇兑损益,带数值案例 + 完整分录)前置基础概念铺垫(EBS 标准采购库存模块逻辑)

华为MetaERP Oracle EBS PO 接收全流程会计核算深度解析(含 PPV、IPV、不可抵扣进项税、汇兑损益,带数值案例 + 完整分录)前置基础概念铺垫(EBS 标准采购库存模块逻辑)

Oracle EBS PO 接收全流程会计核算深度解析(含 PPV、IPV、不可抵扣进项税、汇兑损益,带数值案例 完整分录)前置基础概念铺垫(EBS 标准采购库存模块逻辑)1. 核心科目定义(标准企业会计准则,制造…

2026/7/26 8:39:00阅读更多 →
知识蒸馏技术详解:从核心原理到PyTorch实战应用

知识蒸馏技术详解:从核心原理到PyTorch实战应用

在深度学习模型部署和优化的实践中,我们经常会遇到一个核心矛盾:如何让强大的大模型(教师模型)的能力有效地迁移到更轻量、更适合部署的小模型(学生模型)上?知识蒸馏(Knowledge Dist…

2026/7/26 8:39:00阅读更多 →
Unity Shader深度偏移(Offset)原理详解与实战应用指南

Unity Shader深度偏移(Offset)原理详解与实战应用指南

1. 项目概述:为什么深度偏移是Shader开发中的“双刃剑”在写Shader的时候,尤其是处理半透明物体、植被、头发或者UI特效时,你一定遇到过那个让人头疼的“深度冲突”问题。两个面片靠得太近,GPU在光栅化时对它们到摄像机的距离判断…

2026/7/26 8:39:00阅读更多 →
VMware安装Rocky Linux 9.4全攻略

VMware安装Rocky Linux 9.4全攻略

1. 环境准备与镜像获取Rocky Linux 9.4作为RHEL的替代发行版,在服务器和开发环境中越来越受欢迎。在VMware中安装它之前,我们需要做好以下准备工作:首先确认你的VMware版本是否支持Rocky 9.4。我推荐使用VMware Workstation 16 Pro或更新版本…

2026/7/26 12:47:51阅读更多 →
深入解析TMS320F28335核心外设:ePWM、ADC与eCAN在电机控制中的协同实战

深入解析TMS320F28335核心外设:ePWM、ADC与eCAN在电机控制中的协同实战

1. 项目概述与核心价值如果你正在开发一个需要精确控制电机转速、实时采集传感器数据,并通过工业网络进行可靠通信的系统,那么TMS320F28335这颗芯片大概率已经进入了你的视野。作为TI C2000系列中的明星产品,F28335之所以在电机控制、数字电源…

2026/7/26 12:47:51阅读更多 →
从0到日均承接8000+销售对话,AI数字人客服系统搭建全流程,含NLU训练集标注规范与质检SOP

从0到日均承接8000+销售对话,AI数字人客服系统搭建全流程,含NLU训练集标注规范与质检SOP

更多请点击: https://intelliparadigm.com 第一章:AI数字人销售客服系统建设全景图 AI数字人销售客服系统并非单一技术模块的堆砌,而是融合语音识别、自然语言处理、3D建模渲染、实时驱动引擎与业务中台能力的有机整体。其核心目标是在保障服…

2026/7/26 12:47:51阅读更多 →
5分钟搞定网盘直链:无需安装客户端的终极下载方案

5分钟搞定网盘直链:无需安装客户端的终极下载方案

5分钟搞定网盘直链:无需安装客户端的终极下载方案 【免费下载链接】Online-disk-direct-link-download-assistant 一个基于 JavaScript 的网盘文件下载地址获取工具。基于【网盘直链下载助手】修改 ,支持 百度网盘 / 阿里云盘 / 中国移动云盘 / 天翼云盘…

2026/7/26 12:47:51阅读更多 →
Win11系统DOTA2报错dxgi.dll缺失的全面解决方案

Win11系统DOTA2报错dxgi.dll缺失的全面解决方案

1. 问题现象与背景解析最近在Windows 11系统上运行DOTA2时,不少玩家遇到了"缺少dxgi.dll文件"的错误提示。这个看似简单的报错背后,其实涉及到DirectX图形接口、系统组件依赖和游戏运行环境的复杂交互关系。dxgi.dll是DirectX Graphics Infras…

2026/7/26 12:47:51阅读更多 →
MeshLib高级应用:从布尔运算到网格修复的全面解析

MeshLib高级应用:从布尔运算到网格修复的全面解析

MeshLib高级应用:从布尔运算到网格修复的全面解析 【免费下载链接】MeshLib Mesh processing library 项目地址: https://gitcode.com/gh_mirrors/me/MeshLib MeshLib是一款功能强大的网格处理库,为开发者和设计师提供了从布尔运算到网格修复的完…

2026/7/26 12:45:51阅读更多 →
覆盖国产 + 海外 + 开源模型,OpenClaw 2.7.9 Windows/Mac 双端部署详解

覆盖国产 + 海外 + 开源模型,OpenClaw 2.7.9 Windows/Mac 双端部署详解

🔹 工具基础介绍 OpenClaw 是开源生态中一款实用性较强的本地智能工具,凭借本地离线运行、可视化图形操作和任务自动化三大核心特性,赢得了众多用户的青睐。与普通在线对话AI工具不同,它属于能够直接操控本机软硬件的智能数字员工…

2026/7/26 0:01:28阅读更多 →
伺服阀焊完微漏毁整机?精密激光焊接三关锁住高压

伺服阀焊完微漏毁整机?精密激光焊接三关锁住高压

所谓液压伺服阀体的精密激光焊接,是用激光束对阀座壳体(通常为不锈钢或铝合金)进行密封焊接,使阀体在21-35MPa的高压液压油或压缩气体中长期运行而不发生介质泄漏。液压伺服阀是高端液压系统的"大脑"。从航空航天飞行控…

2026/7/26 0:01:28阅读更多 →
D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南

D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南

D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南 【免费下载链接】d2dx D2DX is a complete solution to make Diablo II run well on modern PCs, with high fps and better resolutions. 项目地址: https://gitcode.com/gh_mirrors/d2/d2dx 你是否还在…

2026/7/26 0:01:28阅读更多 →
覆盖国产 + 海外 + 开源模型,OpenClaw 2.7.9 Windows/Mac 双端部署详解

覆盖国产 + 海外 + 开源模型,OpenClaw 2.7.9 Windows/Mac 双端部署详解

🔹 工具基础介绍 OpenClaw 是开源生态中一款实用性较强的本地智能工具,凭借本地离线运行、可视化图形操作和任务自动化三大核心特性,赢得了众多用户的青睐。与普通在线对话AI工具不同,它属于能够直接操控本机软硬件的智能数字员工…

2026/7/26 0:01:28阅读更多 →
伺服阀焊完微漏毁整机?精密激光焊接三关锁住高压

伺服阀焊完微漏毁整机?精密激光焊接三关锁住高压

所谓液压伺服阀体的精密激光焊接,是用激光束对阀座壳体(通常为不锈钢或铝合金)进行密封焊接,使阀体在21-35MPa的高压液压油或压缩气体中长期运行而不发生介质泄漏。液压伺服阀是高端液压系统的"大脑"。从航空航天飞行控…

2026/7/26 0:01:28阅读更多 →
D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南

D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南

D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南 【免费下载链接】d2dx D2DX is a complete solution to make Diablo II run well on modern PCs, with high fps and better resolutions. 项目地址: https://gitcode.com/gh_mirrors/d2/d2dx 你是否还在…

2026/7/26 0:01:28阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/25 23:03:25阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/25 19:03:04阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/25 19:03:04阅读更多 →