【OP-TEE】TA的加载(超详细)

本文详细介绍了TEE(Trusted Execution Environment)中动态可信应用程序(TA)的加载流程,从通过GP标准调用打开会话开始,深入到TEE内部的tee_entry_std函数,再到动态加载TA的实现。解析了ldelf如何加载TA二进制,包括验签、解密、映射段等步骤。最后,阐述了如何解析ELF文件,加载段到安全内存,并处理依赖关系,直至TA准备就绪,进入用户模式运行。
摘要由CSDN通过智能技术生成

在这里插入图片描述

动态TA按照存储位置的不同分为REE filesystem TA:存放在REE侧文件系统里的TA;Early TA:被嵌入到optee os里的在supplicant启动之前就可用了。这里我们讲的是常规的存放在REE侧文件系统里的TA。

通过GP标准调用的与TA通信的命令(opensession\invoke\closession)其实都是std smc call,该smc调用后,会进入到TEE中的tee_entry_std中。
tee_entry_std函数中调用就是opensession\invoke\closession的接口。调用到entry_open_session接口。动态TA最终会在tee_ta_init_user_ta_session函数里加载。

ldelf

elf格式的文件是一种可执行文件,ldelf就是加载elf文件的加载器。而TA文件就是加了头的elf文件。所以ldelf其作用为load需要加载的TA。调用ldelf → ta_elf_load_main → load_main加载TA的二进制文件。
tee_ta_init_user_ta_session里调用load_ldelf函数。其实现大致为,在代码编译的时候,optee-os下的ldelf目录下编译出ldelf.elf二进制文件,通过gen_ldelf_hex.py脚本将二进制文件生成ldelf_hex.c文件。其二进制数据会被写入数组ldelf_data,还有代码段,数据段的大小。在load_ldelf函数中将其加载到内存中。再通过init_with_ldelf函数调用thread_enter_user_mode函数切换到用户模式el0,运行此ldelf的代码。
在这里插入图片描述

init_elf:

验签、解密、加载TA二进制文件,并判断其是否是ELF格式。

sys_open_ta_bin:

其功能是调用systemPTA,找到并打开TA文件,并返回句柄。
此函数会遍历注册的TA_STORE,调用open等操作函数。注册的是下面这个。

TEE_TA_REGISTER_TA_STORE(9) = {
	.description = "REE",
	.open = ree_fs_ta_open,
	.get_size = ree_fs_ta_get_size,
	.get_tag = ree_fs_ta_get_tag,
	.read = ree_fs_ta_read,
	.close = ree_fs_ta_close,
};

ree_fs_ta_open函数解析:
先调用rpc_load:通过rpc调用ree侧的tee_supplicant ,传入uuid,ree侧将uuid对应的TA二进制文件加载到共享内存中,在rpc_load中包含两次TA加载的rpc请求,第一次参数里只有UUID,因为此时optee-os不知道加载的TA的image大小,tee supplicant接收到TA加载请求后由于解析出参数的image size为0,所以其通过UUID读取TA文件后计算出ta_size,发现传入大小不足,所以此时没有加载TA 并返回需要传入的size给到optee-os的rpc_load(),其得到size后通过rpc请求size大小的共享内存并得到地址,此时将uuid、共享内存地址、size传入作为第二次rpc加载TA请求,tee-suplicant收到后加载TA到对应的共享内存中。

/* Request TA from tee-supplicant */
res = rpc_load(uuid, &ta, &ta_size, &mobj);

接着调用shdr_alloc_and_copy:通过rpc_load()之后得到的TA加载的共享内存地址,因为TA镜像在签名时增加签名相关的头部文件shdr,在此函数中计算shdr的大小,struct shdr + hash_size + sig_size。然后申请此大小的内存并将TA镜像的shdr拷贝到申请的内存里。用于后面的验签流程。

/* Make secure copy of signed header */
shdr = shdr_alloc_and_copy(ta, ta_size);

接着调用shdr_verify_signature执行验签工作。传入镜像头部文件shdr指针。shdr包括了magic、img_type、img_size、algo等参数。先检验代码中的magic与shdr中的是否一致,从前面提到的ta_pub_key.c中读取ta_pub_key_modulus,提取出pub_key,从shdr中提取hash和sig,使用rsa算法验签此shdr。

