2. 构建最小SEC阶段模拟环境

任务目标

本案例使用QEMU模拟器构建一个最小固件,模拟UEFI平台初始化中的SEC(Security)阶段。该固件在QEMU启动后执行以下操作:完成基本的CPU初始化(设置栈指针)、打印一条调试信息,然后跳转到一个模拟的PEI入口函数。通过该案例,你可以理解SEC阶段的核心职责——从复位向量开始,建立临时RAM(通过栈指针模拟)、传递控制权到PEI阶段。

环境依赖

  • QEMU模拟器(支持x86_64架构,如 qemu-system-x86_64)
  • GCC交叉编译器(用于生成32位或64位裸机代码,本案例使用32位以简化)
  • GNU Make(可选,用于构建脚本)
  • Linux环境(或Windows WSL)

实现原理与代码

本案例的核心是一个独立的汇编文件,它从0xFFFFFFF0(复位向量)开始执行。代码首先设置栈指针(模拟建立临时RAM),然后调用一个C函数打印调试信息,最后跳转到PEI入口。整个固件被链接到4GB地址空间的顶端,符合x86处理器的复位行为。

以下是最小可复现的完整代码,包含汇编启动文件和C入口函数:

# file: sec_start.S
# 最小SEC阶段模拟 - 汇编启动部分
.section .text
.code32
.global _start

# 复位向量位于 0xFFFFFFF0,此处通过链接脚本将 _start 放置在该地址
_start:
    cli                     # 关闭中断
    xorl %eax, %eax
    movl %eax, %cr0         # 关闭分页和缓存(最小初始化)
    
    # 设置栈指针(模拟建立临时RAM)
    movl $0x8000, %esp      # 使用低内存区域作为临时栈
    
    # 调用C函数打印调试信息
    call sec_debug_print
    
    # 跳转到PEI入口(模拟)
    call pei_entry
    
# 死循环(防止CPU跑飞)
halt_loop:
    hlt
    jmp halt_loop

# file: sec_main.c
# 最小SEC阶段模拟 - C入口函数
typedef void (*PEI_ENTRY)(void);

void sec_debug_print(void) {
    /* 直接写串口(COM1, 0x3F8)输出调试信息 */
    volatile unsigned short *com1 = (unsigned short *)0x3F8;
    const char *msg = "SEC: CPU initialized, jumping to PEI...\n";
    while (*msg) {
        *com1 = *msg++;   /* 简单串口输出,不检查状态寄存器 */
    }
}

void pei_entry(void) {
    /* 模拟PEI入口点 */
    volatile unsigned short *com1 = (unsigned short *)0x3F8;
    const char *msg = "PEI: Entry point reached.\n";
    while (*msg) {
        *com1 = *msg++;
    }
    /* 此处可扩展真正的PEI初始化 */
}

链接脚本与构建

需要将入口点定位到正确的地址。创建链接脚本 link.ld:

/* file: link.ld */
OUTPUT_FORMAT(elf32-i386)
ENTRY(_start)
SECTIONS
{
    . = 0xFFFFFFF0;  /* 复位向量地址 */
    .text : {
        *(.text)
    }
    . = 0xFFFFFFF0 + SIZEOF(.text);
    .data : {
        *(.data)
    }
    .bss : {
        *(.bss)
    }
}

构建命令如下(假设文件位于同一目录):

gcc -m32 -c sec_main.c -o sec_main.o
as --32 sec_start.S -o sec_start.o
ld -m elf_i386 -T link.ld sec_start.o sec_main.o -o sec_firmware.elf
objcopy -O binary sec_firmware.elf sec_firmware.bin

运行与验证

使用QEMU运行生成的固件镜像。由于固件被链接到4GB地址空间顶端,QEMU需要加载到正确位置:

qemu-system-x86_64 -m 64 -drive file=sec_firmware.bin,format=raw,if=pflash -nographic

验证方法:观察QEMU串口输出。如果实现正确,你将看到以下两行输出(顺序固定):

SEC: CPU initialized, jumping to PEI...
PEI: Entry point reached.

第一行证明SEC阶段完成了CPU初始化(设置栈、关闭中断)并执行了调试打印;第二行证明控制权已成功跳转到PEI入口。若未看到输出,请检查:

  • QEMU是否以 -nographic 模式运行(否则串口输出不会显示在终端)
  • 固件是否被正确加载到0xFFFFFFF0(可通过 objdump -h sec_firmware.elf 确认)
  • 编译时是否使用了 -m32 和 --32 标志(确保生成32位代码)

以上输出是代码逻辑直接推出的确定性结果,非实测数据。该案例完整演示了SEC阶段的最小职责:从复位向量开始、初始化CPU、建立临时栈、打印调试信息并移交控制权到PEI。