TEE 严重内存泄漏事件

一次压测循环播放几个APK里面DRM视频1小时后会发生失败,现象是TA没有load 成功 出错log如下

[ 2276.030861] [TEE] D/TC:? 00 tee_ta_init_session_with_context:583 Re-open TA 37eb0e02-c43d-48a8-a129-a95dd3d43929
[ 2276.030864] [TEE] E/TC:1 00 print_oom:200 Memory allocation failed: size 7689 context 0x24b62694
[ 2276.030868] [TEE] E/TC:1 00 TEE load address @ 0x24b00000
[ 2276.030871] [TEE] E/TC:1 00 Call stack:
[ 2276.030874] [TEE] E/TC:1 00  0x24b0a149
[ 2276.030876] [TEE] E/TC:1 00  0x24b3e2f7
[ 2276.030879] [TEE] E/TC:1 00  0x24b3e9c5
[ 2276.030882] [TEE] E/TC:1 00  0x24b1c8cb
[ 2276.030885] [TEE] E/TC:1 00  0x24b10c65
[ 2276.030887] [TEE] E/TC:1 00  0x24b0edc0
[ 2276.030891] [TEE] D/TC:? 00 tee_ta_close_session:490 csess 0x24b78bf8 id 5
[ 2276.030894] [TEE] D/TC:? 00 tee_ta_close_session:509 Destroy session

可以看到, print_oom 函数报错,这个函数调用一般是在malloc和calloc的时候,调用bget发现内存不足无法提供,于是报出这个错误 。

最开始见到这个bug的时候我们 并不认为很难解决,因为按照经验之谈,基本上是ta stack 或者 heap size 太大, 又经过长时间的open close 导致ta ram 存在内存碎片,无法正常加载

以上情况,我们通常有2种解决方案:

  • 禁止ta 频繁 open close, 一旦open 成功就不让他关闭,一直占着这块内存.
  • 增加CFG_RSV_SIZE  通过增大bl32 整体内存来扩大 ta ram, 这样可以在内存碎片比较多的情况下 还能load ta.

但是事实证明我们想的太简单了, 我们跟踪调用栈,发现这个print_oom 使用者仅仅想申请一块4byte的数据就失败了,就算有内存碎片也是影响申请大块内存,除非系统已经完全没有内存了才会导致4byte都无法成功申请。

我们制定了如下测试手段检查内存使用情况:

  • 检查单个APK播放drm视频是否会有泄漏
  • 检查单个APK切台播放drm视频是否有泄漏
  • 一次完整循环两个APK播放drm视频是否会有泄漏

很幸运,我们在检查到第二个的时候, 发现livetv 切台,每次切台泄漏内存固定28+32 byte

在这里分享一下 tee 的查内存泄漏的手段 :

tee 内置了内存使用记录函数 

void mdbg_check(int bufdump)
{       
        gen_mdbg_check(&malloc_ctx, bufdump);
}

我们仅仅需要再合适的时候调用tee执行 mdbg_check(1)  , 就会列出来所有的 执行过malloc ,calloc, realloc的 函数

如下就是一部分dump出来的结果

[57288.043962@2]  [TEE] I/TC:3 00 gen_mdbg_check:639 count: 10, vaddr: 0x32efe1e8, buffer: 32 bytes core/arch/arm/mm/mobj.c:168
[57288.045380@2]  [TEE] I/TC:3 00 gen_mdbg_check:639 count: 11, vaddr: 0x32efe2d0, buffer: 32 bytes core/arch/arm/mm/mobj.c:168
[57288.046809@2]  [TEE] I/TC:3 00 gen_mdbg_check:639 count: 12, vaddr: 0x32efe310, buffer: 28 bytes core/arch/arm/mm/tee_mmu.c:368
[57288.048187@2]  [TEE] I/TC:3 00 gen_mdbg_check:639 count: 13, vaddr: 0x32efe348, buffer: 28 bytes core/arch/arm/mm/tee_mmu.c:257
[57288.049616@2]  [TEE] I/TC:3 00 gen_mdbg_check:639 count: 14, vaddr: 0x32efe3b8, buffer: 32 bytes core/arch/arm/mm/mobj.c:168
[57288.051035@2]  [TEE] I/TC:3 00 gen_mdbg_check:639 count: 15, vaddr: 0x32efe3f8, buffer: 32 bytes core/arch/arm/mm/mobj.c:168
[57288.052409@2]  [TEE] I/TC:3 00 gen_mdbg_check:639 count: 16, vaddr: 0x32efe4a0, buffer: 32 bytes core/arch/arm/mm/mobj.c:168
[57288.053806@2]  [TEE] I/TC:3 00 gen_mdbg_check:639 count: 17, vaddr: 0x32efe4e0, buffer: 32 bytes core/arch/arm/mm/mobj.c:168

我们就是根据count 值的增加来判断出来 每次都是泄漏28+32 byte