/* Validate header signature */
res = shdr_verify_signature(shdr);

然后在ree_fs_ta_open()里还要取出TA镜像里的uuid与传入的uuid比较,并初始化hash_ctx。

sys_map_ta_bin

其功能是调用systemPTA,映射TA二进制文件的各种segment到安全内存中。

res = sys_map_ta_bin(&va, SMALL_PAGE_SIZE, flags, elf->handle, 0, 0, 0);

在init_elf中,此处是第一次调用,传入的长度是0x1000,最小页大小。目的为先将TA的从0到0x1000的数据映射到安全内存。主要作用先申请块安全内存,然后映射其地址。

struct fobj *f = fobj_ta_mem_alloc(num_pages);
mobj = mobj_with_fobj_alloc(f, file);
res = vm_map_pad(uctx, va, num_rounded_bytes,
                 TEE_MATTR_PRW, vm_flags, mobj, 0,
                 pad_begin, pad_end, 0);

经过这几个步骤,其就会申请了大小为num_pages个pages的空间,并映射到va_range的地址空间里,调试出的信息就是申请的是0xexxxxxxx的安全内存物理地址,变为了0x4xxxxxxx的虚拟地址里。然后调用binh_copy_to函数,此函数调用注册的ree_fs_ta_read,将共享内存中的TA镜像加载到安全内存中。

binh_copy_to函数解析

/*此函数是根据binh句柄,其中记录了当前读取ELF文件的偏移,文件总大小的信息,来从offs_bytes在文件中的偏移,
读取num_bytes长度的数据到va这个地址,其调用注册的read接口,也就是ree_fs_ta_read*/
static TEE_Result binh_copy_to(struct bin_handle *binh, vaddr_t va,
			       size_t offs_bytes, size_t num_bytes)
{
	TEE_Result res = TEE_SUCCESS;
	size_t next_offs = 0;

	if (offs_bytes < binh->offs_bytes)
		return TEE_ERROR_BAD_STATE;

	if (ADD_OVERFLOW(offs_bytes, num_bytes, &next_offs))
		return TEE_ERROR_BAD_PARAMETERS;

	if (offs_bytes > binh->offs_bytes) {
		/*此处需要读取文件的偏移已经大于了文件所处的偏移,因为ELF文件中的segment之间存在空的部分,
		如果不读取中见的信息,那么ree_fs_ta_read里更新hash_ctx就会出问题,
		导致校验hash不过,所以传入空的地址给read函数,只是为了读取空的部分更新hash_ctx,并不加载*/
		res = binh->op->read(binh->h, NULL,
				     offs_bytes - binh->offs_bytes);
		if (res)
			return res;
		binh->offs_bytes = offs_bytes;
	}

	if (next_offs > binh->size_bytes) {
		/*这种情况就是需要读取的部分已经超过了文件的范围,所以此时直接将文件剩下的全部读取*/
		size_t rb = binh->size_bytes - binh->offs_bytes;

		res = binh->op->read(binh->h, (void *)va, rb);
		if (res)
			return res;
		memset((uint8_t *)va + rb, 0, num_bytes - rb);
		binh->offs_bytes = binh->size_bytes;
	} else {
		res = binh->op->read(binh->h, (void *)va, num_bytes);
		if (res)
			return res;
		binh->offs_bytes = next_offs;
	}

	return TEE_SUCCESS;
}

ree_fs_ta_read函数解析:

