image.png

本小节从代码的角度去看下,代码环境准备还是参考之前的文章:XXX。镜像加载的时候进行校验,这里参考之前的文章:XXX 2.4.3中bl2镜像加载章节,bl1_load_bl2()函数为例进行说明。

本篇文章从代码角度,深挖到底,进行分析bl2的加载流程,纯干货实操分享,欢迎下载代码进行加log打印调试,上手试一试,还是那句:“纸上得来终觉浅,须知此事需躬行”。

1 使用fip.bin代码修改

make arm-tf DEBUG=1
make -f qemu_v8.mk run-only

修改atf的代码后,执行上面的命令就可以运行起来。但是里面有这个打印:

image.png

如之前文章XXX中说的,BL2的镜像直接使用的bl2.bin而不是从fip包里面获取的,这里的原因就是fip包的内容读取失败了,fip包的头校验也失败了,最后直接从文件获取了。

首先我们来看下是否有fip.bin在目录

image.png

说明我们编译已经生成了fip.bin,既然bl2.bin都可以从文件里面读。那么我们仿照bl2.bin也搞一下。首先是在out目录里面建立软连接:

ln -s /home/XXX/optee/build/../trusted-firmware-a/build/qemu/debug/fip.bin fip.bin

image.png

我们使用hexdump看下这个fip.bin的内容:

image.png

上面我们说报错"Firmware Image Package header check failed.\n":的地方校验代码如下:

/* This is used as a signature to validate the blob header */
#define TOC_HEADER_NAME    0xAA640001

static inline int is_valid_header(fip_toc_header_t *header)
{
    if ((header->name == TOC_HEADER_NAME) && (header->serial_number != 0)) {
        return 1;
    } else {
        return 0;
    }
}

可见我们找到的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 *policy = get_io_policy(image_id);
    int result;

    if (image_id == 0) {
        return get_alt_image_source(image_id, dev_handle, image_spec);
    }

    result = policy->check(policy->image_spec);
    if (result == 0) {
        INFO("$$$$ plat_get_image_source check success\n");

        *image_spec = policy->image_spec;
        *dev_handle = *(policy->dev_handle);
    } else {
        INFO("Trying alternative IO\n");
        result = get_alt_image_source(image_id, dev_handle, image_spec);
    }

    return result;
}

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.bin"

static const io_file_spec_t sh_file_spec[] = {
    [FIP_IMAGE_ID] = {
        .path = FIP_IMAGE_NAME,
        .mode = FOPEN_MODE_RB
    },

2. bl1_load_bl2过程函数解析

image.png

2.1 bl1_load_bl2

bl1_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去loadbl2的bin文件。 load_auth_image--》load_image

load_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_source

get_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_init

2.2 fip_dev_init读取fip

fip_dev_init
    plat_get_image_source
    io_open
    io_read
    io_close

plat_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_header

typedef 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->name == TOC_HEADER_NAME) && (header->serial_number != 0)) {
        return 1;
    } else {
        return 0;
    }
}

我们直接使用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 *policy = get_io_policy(image_id);
    int result;

    if (image_id == 0) {
        return get_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.bin

io_open--》fip_file_open,

static 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;

     result = io_open(backend_dev_handle, backend_image_spec,
             &backend_handle);

     /* Seek past the FIP header into the Table of Contents */
    result = io_seek(backend_handle, IO_SEEK_SET,
             (signed long long)sizeof(fip_toc_header_t));

do {
        result = io_read(backend_handle,
                 (uintptr_t)&current_fip_file.entry,
                 sizeof(current_fip_file.entry),
                 &bytes_read);
        if (result == 0) {
            if (compare_uuids(&current_fip_file.entry.uuid,
                      &uuid_spec->uuid) == 0) {
                found_file = 1;
            }
        } else {
            WARN("Failed to read FIP (%i)\n", result);
            goto fip_file_open_close;
        }
    } while ((found_file == 0) &&
            (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 = {
    .uuid = UUID_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 Entry 0    |
|----------------|
| ToC Entry 1    |
|----------------|
| ToC End Marker |
|----------------|
|                |
|     Data 0     |
|                |
|----------------|
|                |
|     Data 1     |
|                |
------------------

下面我们详细看下怎么读取里面的内容:

首先就是获取fip.bin的内存地址到backend_handle。backend_dev_handle和backend_image_spec是在打开fip.bin的时候调用fip_dev_init函数的时候拿到的全局变量,这里直接使用。

     result = io_open(backend_dev_handle, backend_image_spec,
             &backend_handle);

然后定位去掉fip文件的头ToC Header 也就是结构体fip_toc_header_t

result = io_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变量中

result = io_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_file = 1;
}
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_read

static 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 position in the FIP where the payload lives */
    file_offset = fp->entry.offset_address + fp->file_pos;
    result = io_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_open

static int load_image(unsigned int image_id, image_info_t *image_data)
{
uintptr_t image_handle;
io_result = io_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_file == 1) {
        /* All fine. Update entity info with file state and return. Set
         * the file position to 0. The 'current_fip_file.entry' holds
         * the base and size of the file.
         */
        current_fip_file.file_pos = 0;
        entity->info = (uintptr_t)&current_fip_file;
    }

可见fip_file_open中找到uuid对应的bl2.bin后就把entity的info赋值了。下图是笔者调试的一些打印:

image.png

调试的时候,如果一下启动到linux里面,打印非常的多,我们可以让起到我们想研究的地方就停下来,调用如下代码就可以了:

void __dead2 plat_error_handler(int err)
{
    while (1)
        wfi();
}

后记:

上面的分析是从fip包里面读取bl2.bin镜像,对于secure boot来说有两点:1.信任链2.加解密。这里bl1加载bl2有了信任链,但是这里缺少一个流程那就是加解密。下一个文章我们再详细介绍,这里先把fip包搞明白。

最近一忙起来点,抽空看这个代码就比较费劲,老是看了一会就忘记之前看到那个流程了。后来感觉需要画一个流程图,帮助记忆,这样下一次再看的时候很快就能顺起来,就这样断断续续看了一周才看完。帮助记忆和梳理逻辑还是多画图比较好。

results matching ""

    No results matching ""