于是根据每次增加的具体api ,去反推到具体的调用的api ,发现是mobj_phys_alloc api, 那么再继续反推发现是tee_tvp_vdec_input_buf_map 函数 ,于是 判断应该是tee_tvp_vdec_input_buf_unmap  调用次数和map的次数不匹配,导致这个 obj没有释放 。

同时情形的是,我们在公版上一样复现到了这个bug,这就容易排查了, 而且公版有个好处,就是debug手段比较多,还能打印出来每个ta的region信息,
user_mode_ctx_print_mappings 函数是tee提供的可以打印ta的region函数

大概如下

[57301.908865@1]  [TEE] E/TC:? 00 Status of user TA((null) 37eb0e02-c43d-48a8-a129-a95dd3d43929) (0x32f04e40)
[57301.910066@1]  [TEE] E/TC:? 00 Arch: arm
[57301.910586@1]  [TEE] E/TC:? 00 Flags: SINGLE_INSTANCE|MULTI_SESSION|CACHE_MAINTENANCE
[57301.911530@1]  [TEE] E/TC:? 00 Sessions List:
[57301.912071@1]  [TEE] E/TC:? 00 0x32f05080
[57301.912580@1]  [TEE] E/TC:? 00 Stack Ptr: 0x118800, Size: 0x9000, Free: 0x7e20
[57301.913479@1]  [TEE] E/TC:? 00 Memory Used: 0x402000
[57301.914096@1]  [TEE] E/TC:? 00 Memory List:
[57301.914662@1]  [TEE] E/TC:? 00  region  0: va 0x00100000 pa 0x05304000 size 0x002000 flags ---R-X
[57301.915722@1]  [TEE] E/TC:? 00  region  1: va 0x00103000 pa 0x05595000 size 0x002000 flags rw-RW-
[57301.916827@1]  [TEE] E/TC:? 00  region  2: va 0x00105000 pa 0x05597000 size 0x006000 flags r-x---
[57301.918324@1]  [TEE] E/TC:? 00  region  3: va 0x0010b000 pa 0x0559d000 size 0x001000 flags rw-RW-
[57301.919221@1]  [TEE] E/TC:? 00  region  4: va 0x0010c000 pa 0x0559e000 size 0x003000 flags rw-RW-
[57301.920744@1]  [TEE] E/TC:? 00  region  5: va 0x0010f000 pa 0x055a1000 size 0x001000 flags r--R--
[57301.921888@1]  [TEE] E/TC:? 00  region  6: va 0x00110000 pa 0x055a2000 size 0x009000 flags rw-RW-
[57301.922352@1]  [TEE] E/TC:? 00  region  7: va 0x00178000 pa 0x05ab5000 size 0x028000 flags r-xR--
[57301.923508@1]  [TEE] E/TC:? 00  region  8: va 0x001a0000 pa 0x05add000 size 0x3c4000 flags rw-RW-

一般来说一个ta的region 也就是在8个左右, 当他去map别的地址的时候 就会在region多增加一个region, 那也是合理的数量,而且我们版本只能让ta去map一个段

那就是说ta 最多就是9个region。 

但是我们发现了出问题的ta如下:在一次完整切换后,region增加几十个, 这很不正常,甚至最后没有内存的时候 region达到了800多个。

不过我们松了一口气,这肯定不是我们tee的问题,而是上层忘了释放或者释放失败