static TEE_Result ree_fs_ta_read(struct user_ta_store_handle *h, void *data,
				 size_t len)
{
	struct ree_fs_ta_handle *handle = (struct ree_fs_ta_handle *)h;
	/*获取TA镜像除去头之后的elf格式所在位置*/
	uint8_t *src = (uint8_t *)handle->nw_ta + handle->offs;
	size_t next_offs = 0;
	uint8_t *dst = src;
	TEE_Result res = TEE_SUCCESS;

	if (ADD_OVERFLOW(handle->offs, len, &next_offs) ||
	    next_offs > handle->nw_ta_size)
		return TEE_ERROR_BAD_PARAMETERS;
	/*判断TA类型*/
	if (handle->shdr->img_type == SHDR_ENCRYPTED_TA) {
		if (data) {
			dst = data; /* Hash secure buffer */
			/*解密len长度的数据到安全内存中,加载多长的数据就解密多长的数据*/
			res = tee_ta_decrypt_update(handle->enc_ctx, dst, src,
						    len);
			if (res != TEE_SUCCESS)
				return TEE_ERROR_SECURITY;
		} else {
			size_t num_bytes = 0;
			size_t b_size = MIN(1024U, len);
			uint8_t *b = malloc(b_size);

			if (!b)
				return TEE_ERROR_OUT_OF_MEMORY;

			dst = NULL;
			while (num_bytes < len) {
				size_t n = MIN(b_size, len - num_bytes);

				res = tee_ta_decrypt_update(handle->enc_ctx, b,
							    src + num_bytes, n);
				if (res)
					break;
				num_bytes += n;

				res = crypto_hash_update(handle->hash_ctx, b,
							 n);
				if (res)
					break;
			}

			free(b);
			if (res != TEE_SUCCESS)
				return TEE_ERROR_SECURITY;
		}
	} else if (data) { /*不是加密的TA则直接拷贝*/
		dst = data; /* Hash secure buffer (shm might be modified) */
		memcpy(dst, src, len);
	}

	if (dst) {
		/*更新hash_ctx,以便后面计算出TA的hash值使用*/
		res = crypto_hash_update(handle->hash_ctx, dst, len);
		if (res != TEE_SUCCESS)
			return TEE_ERROR_SECURITY;
	}

	handle->offs = next_offs;/*将句柄的偏移更新*/
	if (handle->offs == handle->nw_ta_size) {/*判断当前是否已经加载了所有的segment*/
		if (handle->shdr->img_type == SHDR_ENCRYPTED_TA) {
			/*
			 * Last read: time to finalize authenticated
			 * decryption.
			 */
			res = tee_ta_decrypt_final(handle->enc_ctx,
						   handle->ehdr, NULL, NULL, 0);
			if (res != TEE_SUCCESS)
				return TEE_ERROR_SECURITY;
		}
		/*
		 * Last read: time to check if our digest matches the expected
		 * one (from the signed header)
		 */
		 /*加载所有segment之后进行hash校验与shdr里的比较*/
		res = check_digest(handle);
		if (res != TEE_SUCCESS)
			return res;

		if (handle->bs_hdr)
			res = check_update_version(handle->bs_hdr);
	}
	return res;
}

由此时传入的参数得知,此次的sys_map_ta_bin只是将TA的elf文件的头映射到了内存中。
有了头就可以根据elf头判断其基本信息了。
完成这些之后,程序会解析判断其elf格式

if (!IS_ELF(*(Elf32_Ehdr *)va))/*根据elf head magic判断*/
		err(TEE_ERROR_BAD_FORMAT, "TA is not an ELF");

	res = e32_parse_ehdr(elf, (void *)va);
	if (res == TEE_ERROR_BAD_FORMAT)
		res = e64_parse_ehdr(elf, (void *)va);/*解析elf head到elf结构体*/
	if (res)
		err(res, "Cannot parse ELF");

	if (MUL_OVERFLOW(elf->e_phnum, elf->e_phentsize, &sz) ||
	    ADD_OVERFLOW(sz, elf->e_phoff, &sz))
		err(TEE_ERROR_BAD_FORMAT, "Program headers size overflow");

	if (sz > SMALL_PAGE_SIZE)
		err(TEE_ERROR_NOT_SUPPORTED, "Cannot read program headers");

	elf->phdr = (void *)(va + elf->e_phoff);

ELF格式的文件加载到安全内存之后,加载器会判断其是否是ELF格式的文件,以及头是否符合elf规范。
ELF格式分两种,32位和64位,基本一样。格式的基本信息包含在ELF的头里,这个是通用的,使用此结构体表示。

/*
 * ELF header.
 */

