赞
踩
二级boot的实现方案有很多,比如bl2,u-boot、uboot-spl, uefi。
bl2的主要职责就是将后续固件如u-boot。kernel,SCP或者其他异构核的固件,加载到ram中。
对于一个ARMv8架构的Core,比如A35,A53,A7x来说,大部分SoC设计厂商会使用ARM推荐的启动方式:ARMv8 AARCH64,Single Core Boot。
这样,Cold boot是,EL3,ARMv8 Aarch64,加载bl2,可以选择的组合有:
每种bl2启动方式都有自己的特点,我们在讲完整个启动阶段(即启动kernel为止的启动阶段)后在分析。
在前一阶段,主CPU核运行bl1代码。在Bl2上,同样只有主cpu核运行bl2代码(通过platform_is_primary_cpu()函数识别主cpu核)。Bl1将系统的控制权交给bl2(跳转到BL2_BASE)。Bl2调用plat_get_bl_image_load_info()从外部flash获取要加载的image list(通过plat_get_bl_image_load_info() )。加载完成后跳转到下一个image运行( plat_get_next_bl_params())
BL2的运行分为两种情况,回忆在build BL1时,编译选项BL2_AT_EL3指出是否BL2也运行在EL3上,在EL1和EL3上的entrypoint初始化代码会有区别。我们分别分析这两种情况。默认BL2_AT_EL3=0
bl2运行在哪个异常等级(EL),和整个系统的启动流程,boot阶段的设计密不可分,bootrom的功能也会影响到能否支持BL2_AT_EL3。
在低等级的异常上,不具备访问高等级异常的权限,假如设计的armv8 ARCH64的启动流程是:
bootrom ---> bl2 ---> bl31 -->u-boot -->klernel
如果bl2不运行在EL3,那么进入bl31执行的唯一方式是smc,启动到bl2阶段,只有bootrom(BL1)在el3上执行过,且el3的VBAR只在bootrom(BL1),所以需要bootrom的smc handler支持execuate和auth。当然,这些ATF已经帮你做好了,加上自己的平台实现函数,编进去就可以。
如果固化的bootrom没有这个功能,就需要bl2在EL3上运行,以便启动bl31。
一般这种情况下,启动阶段对应的异常等级如下表
启动阶段 | 异常等级 | 安全 | 说明 |
bl1 | EL3 | S | 默认从core支持的最高EL |
bl2 | EL3 | S | 运行在EL3,方便直接跳转到bl31 |
bl31 | EL3 | S | 需要EL3,需要支持PSCI和smc 不同的client,core上下电,访问所有地址空间 |
bl32 | EL1 | S | trust-os |
bl33 | EL2 | NS | 在ns的el2上执行,把ns-el1腾出来给kernel,方便兼容64位和32位的kernel |
kernel | EL1 | NS |
Make PLAT=fvp COLD_BOOT_SINGLE_CPU=1 SPD=tspd bl2 CROSS_COMPILE=<path-to-aarch64-gcc>/bin/aarch64-linux-gnu-
Bl2入口地址:bl2_entrypoint 分别在bl2/aarch64/bl2_entrypoint.S和bl2/aarch64/bl2_el3_entrypoint.S)
Ld文件:bl2.ld.s和bl2_el3.ld.S
在编译bl2时,会在./build/<PLAT>/<DEBUG>bl2/目录下下生成最终的ld文件bl2*.ld。
对于AArch64:
对于ARM fvp,BL2 执行下列的初始化步骤:
BL2通过查找image list的方式加载image,并且将这个list传递给下一个bl 镜像。(bl1也是这么做的)。
平台实现方法提供的可加载image list还可以包含动态配置文件。这个配置文件可以根据需要在bl2_plat_handle_post_image_load()函数中进行解析。 通过更新此函数中的相应ep信息,可以将这些配置文件作为参数传递给下一个Boot Loader阶段。
某些系统具有单独的系统控制处理器(SCP),用于电源,时钟,复位和系统控制。 BL2将可选的SCP_BL2镜像从平台存储设备加载到特定的安全内存区域。 SCP_BL2的后续处理是特定于具体平台的,需要自行实现。 例如,Arm Juno ,BL2先把SCP_BL2加载到trust sram,再使用Boot Over MHU (BOM) 协议,把SCP_BL2加载到 SCP的内部RAM之后,SCP运行SCP_BL2,并给AP发出signals,通知BL2继续执行。
BL2从平台存储设备加载EL3 runtime software 到trusted SRAM.如果内存空间不够或者镜像不存在去,则assert停止运行
BL2将可选的BL32镜像从平台存储设备加载到特定于平台的安全存储区域。BL32镜像在安全世界中执行。BL2依靠BL31将控制权限传递给BL32(如果存在)。 因此,BL2也会使用BL32镜像的entrypoint。 用于进入BL32的Saved Processor Status Register(SPSR)的值不是由BL2确定的,它由BL31内的Secure-EL1 Payload Dispatcher(SPD)初始化,SPD负责管理与BL32的交互。此信息将传递给BL31。
BL2将BL33镜像(e.g. UEFI or other test or boot software)从平台存储设备加载到由平台定义的非安全内存中。
一旦安全状态初始化完成,BL2依靠EL3 Runtime Software将控制权传递给BL33。 因此,BL2使用正常世界的镜像入口和保存程序状态寄存器(SPSR)填充平台指定的存储区域。entrypoint是BL33镜像的加载地址。 SPSR按照PSCI PDD中的规定确定(PSCI 5.13节)。 此信息将传递给EL3runtime software。
BL2执行继续如下:
一些平台的BOOT ROM是non-TF-A的,并且下一阶段的boot直接运行在EL3上。这些bl2atEL1的平台上,TF-A BL1浪费了内存资源,因为这样做的唯一目的是确保在S-EL1上运行TF-A BL2。为了避免这种浪费,ARM提供一个特殊模式使BL2能够在EL3上执行,允许非TF-A Boot ROM加载并直接跳转到BL2。使用BL2_AT_EL3=1使能该模式。这种模式的主要区别是:
我们假设平台支持3种不同类型的BootROM:
在最后两种情况下,BL2的任何代码都不需要驻留在内存中。在前两种情况下,我们希望Boot ROM能够区分Cold boot和Warm boot,以避免在warm boot期间再次加载BL2。
FVP平台可以直接测试此功能,将镜像直接加载到内存中,并更改系统reset时候的跳转地址。例如:
-C cluster0.cpu0.RVBAR=0x4022000 –data cluster0.cpu0=bl2.bin@0x4022000
通过这种配置,FVP就像前面描述第一种情况,其中Boot ROM总是跳转到同一地址。 为简化起见,在这种情况下,BL32加载到DRAM中,以避免其他镜像回收BL2内存。
bl2/aarch32和bl2/aarch64。
BL2 at EL1使用的链接脚本是bl2.ld.S
BL2 at EL3使用的链接脚本是bl2_el3.ld.S
代码很简单:
15行,BL2_BASE,BL2_LIMIT都在arm-def.h中定义。注意,在BL1中使用了BL2_BASE,BL2_LIMIT,所以修改此值后,所有的bl 都需要重新编译。
22行,PAGE_SIZE在include/lib/xlat_tables/xlat_tables_defs.h中定义
入口地址:bl2_entrypoint
给.image_pasrser_lib_desc保留run-able地址
85行定位RW起始地址
Stack。
BSS段
Xlat_table库代码。Bl1.ld.s也是如此
EL1上的bl2相对简单,bl2/aarch64/bl2_entrypoint.S:
首先保存bl1 用到的x0~x3寄存器,说明今后bl1还需要运行。
31行,设置S-EL1 vector base address :early_exceptions。
在common/aarch64/early_exceptions.S中,定义各种异常的入口函数,调用平台实现函数plat_xxx处理异常:
early_exceptions不会处理任何异常,都会panic
清D-cache
bl2_entrypoint函数(bl2/aarch64/bl2_entrypoint.S, 35~65行),使能SError异常;D-Cache,stack pointer and data access alignment checks;使bl2使用ram空间的d-cache无效。
inv_dcache_range 在\lib\aarch64\cache_helpers.S,line 12,bl1中介绍过,这里略
初始化BSS和COHERENT_MEM
bl2_entrypoint函数(bl2/aarch64/bl2_entrypoint.S, 73~87行),初始化bss段和COHERENT_MEM(目前没用到)。
建立C stack
bl2_entrypoint函数(bl2/aarch64/bl2_entrypoint.S,97行),跳转plat_set_my_stack执行:分配栈,当启用MMU时,其内存将标记为Normal-IS-WBWA。启用MMU后没有读取过时栈内存的风险,因为此时只有主CPU核正在运行。
Plat_set_my_stack,平台实现函数:porting guide对其描述如下:
设置当前栈指针,位于normal memory stack ,分配给当前CPU。对于bl只配分一个stack给主CPU(BL2只有主核在跑),使用UP版本代码(另一个版本是MP)栈大小由PLATFORM_STACK_SIZE指定。
PLATFORM_STACK_SIZE在plat/arm/board/fvp/include/platform_def.h中定义:
支持trust boot情况下,验签需要递归实现,所以栈大一点。分别是4K和1K
通用的实现(weak函数)plat/common/aarch64/platform_up_stack.S(UP) 和plat/common/aarch64/platform_mp_stack.S(MP)
我们看UP:
两个宏,第一个get_up_stack(include/commonaarch64/asm_macro.S,120行):
第二个platform_normal_stacks:
Declare_stack 在(include/commonaarch64/asm_macro_common.S,92行):
将栈放到.section tzfw_normal_stacks段(对应到bl2.ld.s ,99行)
针对Stack的安全措施
回到bl2_entrypoint函数(bl2/aarch64/bl2_entrypoint.S, 104~106行):
尽管默认STACK_PROTECTOR_ENABLED=0,但我们有必要分析如何保护stack的安全,有一部分攻击是通过 stack overflow的。关于stack protect,可以参考:wiki 和附录A
在\lib\stack_protector\aarch64\asm_stack_protector.S :
先看porting guide:
使用ENABLE_STACK_PROTECTOR=1启用堆栈保护。此函数返回一个随机值,这种方法称为Random canaries。 由于可预测的值会削弱保护作用,因为攻击者可以在大多数时间轻松地将正确的值写入攻击的一部分。 因此,它应该返回一个真正的随机数。
注意:为使保护有效,需要将全局数据放在比堆栈基数低的地址。 如果不这样做,攻击者就会覆盖canary,作为stack buffer overflow攻击的一部分。
Fvp平台没有random number generator,使用cntpct^ 固定值充当随机数
具体攻击方法和原理见附录A
Bl2_early_platform_setup2
回到bl2_entrypoint函数(bl2/aarch64/bl2_entrypoint.S, 108~117行)
在bl2/aarch64/bl2_entrypoint.S, 108~117行21~25行,将x0~x3先保存在了20~23中,此时恢复出来
在porting guide中:
在MMU和d-cache关闭的情况下运行 只有主核运行这个函数, 4个参数是平台指定的由bl1传递给bl2的。 ARM 平台上,这些参数是: arg0 - 如果存在HW_CONFIG ,则arg0为HW_CONFIG的地址 arg1 - 在bl1中设置的meminfo 结构体. 函数功能: 初始化UART(PL011,一个arm的ip) console 初始化storage abstraction layer为读取bootloader images做准备。注意,SCP_BL2 加载之后(如果存在的话),就要立即执行bl2_platform_setup . |
bl2_early_platform_setup2在plat/arm/board/fvp/fvp_bl2_setup.c16行(和bl1类似,arm_bl2_setp.c也存在一个默认的弱实现plat/arm/common/arm_bl2_setup.c):
四个参数,只用前两个。回忆bl1中的Cold boot期间的动态配置,
Arg0用来保存tb_fw_config,arg1用来保存mem_layout
bl2_early_platform_setup2首先调用arm_bl2_early_platform_setup(plat/arm/common/arm_bl2_setup.c,51行):
对比bl1中的arm_bl1_eary_platform_setup,bl2不需要重新初始化WDOG。执行了在bl1中相同的console初始化arm_console_boot_init。bl2_tzram_layout是一个meminfo_t结构体
plat_arm_io_setup则是调用io_dev_xxx 初始化相关的io。这在bl1的bl1_main->bl1_platform_setup->arm_bl1_platform_setup调用的代码相同。只是在bl2中的plat_arm_io_setup执行位置比较靠前。
arm_bl2_set_tb_cfg_addr则是给void* 的tb_fw_cfg_dtb直接赋值:
fvp_config_setup(plat/arm/board/fvp/fvp_common.c,245行),初始化平台配置供将来使用。在bl1阶段,bl1_early_platform_setup也执行过同样的代码。
具体代码TBD。
再看看其他平台在bl2_early_platform_setup2做了什么:
\plat\hisilicon\poplar\bl2_plat_setup.c
初始化console和emmc,timer
\plat\marvell\common\bl2_plat_setup.c
bl2_plat_arch_setup
回到bl2_entrypoint函数(bl2/aarch64/bl2_entrypoint.S, 119行):调用bl2_plat_arch_setup
在plat/arm/common/arm_bl2_setup.c,139行
在porting guide中:
写的很清楚,执行非常早期的平台特定架构的初始化。初始化mmu,映射BL2 RO。105行:
注意
他也是个weak函数,但fvp平台并没有实现平台代码。
再看看其他平台做了什么:
\plat\hisilicon\poplar\bl2_plat_setup.c:
\plat\marvell\common\bl2_plat_setup.c
\plat\marvell\common\bl2_plat_setup.c
都是以平台实现的特定方式初始化mmu。
bl2_plat_arch_setup,与bl1中的bl1_plat_setup类似。在bl1_entrypoint执行44行的bl1_plat_arch_setup(\plat\arm\common\arm_bl1_setup.c)调用arm_bl1_plat_arch_setup,同样配置bl1 ram的mmu。ARM在各BL阶段的初始化流程和代码调用基本一致。
那么,下面应该是执行bl2_main了。在此之前,bl2_plat_arch_setup的136行调用的arm_setup_romlib,后面章节会详细说明。
(To be continued)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。