第2章 ABL模块入口实现

2.1 入口函数原型

ABL(Android Bootloader)作为一个UEFI应用程序,其入口函数遵循UEFI驱动模型,但名称和约定与标准EDK II框架有所区分。典型ABL入口原型如下:

EFI_STATUS
ABL_ENTRY_POINT (
  IN EFI_HANDLE           ImageHandle,
  IN EFI_SYSTEM_TABLE    *SystemTable
);

习惯上,ABL代码中将该函数命名为 abl_entry 或 BootloaderEntry,具体名称由编译时的固件集成者确定。入口点通过UEFI图像加载器从 .efi 文件的PE头中解析并调用。

集成提示: 在UEFI平台的 .dsc 和 .fdf 文件中,需要将ABL的入口函数符号与固件中的 BaseMemoryLib、DevicePathLib 等库正确链接;否则调用时可能出现符号未定义错误。

2.2 参数含义详解

两个参数 ImageHandle 和 SystemTable 分别承载ABL自身的模块上下文和全局UEFI运行时服务。具体含义如下表:

参数类型含义
ImageHandleEFI_HANDLEABL图像自身的句柄,用于获取加载的设备路径、映像类型、协议接口等。
SystemTableEFI_SYSTEM_TABLE *指向UEFI系统表的指针,包含 BootServices、RuntimeServices、ConfigurationTable 和标准控制台输出输入。

在ABL实现中,入口函数第一件事通常是将 SystemTable 保存到全局变量,以便后续阶段能持续访问UEFI服务。典型的初始化片段:

EFI_STATUS
abl_entry(IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable)
{
  EFI_STATUS Status;

  gImageHandle = ImageHandle;
  gST = SystemTable;
  gBS = SystemTable->BootServices;
  gRT = SystemTable->RuntimeServices;

  // 注册退出启动服务回调
  Status = gBS->RegisterProtocolNotify(&gEfiEventExitBootServicesGuid,
                                        TPL_CALLBACK,
                                        ExitBootServicesCallback);
  ...
}
常见陷阱: 如果ABL入口函数内未主动调用 gBS->SetWatchdogTimer(0, 0, 0, NULL) 禁用UEFI看门狗,长时间加载引导镜像可能导致系统复位。建议在入口点立即关闭看门狗。

2.3 与UEFI Entry Point的区别

标准UEFI应用程序入口(例如EDK II生成的 efi_main)与ABL入口在调用约定、资源初始化阶段、以及生命周期方面存在本质差异。下表逐项对比:

对比项UEFI标准Entry PointABL入口(abl_entry)
调用者UEFI固件的图像加载器(DXE或BDS阶段)固件内嵌的引导管理器(BDS),部分平台通过 LoadImage + StartImage 调用
参数传递纯UEFI规范:ImageHandle + SystemTable完全一致;但ABL通常还会在固件侧预置自定义协议或 RAM磁盘设备路径
调用约定EFIAPI(微软x64调用/ARM AAPCS)在ABL源码中可能未明确标注EFIAPI,由链接脚本保证与固件一致
生命周期返回后若Status为SUCCESS,映像被卸载;否则保留句柄应始终返回 EFI_SUCCESS,因为ABL必须接管启动流程;若返回错误,固件可能回退至shell或菜单
栈与堆由UEFI BDS分配默认栈(通常64 KB)ABL在入口点会重新分配更大的栈(例如256 KB)用于解压和验证
协议访问通过 SystemTable->BootServices 标准接口额外依赖设备路径协议、活动分区协议、存储协议(如 BlockIo、DiskIo)
关键差异: ABL入口在返回前必须调用 EFI_BOOT_SERVICES.ExitBootServices() 以转移控制权给实际内核;而普通UEFI程序通常保持启动服务直至退出。ABL设计为“一次性引导”,不在UEFI环境中常驻。

2.4 入口点之后的初始化流程

ABL入口函数内部典型执行顺序:

  1. 保存SystemTable并禁用看门狗。
  2. 获取平台设备路径,枚举当前存储设备上的分区。
  3. 搜索并解析引导配置文件(boot.img、vendor_boot.img 或 UEFI分区)。
  4. 验证签名并加载内核、DTB、ramdisk到物理内存。
  5. 设置页表、为ExitBootServices做准备。
  6. 调用 gBS->ExitBootServices(ImageHandle, MapKey) 并将控制权交给内核。

下面结构图展示了ABL入口在UEFI阶段中的位置及参数流向:

UEFI阶段与ABL入口参数流向 SEC (安全验证) PEI (内存初始化) DXE (驱动执行) BDS → ABL入口 (abl_entry) 加载内核+ExitBootServices 参数传递 ImageHandle: 模块自身句柄 SystemTable: UEFI系统表指针 (固件通过StartImage传入) ABL vs UEFI Entry • ABL在BDS阶段被链接,非动态加载 • 必须禁用看门狗,重新分配栈 • 返回前必须调用ExitBootServices() • 访问存储协议、分区表、签名验证

上图清晰展示了ABL入口在UEFI阶段的承接位置:经过SEC、PEI、DXE三层逐级初始化后,BDS阶段调用ABL入口,并传入 ImageHandle 和 SystemTable。右侧的说明框总结了与标准UEFI入口的核心区别,便于开发者快速定位差异点。

实现建议: 调试ABL入口时,可利用 SystemTable->ConOut->OutputString() 打印日志,但需注意此时启动服务尚未退出,不能依赖物理地址固定映射。推荐在入口点立即将串口重定向输出到 ConOut 上。