typedef struct {
	unsigned char	e_ident[EI_NIDENT];	/* File identification. */
	Elf64_Half	e_type;		/* File type. */
	Elf64_Half	e_machine;	/* Machine architecture. */
	Elf64_Word	e_version;	/* ELF format version. */
	Elf64_Addr	e_entry;	/* Entry point. */
	Elf64_Off	e_phoff;	/* Program header file offset. */
	Elf64_Off	e_shoff;	/* Section header file offset. */
	Elf64_Word	e_flags;	/* Architecture-specific flags. */
	Elf64_Half	e_ehsize;	/* Size of ELF header in bytes. */
	Elf64_Half	e_phentsize;	/* Size of program header entry. */
	Elf64_Half	e_phnum;	/* Number of program header entries. */
	Elf64_Half	e_shentsize;	/* Size of section header entry. */
	Elf64_Half	e_shnum;	/* Number of section header entries. */
	Elf64_Half	e_shstrndx;	/* Section name strings section. */
} Elf64_Ehdr;

elf head头后面就是程序头表program segment header,位置就是ELF头里的e_phnum值在的偏移。记录了每个Segment的相关信息,比如类型、对应文件的偏移、大小、属性等。用下面的结构体表示。

typedef struct {
    Elf64_Word  p_type;     /* Entry type. */
    Elf64_Word  p_flags;    /* Access permission flags. */
    Elf64_Off   p_offset;   /* File offset of contents. */
    Elf64_Addr  p_vaddr;    /* Virtual address in memory image. */
    Elf64_Addr  p_paddr;    /* Physical address (not used). */
    Elf64_Xword p_filesz;   /* Size of contents in file. */
    Elf64_Xword p_memsz;    /* Size of contents in memory. */
    Elf64_Xword p_align;    /* Alignment in memory and file. */
} Elf64_Phdr;

optee example的一个TA的elf读出其program header:
在这里插入图片描述
Init_elf函数结束。
总结就是:找到TA文件,验签成功则解密0x1000头部数据,加载0x1000字节到内存,判断其是否是elf以及是否合法。

map_segments

parse_load_segment:

将elf文件的program segment中的信息填充到elf指针中。放入管理segment的链表里。
在hello_world TA里放入的是Loadable segment类型的segment。所以segments只放入了两个

for (n = 0; n < elf->e_phnum; n++)
            if (phdr[n].p_type == PT_LOAD) {
                add_segment(elf, phdr[n].p_offset,
                        phdr[n].p_vaddr, phdr[n].p_filesz,
                        phdr[n].p_memsz, phdr[n].p_flags,
                        phdr[n].p_align);
            } else if (phdr[n].p_type == PT_TLS) {
                elf->tls_start = phdr[n].p_vaddr;
                elf->tls_filesz = phdr[n].p_filesz;
                elf->tls_memsz = phdr[n].p_memsz;
            }

adjust_segment:

做segment的健壮性检查。segment相关的一些设置。略过。

populate_segments

此函数的作用是根据添加到elf入参里的segment,不同类型segment进行不同的映射到安全内存中。
遍历放入map_segment函数放入链表里的segment,根据segment不同的flag参数,也就是读写等标识。进行不同的映射。
核心代码如下:

if (flags & PTA_SYSTEM_MAP_FLAG_WRITEABLE) {/*如果是可写的segment*/
	/*映射一块被初始化的地址给给va*/
	res = sys_map_zi(memsz, 0, &va, pad_begin,
             pad_end);
    if (pad_begin && res == TEE_ERROR_OUT_OF_MEMORY)
        res = sys_map_zi(memsz, 0, &va, 0,
                 pad_end);
    if (res)
        err(res, "sys_map_zi");
	/*调用的还是binh_copy_to函数,通过句柄,将大小为filesz的segment加载到va地址*/
    res = sys_copy_from_ta_bin((void *)va, filesz,
                   elf->handle, offset);
    if (res)
        err(res, "sys_copy_from_ta_bin");
} else {/*不是可写类型的segment*/
    if (filesz != memsz)
        err(TEE_ERROR_BAD_FORMAT,
             "Filesz and memsz mismatch");
	/*加载segment到新va*/
    res = sys_map_ta_bin(&va, filesz, flags,
                 elf->handle, offset,
                 pad_begin, pad_end);
    if (pad_begin && res == TEE_ERROR_OUT_OF_MEMORY)
        res = sys_map_ta_bin(&va, filesz, flags,
                     elf->handle,
                     offset, 0,
                     pad_end);
    if (res)
        err(res, "sys_map_ta_bin");
}

