Shared Libraries of TA
Preface
OPTEE里有个宏叫做”CFG_ULIBS_SHARED”,由此联想到主流OS里动态链接库,这篇文章主要看这个宏有什么作用,和动态链接库有什么关联区别。
SoC Arch : ARMv8
OPTEE version: 4.0.0。
Content
ldelf
OPTEE里有个module叫做ldelf,它的主要功能包括open ta,parse ELF,load dependency(if any),map and relocate elf等。这个模块也挺有意思,它并不是link在OPTEE core里的,而是独立编译成可执行的elf文件,然后通过gen_ldelf_hex.py这个脚本生成ldelf_hex.c,然后以数组的形式编译进OPTEE core的。在做TA的open session的时候通过ldelf_load_ldelf load到tee memory中,然后通过function ldelf_init_with_ldelf进入user mode(SEL0 under ARMv8)把要load的TA准备好后返回OPTEE Core。今天提到的shared library就是在ldelf里处理的。ldelf可以单独作为一个话题写一篇文章了,先上张官网的图酝酿下。
Shared Libraries
OPTEE里的shared libraries主要是以下几个:
- libutils
提供标准c库函数。例如strncmp(),qsort()等。
UUID:71855bba-6055-4293-a63f-b0963a737360 - libutee
GPD TEE Internal Core API都包在这个库里。
UUID:4b3d937e-d57e-418b-8673-1c04f2420226 - libmbedtls
提供Mbed TLS library的支持。主要是各种crypto操作。
UUID:87bb6ae8-4b1d-49fe-9986-2b966132c309 - libdl
TA用这个库来支持动态链接库,类似于Linux中的dl module。 UUID:be807bbd-81e1-4dc4-bd99-3d363f240ece
这些lib在“CFG_ULIBS_SHARED”打开后在准备TA编译的stuff的时候,在export-ta_arm64/lib下(只考虑64bit TA)生成了4个TA和.so文件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
4b3d937e-d57e-418b-8673-1c04f2420226.elf -> libutee.so
4b3d937e-d57e-418b-8673-1c04f2420226.ta
71855bba-6055-4293-a63f-b0963a737360.elf -> libutils.so
71855bba-6055-4293-a63f-b0963a737360.ta
87bb6ae8-4b1d-49fe-9986-2b966132c309.elf -> libmbedtls.so
87bb6ae8-4b1d-49fe-9986-2b966132c309.ta
be807bbd-81e1-4dc4-bd99-3d363f240ece.elf -> libdl.so
be807bbd-81e1-4dc4-bd99-3d363f240ece.ta
libdl.a
libdl.so
libdl.stripped.so
libmbedtls.a
libmbedtls.so
libmbedtls.stripped.so
libutee.a
libutee.so
libutee.stripped.so
libutils.a
libutils.so
libutils.stripped.so
当然libxxx.a也还在,developer可以决定编译TA的时候是static link还是dynamic link。从code看,OPTEE同时支持static link的TA和dynamic link的TA。注意,对于in-tree TA,OPTEE默认是static link的(build-user-ta.mk)。
TA compile
在编译环节,把link时候的command line打印出来如下(以编译hello_world为例):
1
aarch64-linux-gnu-ld.bfd -e__ta_entry -pie -T ./ta.lds -Map=./8aaaf200-2450-11e4-abe2-0002a5d5c51b.map --sort-section=alignment -z max-page-size=4096 --as-needed --dynamic-list ./dyn_list ./hello_world_ta.o ./user_ta_header.o -L ./export-ta_arm64/lib --start-group -lutils -lutee -lmbedtls -ldl --end-group ./toolchain/aarch64/gcc-arm-aarch64-linux-gnu-8.3/bin/../lib/gcc/aarch64-linux-gnu/8.3.0/libgcc.a -lutils -o 8aaaf200-2450-11e4-abe2-0002a5d5c51b.elf
竟然发现link的command line竟然是一样的。哪developer究竟怎样决定编译TA的时候是static link还是dynamic link?感兴趣的从How to Make a Library中找答案吧。
objdump下看需要动态链接哪些库文件。咦,这里又出问题了,上面的ld command里没有这几个库啊,这串数字是什么?嘿嘿,继续从How to Make a Library中找答案吧。
1
2
3
Dynamic Section:
NEEDED 71855bba-6055-4293-a63f-b0963a737360
NEEDED 4b3d937e-d57e-418b-8673-1c04f2420226
TA loading
之前有提到TA主要由ldelf来load,它的入口函数从start_a64.S开始。call stack如下(注释只加了和本文相关的部分):
- ldelf
- ta_elf_load_main
- load_main
- init_elf
- sys_open_ta_bin //ldelf的syscall到ta_stores的open函数,把binary从支持的storage里读取出来。
- sys_map_ta_bin
- map_segments
- populate_segments
- add_dependencies //从dynamic section里把依赖的库的UUID记在列表里
- copy_section_headers
- …….
- init_elf
- load_main
- ta_elf_load_dependency //依次把依赖的UUID对应的TA load进来,过程和load_main类似
- ta_elf_relocate
- ta_elf_finalize_mappings
- ta_elf_finalize_load_main
- sys_return_cleanup
- ta_elf_load_main
关于ELF部分和relocate部分不打算做细节的描述,网上有不少不错的文章,有兴趣的可以搜索学习下。
Result Analysis
Size of TA
众所周知,主流OS中动态链接库的最大好处之一就是节省空间。以hello world为例,看TA size的变化有多大。
Static:
1
-rw-rw-r-- 1 86664 8aaaf200-2450-11e4-abe2-0002a5d5c51b.ta
Dynamic:
1
-rw-r--r-- 1 12600 8aaaf200-2450-11e4-abe2-0002a5d5c51b.ta
TA的size确实变小了,但来看下几个lib的大小:
1
2
3
4
-rw-rw-r-- 1 110092 export-ta_arm64/lib/4b3d937e-d57e-418b-8673-1c04f2420226.ta //libutee
-rw-rw-r-- 1 34924 export-ta_arm64/lib/71855bba-6055-4293-a63f-b0963a737360.ta //libutil
-rw-rw-r-- 1 316212 export-ta_arm64/lib/87bb6ae8-4b1d-49fe-9986-2b966132c309.ta //libmbedtls
-rw-rw-r-- 1 5796 export-ta_arm64/lib/be807bbd-81e1-4dc4-bd99-3d363f240ece.ta //libdl
这几个lib加起来有接近500KB,而hello_world TA只减小了70KB。不过hello_world TA只依赖libutee和libutil,减小的size不算显著,如果依赖libmbedtls,对size的影响会比较大,这里随机选了xtest里crypt TA做个比较。
Static:
1
-rw-rw-r-- 1 361816 optee_test/ta/crypt/cb3e5ba0-adf1-11e0-998b-0002a5d5c51b.ta
Dynamic:
1
-rw-r--r-- 1 78424 optee_test/ta/crypt/cb3e5ba0-adf1-11e0-998b-0002a5d5c51b.ta
所以为了节省存储空间而打开这个宏的话,这个帐要仔细算一下。
Loading Time
动态链接库的另一大可能的好处是减少loading的时间。application的size变小,loading时间减少,而动态链接库有可能已经loaded放在memory里了,这样总时间变少。但这对OPTEE TA有效吗?
如上ldelf中TA loading的流程,ldelf并不会直接load TA,它会syscall回去调用ta store的open函数来拿到TA。最常用的TA store就是ree fs ta。来看下它会不会缓冲曾经加载过的TA。
以下是ree fs ta store的注册REGISTER_TA_STORE:
1
2
3
4
5
6
7
8
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回ree找tee_supplicant要binary,这里是没有缓冲的。但这里注意到有另外一个宏“CFG_REE_FS_TA_BUFFERED”,初始以为是把曾经加载过的TA缓冲起来,仔细阅读代码并不是。这里还找到了这个宏的说明,也确认不是这个功能。
1
2
3
4
5
# - If CFG_REE_FS_TA_BUFFERED=y: load TA binary into a temporary buffer in the
# "Secure DDR" pool, check the signature, then process the file only if it is
# valid.
# - If disabled: hash the binaries as they are being processed and verify the
# signature as a last step.
如上,OPTEE的shared library这个功能有可能非但不会省时间,还会增加时间。
Applicable Scenario
前面从size和loading time来分析了OPTEE的shared library功能,适用场景也还是以下两点:
- 节省存储空间。
想要达到节省存储空间的目的还需要精打细算,和TA的个数和依赖的库相关,毕竟application的体量不能和诸如Linux这样的OS比较。 - 提高性能。
对于追求性能,并且memory(特别是secure memory)资源比较宽裕的开发者,OPTEE original code并不能直接达到目的,还需要做一定的改造。下面章节提出了一种可能方法。
Possible Improvement
通过分析ree fs ta的ree_fs_ta_open函数知道,它并不会对曾经load过的TA进行缓冲,而“CFG_REE_FS_TA_BUFFERED”增加的功能给了一定启示,可以申请一块buffer来存储TA,在调用ta store的open函数之前查找缓冲区,如果已经缓冲,则直接返回TA,如果没有,open函数读取ta,并添加在缓冲区。
考虑到有些TA只load一次就不会在load,这里可以做进一步的优化,只缓冲shared library,可以节省一些memory。
写到这里,忽然产生一个想法,把shared library(已经编译为TA)当成early TA来处理,这样还少了siganature check和decryption,岂不是更简单高效?这个想法实践过后再来update吧。
Update
Update来了,标准OPTEE不可行,稍做hack可行。Early TA of OPTEE
Summary
OPTEE的shared library并不像Linux那样完备,适用场景也有一定的限制,看起来可用性不是很高。大家酌情使用吧。