Secure boot入门-2fip包加载image流程

张开发
2026/4/14 8:29:02 15 分钟阅读

分享文章

Secure boot入门-2fip包加载image流程
本小节从代码的角度去看下代码环境准备还是参考之前的文章ATF入门-1qmeu搭建ARM全套源码学习环境不用开发板免费学习ARM。secure boot在arm上需要用到fip包这里以bl1加载bl2为例bl2.bin是在fip.bin里面进行打包的。参考之前的文章ATF入门-3BL2启动流程分析 2.4.3中bl2镜像加载章节bl1_load_bl2函数为例进行说明。本篇文章从代码角度深挖到底进行分析bl2的加载流程纯干货实操分享欢迎下载代码进行加log打印调试上手试一试还是那句“纸上得来终觉浅须知此事需躬行”。1 使用fip.bin代码修改makearm-tfDEBUG1make-fqemu_v8.mk run-only代码下载运行不再详述了。修改atf的代码后执行上面的命令就可以运行起来。但是里面有这个打印如之前文章ATF入门-3BL2启动流程分析中说的BL2的镜像直接使用的bl2.bin而不是从fip包里面获取的这里的原因就是fip包的内容读取失败了没有进行内存映射fip包的头校验也失败了最后直接从文件获取了。首先我们来看下**是否有fip.bin**在目录说明我们编译已经生成了fip.bin既然bl2.bin都可以从文件里面读。那么我们仿照bl2.bin也搞一下。首先是在out目录里面建立软连接ln-s/home/XXX/optee/build/../trusted-firmware-a/build/qemu/debug/fip.bin fip.bin我们使用hexdump看下这个fip.bin的内容上面我们说报错Firmware Image Package header check failed.\n的地方校验代码如下/* This is used as a signature to validate the blob header */#define TOC_HEADER_NAME 0xAA640001static inline int is_valid_header(fip_toc_header_t *header){if((header-nameTOC_HEADER_NAME)(header-serial_number!0)){return1;}else{return0;}}可见我们找到的fip.bin的文件头标识是对的。那么怎么才能可以使用fip.bin我们修改代码int plat_get_image_source(unsigned int image_id, uintptr_t *dev_handle, uintptr_t *image_spec){const struct plat_io_policy *policyget_io_policy(image_id);int result;if(image_id0){returnget_alt_image_source(image_id, dev_handle, image_spec);}resultpolicy-check(policy-image_spec);if(result0){INFO($$$$plat_get_image_source check success\n);*image_specpolicy-image_spec;*dev_handle*(policy-dev_handle);}else{INFO(Trying alternative IO\n);resultget_alt_image_source(image_id, dev_handle, image_spec);}returnresult;}get_alt_image_source就是从文件里面直接去读取fip.bin的。get_alt_image_source–》open_semihosting–》get_alt_image_source–》get_io_file_spec–》sh_file_spec#define FIP_IMAGE_NAME fip.binstatic const io_file_spec_t sh_file_spec[]{[FIP_IMAGE_ID]{.pathFIP_IMAGE_NAME, .modeFOPEN_MODE_RB},2. bl1_load_bl2过程函数解析2.1 bl1_load_bl2bl1_load_bl2 bl1_plat_get_image_desc bl1_plat_handle_pre_image_load load_auth_image bl1_plat_handle_post_image_load主要就是load_auth_image去load bl2的bin文件。load_auth_image–》load_imageload_image plat_get_image_source io_open io_read io_close去获取镜像的资源在哪里plat_get_image_source在qemu平台上在plat/qemu/common/qemu_io_storage.c中定义plat_get_image_source get_io_policy policy-check()get_alt_image_sourceget_io_policy获取策略入参是BL2的image id就是1策略的定义如下/* Firmware Image Package */#define FIP_IMAGE_ID U(0)/* Trusted Boot Firmware BL2 */#define BL2_IMAGE_ID U(1)/* By default, ARM platforms load images from the FIP */ static const struct plat_io_policy policies[]{[FIP_IMAGE_ID]{memmap_dev_handle,(uintptr_t)fip_block_spec, open_memmap},[BL2_IMAGE_ID]{fip_dev_handle,(uintptr_t)bl2_uuid_spec, open_fip},policy-check()对于BL2_IMAGE_ID来说就是open_fip()函数open_fip–》io_dev_init–》fip_dev_init2.2 fip_dev_init读取fipfip_dev_init plat_get_image_source io_open io_read io_closeplat_get_image_source()在plat/qemu/common/qemu_io_storage.c中实现,这时候是第二次进入这个函数了,这次的image id是0,对应的policy是[FIP_IMAGE_ID]{memmap_dev_handle,(uintptr_t)fip_block_spec, open_memmap},这个policy有问题读不出来fip的内容因为对于头文件校验失败fip_dev_init–》io_read–》is_valid_headertypedef struct fip_toc_header{uint32_t name;uint32_t serial_number;uint64_t flags;}fip_toc_header_t;static inline int is_valid_header(fip_toc_header_t *header){if((header-nameTOC_HEADER_NAME)(header-serial_number!0)){return1;}else{return0;}}我们直接使用get_alt_image_source来在plat_get_image_source()函数里面添加int plat_get_image_source(unsigned int image_id, uintptr_t *dev_handle, uintptr_t *image_spec){const struct plat_io_policy *policyget_io_policy(image_id);int result;if(image_id0){returnget_alt_image_source(image_id, dev_handle, image_spec);}这样读出来fip.bin再回到load_image函数里面执行io_open/io_read/io_close操作把bl2.bin从fip.bin里面成功加载到内存中。2.3 从fip读取bl2.binio_open–》fip_file_openstatic int fip_file_open(io_dev_info_t *dev_info, const uintptr_t spec, io_entity_t *entity){const io_uuid_spec_t *uuid_spec(io_uuid_spec_t *)spec;resultio_open(backend_dev_handle, backend_image_spec,backend_handle);/* Seek past the FIP header into the Table of Contents */ resultio_seek(backend_handle, IO_SEEK_SET,(signed long long)sizeof(fip_toc_header_t));do{resultio_read(backend_handle,(uintptr_t)current_fip_file.entry, sizeof(current_fip_file.entry),bytes_read);if(result0){if(compare_uuids(current_fip_file.entry.uuid,uuid_spec-uuid)0){found_file1;}}else{WARN(Failed to read FIP (%i)\n, result);goto fip_file_open_close;}}while((found_file0)(compare_uuids(current_fip_file.entry.uuid,uuid_null)!0));spec是从policy里面获取到的从上面代码里面看从fip包里面找bl2.bin是需要借助这个变量进行比对的bl2_uuid_spec定义为static const io_uuid_spec_t bl2_uuid_spec{.uuidUUID_TRUSTED_BOOT_FIRMWARE_BL2,};#define UUID_TRUSTED_BOOT_FIRMWARE_BL2 \{{0x5f, 0xf9, 0xec, 0x0b},{0x4d, 0x22},{0x3e, 0x4d}, 0xa5, 0x44,{0xc3, 0x9d, 0x81, 0xc7, 0x3f, 0x0a}}关于fip包的操作都在drivers/io/io_fip.c文件里面。fip包的格式如下------------------|ToC Header||----------------||ToC Entry0||----------------||ToC Entry1||----------------||ToC End Marker||----------------||||Data0||||----------------||||Data1|||------------------下面我们详细看下怎么读取里面的内容首先就是获取fip.bin的内存地址到backend_handle。backend_dev_handle和backend_image_spec是在打开fip.bin的时候调用fip_dev_init函数的时候拿到的全局变量这里直接使用。resultio_open(backend_dev_handle, backend_image_spec,backend_handle);然后定位去掉fip文件的头ToC Header也就是结构体fip_toc_header_tresultio_seek(backend_handle, IO_SEEK_SET,(signed long long)sizeof(fip_toc_header_t));typedef struct fip_toc_header{uint32_t name;uint32_t serial_number;uint64_t flags;}fip_toc_header_t;之后就是ToC Entry 0这里我们循环读取到current_fip_file.entry变量中resultio_read(backend_handle,(uintptr_t)current_fip_file.entry, sizeof(current_fip_file.entry),bytes_read);然后跟我们传入的bl2的uuid_spec-uuid进行比较if(compare_uuids(current_fip_file.entry.uuid,uuid_spec-uuid)0){found_file1;}typedef struct fip_toc_entry{uuid_t uuid;uint64_t offset_address;uint64_t size;uint64_t flags;}fip_toc_entry_t;typedef struct uuid uuid_t;struct uuid{uint8_t time_low[4];uint8_t time_mid[2];uint8_t time_hi_and_version[2];uint8_t clock_seq_hi_and_reserved;uint8_t clock_seq_low;uint8_t node[_UUID_NODE_LEN];};可见uuid是一个有6个元素的结构体跟include/tools_share/firmware_image_package.h中的定义一致#define UUID_TRUSTED_BOOT_FIRMWARE_BL2 \{{0x5f, 0xf9, 0xec, 0x0b},{0x4d, 0x22},{0x3e, 0x4d}, 0xa5, 0x44,{0xc3, 0x9d, 0x81, 0xc7, 0x3f, 0x0a}}对比好uuid就可以拿到struct fip_toc_entry结构体的第二个元素offset_address的值和size的值。这里只是找到fip.bin中bl2.bin的位置了**读到内存中**是发生在load_image–》plat_get_image_source–》io_read–》fip_file_readstatic int fip_file_read(io_entity_t *entity, uintptr_t buffer, size_t length, size_t *length_read){fip_file_state_t *fp;fp(fip_file_state_t *)entity-info;/* Seek to the positioninthe FIP where the payload lives */ file_offsetfp-entry.offset_address fp-file_pos;resultio_seek(backend_handle, IO_SEEK_SET,(signed long long)file_offset);传入是是结构体io_entity_t里面entity-info才是fip_file_state_t嵌套关系如下typedef struct io_entity{struct io_dev_info *dev_handle;uintptr_t info;}io_entity_t;typedef struct{unsigned int file_pos;fip_toc_entry_t entry;}fip_file_state_t;typedef struct fip_toc_entry{uuid_t uuid;uint64_t offset_address;uint64_t size;uint64_t flags;}fip_toc_entry_t;那么这个是怎么获取的读之前是先打开的load_image–》plat_get_image_source–》io_open–》fip_file_openstatic int load_image(unsigned int image_id, image_info_t *image_data){uintptr_t image_handle;io_resultio_open(dev_handle, image_spec,image_handle);static int fip_file_open(io_dev_info_t *dev_info, const uintptr_t spec, io_entity_t *entity){if(found_file1){/* All fine. Update entity info withfilestate and return. Set * thefileposition to0. Thecurrent_fip_file.entryholds * the base and size of the file. */ current_fip_file.file_pos0;entity-info(uintptr_t)current_fip_file;}可见fip_file_open中找到uuid对应的bl2.bin后就把entity的info赋值了。下图是笔者调试的一些打印调试的时候如果一下启动到linux里面打印非常的多我们可以让起到我们想研究的地方就停下来调用如下代码就可以了void __dead2 plat_error_handler(int err){while(1)wfi();}后记上面的分析是从fip包里面读取bl2.bin镜像对于secure boot来说有两点1.信任链2.加解密。这里bl1加载bl2有了信任链但是这里缺少一个流程那就是加解密。下一个文章我们再详细介绍这里先把fip包搞明白。最近一忙起来点抽空看这个代码就比较费劲老是看了一会就忘记之前看到那个流程了。后来感觉需要画一个流程图帮助记忆这样下一次再看的时候很快就能顺起来就这样断断续续看了一周才看完老铁们不容易啊赶紧收藏研究比自己看代码事半功倍。“啥都懂一点啥都不精通干啥都能干干啥啥不是专业入门劝退堪称程序员杂家”。欢迎各位有自己公众号的留言申请转载纯干货持续更新欢迎分享给朋友、点赞、收藏、在看、划线和评论交流“那路谈OS与SoC嵌入式软件”欢迎关注个人文章汇总https://thatway1989.github.io

更多文章