add_dependencies

添加依赖,也就是添加需要动态链接的segment信息

add_deps_from_segment
Dynamic segment存放的是和dynamic section相关的信息,结构为

/*
 * Dynamic structure.  The ".dynamic" section contains an array of them.
 */

typedef struct {
    Elf64_Sxword    d_tag;      /* Entry type. */
    union {
        Elf64_Xword d_val;  /* Integer value. */
        Elf64_Addr  d_ptr;  /* Address value. */
    } d_un;
} Elf64_Dyn

下图是optee_testTA的elf文件的Dynamic section
在这里插入图片描述

static void add_dependencies(struct ta_elf *elf)
{
	size_t n = 0;

	if (elf->is_32bit) {
		Elf32_Phdr *phdr = elf->phdr;

		for (n = 0; n < elf->e_phnum; n++)
			add_deps_from_segment(elf, phdr[n].p_type,
					      phdr[n].p_vaddr, phdr[n].p_memsz);
	} else {
		Elf64_Phdr *phdr = elf->phdr;
		/*遍历segment,传入参数*/
		for (n = 0; n < elf->e_phnum; n++)
			add_deps_from_segment(elf, phdr[n].p_type,
					      phdr[n].p_vaddr, phdr[n].p_memsz);
	}
}
static void add_deps_from_segment(struct ta_elf *elf, unsigned int type,
				  vaddr_t addr, size_t memsz)
{
	size_t dyn_entsize = 0;
	size_t num_dyns = 0;
	size_t n = 0;
	unsigned int tag = 0;
	size_t val = 0;
	TEE_UUID uuid = { };
	char *str_tab = NULL;
	size_t str_tab_sz = 0;
	/*先判断是否动是态链接的segment,不是则直接退出*/
	if (type != PT_DYNAMIC)
		return;

	check_phdr_in_range(elf, type, addr, memsz);

	if (elf->is_32bit)
		dyn_entsize = sizeof(Elf32_Dyn);
	else
		dyn_entsize = sizeof(Elf64_Dyn);

	assert(!(memsz % dyn_entsize));
	/*计算出dyn_section数量*/
	num_dyns = memsz / dyn_entsize;
	
	/*遍历Dynamic section成员,直到找到符号表地址和大小 */
	for (n = 0; n < num_dyns && !(str_tab && str_tab_sz); n++) {
		/*依次读取Dynamic section的tag和val*/
		read_dyn(elf, addr, n, &tag, &val);
		if (tag == DT_STRTAB)/*如果是符号表地址,则保存其地址*/
			str_tab = (char *)(val + elf->load_addr);
		else if (tag == DT_STRSZ)/*如果是大小,则保存其大小*/
			str_tab_sz = val;
	}
	check_range(elf, ".dynstr/STRTAB", str_tab, str_tab_sz);

	for (n = 0; n < num_dyns; n++) {
		/*依次读取Dynamic section的tag和val*/
		read_dyn(elf, addr, n, &tag, &val);
		if (tag != DT_NEEDED)/*找到动态链接库*/
			continue;
		if (val >= str_tab_sz)
			err(TEE_ERROR_BAD_FORMAT,
			    "Offset into .dynstr/STRTAB out of range");
		tee_uuid_from_str(&uuid, str_tab + val);
		queue_elf(&uuid);/*存入queue,便于后面加载*/
	}
}

函数结束,此函数就是寻找动态库,找到就将其uuid放入队列。以便将其从ree_fs加载。
在optee-os里,动态库被编译成了.ta文件,以optee_test里的os_test的TA为例。
在这里插入图片描述
os_test依赖os_test_lib,ffd2xxxx.ta就是os_test_lib编译出的TA文件。可以理解为,os_test调用了os_test_lib提供的接口,但是os_test_lib是以动态TA的形式加载的。

copy_section_headers

函数功能是将section拷贝到elf的结构体中。