[57299.745789@2]  [TEE] E/TC:? 00 Status of user TA((null) acbe4b66-6e40-46dc-a2fe-a576922cf170) (0x32f05560)
[57299.747022@2]  [TEE] E/TC:? 00 Arch: arm
[57299.747479@2]  [TEE] E/TC:? 00 Flags: SINGLE_INSTANCE|MULTI_SESSION|CACHE_MAINTENANCE
[57299.748463@2]  [TEE] E/TC:? 00 Sessions List:
[57299.749416@3]  [TEE] E/TC:? 00 0x32f05708
[57299.749505@3]  [TEE] E/TC:? 00 0x32f04928
[57299.750258@3]  [TEE] E/TC:? 00 Stack Ptr: 0x118800, Size: 0x9000, Free: 0x7b20
[57299.751000@3]  [TEE] E/TC:? 00 Memory Used: 0x4f0000
[57299.751532@3]  [TEE] E/TC:? 00 Memory List:
[57299.752050@3]  [TEE] E/TC:? 00  region  0: va 0x00100000 pa 0x05304000 size 0x002000 flags ---R-X
[57299.753156@3]  [TEE] E/TC:? 00  region  1: va 0x00103000 pa 0x05580000 size 0x002000 flags rw-RW-
[57299.754330@3]  [TEE] E/TC:? 00  region  2: va 0x00105000 pa 0x05582000 size 0x006000 flags r-x---
[57299.755452@3]  [TEE] E/TC:? 00  region  3: va 0x0010b000 pa 0x05588000 size 0x001000 flags rw-RW-
[57299.756472@3]  [TEE] E/TC:? 00  region  4: va 0x0010c000 pa 0x05589000 size 0x003000 flags rw-RW-
[57299.757637@3]  [TEE] E/TC:? 00  region  5: va 0x0010f000 pa 0x055af000 size 0x001000 flags r--R--
[57299.758748@3]  [TEE] E/TC:? 00  region  6: va 0x00110000 pa 0x0558c000 size 0x009000 flags rw-RW-
[57299.759787@3]  [TEE] E/TC:? 00  region  7: va 0x00119000 pa 0x5a399000 size 0x001000 flags rw-RW-
[57299.760938@3]  [TEE] E/TC:? 00  region  8: va 0x0011a000 pa 0x5a31a000 size 0x001000 flags rw-RW-
[57299.761997@3]  [TEE] E/TC:? 00  region  9: va 0x0011b000 pa 0x5a10c000 size 0x001000 flags rw-RW-
[57299.763184@3]  [TEE] E/TC:? 00  region 10: va 0x0011d000 pa 0x5a309000 size 0x001000 flags rw-RW-
[57299.764208@3]  [TEE] E/TC:? 00  region 11: va 0x0011e000 pa 0x5a10a000 size 0x001000 flags rw-RW-
[57299.765363@3]  [TEE] E/TC:? 00  region 12: va 0x0011f000 pa 0x5a0d5000 size 0x004000 flags rw-RW-
[57299.766417@3]  [TEE] E/TC:? 00  region 13: va 0x00123000 pa 0x5a308000 size 0x001000 flags rw-RW-
[57300.798649@3]  [TEE] E/TC:? 00  region 14: va 0x00124000 pa 0x5a31c000 size 0x001000 flags rw-RW-
[57300.799354@3]  [TEE] E/TC:? 00  region 15: va 0x00125000 pa 0x5a0b9000 size 0x001000 flags rw-RW-
[57300.801060@1]  [TEE] E/TC:? 00  region 16: va 0x00126000 pa 0x5a37e000 size 0x001000 flags rw-RW-
[57300.801554@1]  [TEE] E/TC:? 00  region 17: va 0x00129000 pa 0x5a380000 size 0x001000 flags rw-RW-
[57300.802700@1]  [TEE] E/TC:? 00  region 18: va 0x0012a000 pa 0x5a37c000 size 0x001000 flags rw-RW-
[57300.803817@1]  [TEE] E/TC:? 00  region 19: va 0x0012b000 pa 0x5a31b000 size 0x002000 flags rw-RW-
[57300.804868@1]  [TEE] E/TC:? 00  region 20: va 0x0012d000 pa 0x5a37f000 size 0x002000 flags rw-RW-
[57300.805973@1]  [TEE] E/TC:? 00  region 21: va 0x00132000 pa 0x5a2ee000 size 0x002000 flags rw-RW-

 

首先我们就把问题转出去,说明原因,让别的teams去排查,结果别的teams  好久找不到原因,甚至努力想把正常的情况的log截取出来,甩锅给我们,这个时候我们只能拼命反击

但是我们仅仅从region增加来看,有可能会被人反咬一口说我们unmap函数失败了,确实我们也奇怪这个事情,为何region会不断地增加 。

 

我们在unmap加上了log,发现有出错提前返回了, 但是原因让我们很奇怪,为何传入的pa 和 size 再已经map的list找不到呢,因为我们打印了所有的list 列表,没有发现能够和当前匹配的region

开始我们怀疑是不是上层传错了,结果我们在log里面向上翻找,发现map 函数确实返回成功,但是在下一次调用unmap 的时候 ,已经map的列表神奇的不见了。

确实这让我们瞬间以为bug又回到我们这边来了,压力巨大,然后突然leader一句话, 正常的DRM调用流程应该是 open_channel -> map address -> unmap address -> close_channel .

会不会是 上层调用顺序错了,open_channel -> map address ->close_channel -> unmap address . 这样就会导致channel丢失导致找不到了已经map 的region。 从而导致unmap失败。

我们可以在open_channel 和 close_channel 加上log,看看是不是close_channel 顺序在unmap之前。

 

这个分析思路瞬间给我们一个巨大的希望,最后的实验结果果然证实了leader的猜测 ,由此真相大白。我们找出了铁证,断定是上层api 因为是多进程不能保证顺序调用导致 我们tee内存泄露。

 

当然最后更 狗血的是,其实后续的版本已经解决了这个问题,但是项目上面的人明知道ta更新过 但是却没有合入,导致我们辛苦了这么久。更辛苦了QA同事连续抓了100多份log。

当然我们自一开始就预感到不是我们的问题导致的,因为tee 本身已经存在多年,经过无数的压力测试都不会有问题,只是我们苦于没有找到证据。

不过也通过了这个bug让我对tee ta的map机制有了更深的理解 ,为了分析这个bug,我们增强了debug的功能,这个对以后很有帮助。


注:世界上的bug,2%是自己引起的,98%的是别人引起的。

完结,撒花

 

posted @ 2024-05-29 14:14  颜小雀  阅读(6)  评论(0编辑  收藏  举报