UEFI从上电到操作系统接管大致启动流程:
本文主要涉及固件代码,不涉及TLS阶段及之后,因为这个阶段以后跟操作系统有关了。注意的是Uefi shell就是在TLS阶段,所以在写uefi应用模块的时候应该知道当前内存环境。
TLS阶段之前的代码,都是提前烧录到芯片中的,想调试前面的代码是有点困难的。我试了一下,IDA+gdb+vmware最早能接手的时候cpu已经进入ia32e模式了,内存也初始化完了,估摸着应该是DXE阶段。
本文基于intel开发的edk2,edk2是一个uefi框架,只是提供了一些代码可以参考,跟实际主板芯片上能用的代码还是有很大差距的,vmware的EFI64.ROM引导文件是基于edk2的,vbox也是。我去下了几个主流主板上新的bios,发现都是国外一个叫ami公司写的,而且并不依赖edk2,或许可能是把里面关于edk2的信息都删了,我也不清楚。
#
#
正文
首先cpu上电一定是实模式,走ResetVector,也就是复位后的第一行代码
UefiCpuPkg\ResetVector\Vtf0\Ia16\ResetVectorVtf0.asm
1 2 3 | nop
nop
jmp EarlyBspInitReal16
|
UefiCpuPkg\ResetVector\Vtf0\Ia16\Init16.asm
1 2 3 4 | EarlyInit16:
EarlyBspInitReal16:
mov di, 'BP'
jmp short Main16
|
UefiCpuPkg\ResetVector\Vtf0\Main.asm
这里有个问题,实模式下使用32位的寄存器。
参考
https://stackoverflow.com/questions/6917503/is-it-possible-to-use-32-bits-registers-instructions-in-real-mode
接下来就是16位实模式转32位,
UefiCpuPkg\ResetVector\Vtf0\Ia16\Real16ToFlat32.asm
主要是根据手册,设置一些控制寄存器的标志位。
这里涉及到寻找BFV,BFV其实就是uefi image的起点。就相当于windows pe文件的dos头。
这里就是BFV,结构可以参考uefi手册3.2.1章节
本来自动解析uefi结构是有几个ida插件的,但是估计更新太慢了,在ida7.6上都用不了。就选择手动解析了。
找到BFV之后就找SEC entry point,把控制权交给SEC
[ebp+0x30]又指向一个叫EFI_FFS_FILE_HEADER的结构,
上图是第一个FFS,他会一直遍历每一个,直到遇到一个类型为EFI_FV_FILETYPE_SECURITY_CORE的节区。
然后加上0x18,每个子节区的头大小都是固定的24字节。后面的字节就是每个类型不同的信息了。像SEC区的话就是一个PE32格式的映像。
然后就这样获得SEC的代码入口点
到这里,正式进入UEFI内核的启动,也就是SEC阶段的开始。
OvmfPkg\Sec\X64\SecEntry.nasm
这样子看不大懂,直接编译一下把宏去掉,就非常清楚了。
所以代码中的FixedPcdGet(x)其实就相当于一个编译期确定的整数。
这部分代码就是选一段临时内存,并且填充一些数字。一部分当作堆,一部分当作栈。网上很多说设置MTRRs来把缓存当临时内存的,我没找到具体代码在哪。现在的bios都是16M大小,容纳810000h地址也是可以的,这部分不太清楚,有会的希望指点一下。
然后控制转到SecCoreStartupWithStack,
这里的注释说了,我们目前是运行在flash芯片里的,内存此时还用不了。
InitializeCpuExceptioInHandlers就是把idt entry里的所有handler赋值为CommonInterruptEntry,定义在UefiCpuPkg\Library\CpuExceptionHandlerLib\Ia32\ExceptionHandlerAsm.nasm。
紧接着调用ProcessLibraryConstructorList。这个函数在很多阶段会调用,只是完成不同的任务。
sec阶段主要是
这部分代码存在于build/x64/OvmfPkg/AutoGen.c目录,build过之后才有,我也不知道咋回事。
然后调用InitializeFloatingPointUnits初始化浮点单元,
然后存储一些信息,进入SEC二阶段,
,上面基本等于SecStartupPhase2(SecCoreData)
然后SEC将控制转到PEI
......
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!