static void copy_section_headers(struct ta_elf *elf)
{
	TEE_Result res = TEE_SUCCESS;
	size_t sz = 0;
	size_t offs = 0;
	/*计算出section header大小*/
	if (MUL_OVERFLOW(elf->e_shnum, elf->e_shentsize, &sz))
		err(TEE_ERROR_BAD_FORMAT, "Section headers size overflow");
	/*申请section header空间,此时shdr与TA的shdr不一样,是指section header*/
	elf->shdr = malloc(sz);
	if (!elf->shdr)
		err(TEE_ERROR_OUT_OF_MEMORY, "malloc");

	/*
	 * We're assuming that section headers comes after the load segments,
	 * but if it's a very small dynamically linked library the section
	 * headers can still end up (partially?) in the first mapped page.
	 */
	if (elf->e_shoff < SMALL_PAGE_SIZE) {
		assert(!elf->is_main);
		offs = MIN(SMALL_PAGE_SIZE - elf->e_shoff, sz);
		memcpy(elf->shdr, (void *)(elf->load_addr + elf->e_shoff),
		       offs);
	}

	if (offs < sz) {
		/*调用systemPTA,将section header拷贝到elf->shdr的空间,也就是加载到了申请的空间里*/
		res = sys_copy_from_ta_bin((uint8_t *)elf->shdr + offs,
					   sz - offs, elf->handle,
					   elf->e_shoff + offs);
		if (res)
			err(res, "sys_copy_from_ta_bin");
	}
}

save_systab

将符号表保存到elf结构体中,主要是做符号表参数检查

for (n = 0; n < elf->e_shnum; n++) {/*遍历所有的section header*/
	if (shdr[n].sh_type == SHT_DYNSYM) {/*找到动态链接符号表*/
    	e64_save_symtab(elf, n);/*保存符号表到elf结构体,做一些参数检查*/
        break;
    }
 }

close_handle

调用sys_close_ta_bin函数调用systemPTA进行释放资源的操作。
其还会调用一次open操作,ree_fs_ta_open。这次的目的是校验hash值。也就是前面讲到的。
ree_fs_ta_read里的最后部分代码

if (handle->offs == handle->nw_ta_size) {/*如果读取完成*/
		if (handle->shdr->img_type == SHDR_ENCRYPTED_TA) {
			/*
			 * Last read: time to finalize authenticated
			 * decryption.
			 */
			/*解密收尾工作*/
			res = tee_ta_decrypt_final(handle->enc_ctx,
						   handle->ehdr, NULL, NULL, 0);
			if (res != TEE_SUCCESS)
				return TEE_ERROR_SECURITY;
		}
		/*
		 * Last read: time to check if our digest matches the expected
		 * one (from the signed header)
		 */
		 /*加载所有segment后进行hash校验与shdr里的比较*/
		res = check_digest(handle);
		if (res != TEE_SUCCESS)
			return res;

		if (handle->bs_hdr)
			res = check_update_version(handle->bs_hdr);
	}

set_tls_offset

此函数设置TLS segment相关,暂时没有研究过。略过。

关于elf的加载,总的就是加载了其LOAD类型的segment到了安全内存。

后续流程

经过前面多次的函数调用后elf->load_addr此时就是TA除去头部的文件加载地址。
elf->head就是.ta_head section。是每个TA定义在user_ta_header.c里的。

elf->head = (struct ta_head *)elf->load_addr;
const struct ta_head ta_head __section(".ta_head") = {
	/* UUID, unique to each TA */
	.uuid = TA_UUID,
	/*
	 * According to GP Internal API, TA_FRAMEWORK_STACK_SIZE corresponds to
	 * the stack size used by the TA code itself and does not include stack
	 * space possibly used by the Trusted Core Framework.
	 * Hence, stack_size which is the size of the stack to use,
	 * must be enlarged
	 */
	.stack_size = TA_STACK_SIZE + TA_FRAMEWORK_STACK_SIZE,
	.flags = TA_FLAGS,
	/*
	 * The TA entry doesn't go via this field any longer, to be able to
	 * reliably check that an old TA isn't loaded set this field to a
	 * fixed value.
	 */
	.depr_entry = UINT64_MAX,
};

所以在接下来才会使用其ta_head里的stack_size去申请内存。

/*申请已经被初始化为0,大小为stack_size的安全内存*/
res = sys_map_zi(elf->head->stack_size, 0, &va, 0, 0);
if (res)
	err(res, "sys_map_zi stack");

