问题:
使用x264进行视频编码,利用xcode提供的leak检查工具发现有一处x264_malloc的内存泄漏点,蜜汁泄漏,无从查起。
现象描述:
假设编码参数qmin = n, qmax = m, 则共有(m-n+1)处x264_malloc泄漏。多次反复试验发现此泄漏也不是每次都出现,Google之后发现也有同学遇到过这个问题,但并没有给出具体解决办法或结论,参考链接https://ask.csdn.net/questions/258684。泄漏点如下图:
问题定位:
下面就是艰难的探索之旅。。。。。
首先,因为是以静态库的方式引入x264,泄漏点没有具体调用栈,只可知是x264_malloc泄漏,难以追溯。所以第一步是导入x264源码并再次使用xcode的内存检测工具测试,这样调用栈就很清晰了:
出于偷懒那就继续换个关键词Goolge吧,能不自己动手就不自己动手。搜索发现提交历史里x264_analyse_init_costs曾经确实有泄漏,但邮件列表显示2016年已经有fix记录了,参考https://mailman.videolan.org/pipermail/x264-devel/2016-January/011530.html 。赶紧查看自己的x264源码有没有这次提交,很遗憾,git show 5c6570495f8f1c716b294aee1430d8766a4beb9c 确实显示这个fix已经上master分支了。至此,只好自己看x264源码了,下面就是不知所以的下x264源码了,咬牙看吧:
根据调用栈(x264_analyse_init_costs ---> init_costs ---> CHECKED_MALLOC ---> x264_malloc)提示,最终泄漏点代码在这里:
int x264_analyse_init_costs( x264_t *h )
{
int mv_range = h->param.analyse.i_mv_range << PARAM_INTERLACED;
....
....
for( int qp = X264_MIN( h->param.rc.i_qp_min, QP_MAX_SPEC ); qp <= h->param.rc.i_qp_max; qp++ )
if( init_costs( h, logs, qp ) )
goto fail;
....
....
}
static int init_costs( x264_t *h, float *logs, int qp )
{
if( h->cost_mv[qp] )
return 0;
int mv_range = h->param.analyse.i_mv_range << PARAM_INTERLACED;
....
CHECKED_MALLOC( h->cost_mv[qp], (4*4*mv_range + 1) * sizeof(uint16_t) );
h->cost_mv[qp] += 2*4*mv_range;
....
....
}
刚刚好和现象一致,for循环h->param.rc.i_qp_max - h->param.rc.i_qp_min + 1次,每次CHECKED_MALLOC来分配内存,循环次数和检测到的泄漏次数一致。
找到了分配内存的地方,接下来就得找释放内存的地方,既然有x264_analyse_init_costs,那总该有个相关的free/release啥的吧,又是刚刚好,x264_analyse_init_costs函数下边就是x264_analyse_free_costs函数:
void x264_analyse_free_costs( x264_t *h )
{
int mv_range = h->param.analyse.i_mv_range << PARAM_INTERLACED;
for( int i = 0; i < QP_MAX+1; i++ )
{
if( h->cost_mv[i] )
x264_free( h->cost_mv[i] - 2*4*mv_range );
for( int j = 0; j < 4; j++ )
{
if( h->cost_mv_fpel[i][j] )
x264_free( h->cost_mv_fpel[i][j] - 2*mv_range );
}
}
}
如上,如果调用了x264_analyse_free_costs,理应不会泄露啊,但诡异的地方是init_costs里mallc后又对分配的内存自增了:
h->cost_mv[qp] += 2*4*mv_range;
而x264_analyse_free_costs进行free时又对内存指针做了个减法:
x264_free( h->cost_mv[i] - 2*4*mv_range );
所以如果cost_mv[i]没有在其他地方被修改,而且mv_range也没有被修改,那么应该不会有泄漏才对。况且如果mv_range被修改的话极可能导致野指针崩溃,x264的编码者不至于傻到这水平。那到底咋回事呢?最简单暴力的方法就是添加日志呗,看看指针在mallc和free时地址是不是一致对应的。加日志如下:
printf("init cost_mv[%d]=%p,mv_range=%d\n",qp,h->cost_mv[qp],mv_range);//加在CHECKED_MALLOC后
printf("free cost_mv[%d]=%p mv_range=%d\n",i,(h->cost_mv[i] - 2*4*mv_range),mv_range);//加在x264_free(....)前;
跑起来得到的日志是:
init cost_mv[0]=0x107926040,mv_range=256
init cost_mv[1]=0x107928240,mv_range=256
init cost_mv[2]=0x10792a440,mv_range=256
init cost_mv[3]=0x10792c640,mv_range=256
......
init cost_mv[69]=0x10799d040,mv_range=256
free cost_mv[0]=0x107926040 mv_range=256
free cost_mv[1]=0x107928240 mv_range=256
free cost_mv[2]=0x10792a440 mv_range=256
free cost_mv[3]=0x10792c640 mv_range=256
......
free cost_mv[69]=0x10799d040 mv_range=256
日志里看出malloc和free的地址一一对应,事实上这里并没有泄露。只要调了x264_analyse_init_costs之后确保调用了x264_analyse_free_costs,那么内存泄漏不应该存在才对。而根据调用关系,x264_analyse_init_costs是在avcodec_open2里被调用,x264_analyse_free_costs是在avcodec_close被调用,也就是说只要avcodec_open2只后avcodec_close正确调用,应该不存在内存泄漏。
日志已经告诉我其实并没有内存泄漏,那就是xcode工具有问题喽,难道工具不是根据mallc和free的统计来检测泄漏的?为了验证这个猜想,我在avcodec_open2下一行加了一句usleep(1000000000),让代码等很久看看xcode内存工具的行为。现象是sleep的过程中就已经报告70处x264_malloc泄漏了,此时这些指针的生命周期还没结束呢,xcode根本就没有等到free检查就是说泄露了!!猜想很可能和h->cost_mv[qp] += 2*4*mv_range这个骚操作有关,认为指向malloc出来内存的指针在赋值后立即自增指向别处了,刚刚malloc出的内存便无人认领,就认定泄漏了,而实际上这些malloc的内存在x264_analyse_free_costs时又通过- 2*4*mv_range的骚操作捡回来。
结论:
只要正确调用了avcodec_close,内存泄漏并不存在,xcode内存检测有bug