if (elf->head->flags & ~TA_FLAGS_MASK)
	err(TEE_ERROR_BAD_FORMAT, "Invalid TA flags(s) %#"PRIx32,
		   elf->head->flags & ~TA_FLAGS_MASK);
/*将elf里的参数传递给函数入参*/
*ta_flags = elf->head->flags;
*sp = va + elf->head->stack_size;
ta_stack = va/*此处两次赋值是因为ta_stack全局变量用作打印*/;
ta_stack_size = elf->head->stack_size;

sp指针的地址就是这样算出来的。此时外部调用的arg参数就得到了需要的stack_ptr,arg->flag等参数。

/*
	 * Load binaries, ta_elf_load() may add external libraries to the
	 * list, so the loop will end when all the dependencies are
	 * satisfied.
	 */
	TAILQ_FOREACH(elf, &main_elf_queue, link)/*如果由动态链接的依赖库在这里加载*/
		ta_elf_load_dependency(elf, arg->is_32bit);

然后ldelf的任务就完成了,此时TA的镜像已经被全部加载到了不i同的内存中,然后带着这些参数返回到optee_os调用其的位置。arg参数会被保存在utc上下文里。

回到内核之后的流程

ldelf结束后,回到init_with_ldelf里,将ldelf返回的参数保存在utc里。

	utc->is_32bit = arg->is_32bit;
    utc->entry_func = arg->entry_func;
    utc->stack_ptr = arg->stack_ptr;
    utc->uctx.ctx.flags = arg->flags;
    utc->dump_entry_func = arg->dump_entry;
#ifdef CFG_FTRACE_SUPPORT
    utc->ftrace_entry_func = arg->ftrace_entry;
    sess->fbuf = arg->fbuf;
#endif
    utc->dl_entry_func = arg->dl_entry;

而utc里的link节点被放入了tee_ctxes链表里

TAILQ_INSERT_TAIL(&tee_ctxes, &utc->uctx.ctx, link);

可以调用此函数通过to_user_ta_ctx函数取出其地址。所以在后面的user_ta_enter函数里,会体现到。

static inline struct user_ta_ctx *to_user_ta_ctx(struct tee_ta_ctx *ctx)
{
    assert(is_user_ta_ctx(ctx));
    return container_of(ctx, struct user_ta_ctx, uctx.ctx);
}

接着返回后会回到tee_open_session里。
关键代码

/*ldelf就是在这里加载运行的*/
res = tee_ta_init_session(err, open_sessions, uuid, &s);

if (tee_ta_try_set_busy(ctx)) {
        set_invoke_timeout(s, cancel_req_to);
		/*调用注册的open_session函数*/
        res = ctx->ops->enter_open_session(s, param, err);
        tee_ta_clear_busy(ctx);
    }

因为是user_ta,所以调用的是user_ta_enter_open_session函数。最终调用到user_ta_enter函数。

static const struct tee_ta_ops user_ta_ops __rodata_unpaged = {
	.enter_open_session = user_ta_enter_open_session,
	.enter_invoke_cmd = user_ta_enter_invoke_cmd,
	.enter_close_session = user_ta_enter_close_session,
	.dump_state = user_ta_dump_state,
#ifdef CFG_FTRACE_SUPPORT
	.dump_ftrace = user_ta_dump_ftrace,
#endif
	.destroy = user_ta_ctx_destroy,
	.get_instance_id = user_ta_get_instance_id,
	.handle_svc = user_ta_handle_svc,
};

user_ta_enter函数关键代码

***
/*获取utc上下文,前面提到了*/
struct user_ta_ctx *utc = to_user_ta_ctx(session->ctx);
***
	usr_stack = utc->stack_ptr;/*设置栈指针*/
	usr_stack -= ROUNDUP(sizeof(struct utee_params), STACK_ALIGNMENT);
    usr_params = (struct utee_params *)usr_stack;
    init_utee_param(usr_params, param, param_va);
***
/*切换到用户模式,运行TA*/
res = thread_enter_user_mode(func, kaddr_to_uref(session),
                     (vaddr_t)usr_params, cmd, usr_stack,
                     utc->entry_func, utc->is_32bit,
                     &utc->uctx.ctx.panicked,
                     &utc->uctx.ctx.panic_code);

到这里,TA的加载和运行流程大致就完成了。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值