使用gdb分析coredump文件排查srs偶发内存泄漏问题

3,831 阅读5分钟

内存泄漏

  • 正常情况内存消耗比较平稳,但是异常后如下图可以看到内存在几个小时内内存消耗殆尽导致操作系统直接kill掉srs进程

image.png

排查难点

  • 内存泄漏是偶发的这是排查的难点,如果是必现可以借助一些成熟的工具比如:Valgrind,memleak, mtrace等等可以很容易定位到问题,并且这些工具网上有铺天盖地的文档,也很容易使用,这里不再赘述。

排查过程

使用工具排查

  • 不知道具体原因的情况下所以首先还是使用Valgrind和memleak等成熟的工具排查但是并没有发现明显的内存泄漏问题,放弃使用。

分析内存镜像

分析core文件

  • gdb attach {pid}
  • 执行(gdb)gcore命令dump生成core文件,注意会短暂的夯死主进程。
  • pmap {pid}获取内存分布情况如下图可以看到内存占用比较大的heap区,也可以通过cat /proc/{pid}/maps查看内存分布但是不如pmap直观的看到虚拟内存的分布情况。
  • image.png
  • 用gdb打开core文件,将0000000005c2b000起始的内存内容输出到文件中
  • gdb -c {core文件} {your_application}
  • (gdb)set height 0
  • (gdb)set logging on
  • (gdb) x/612205568a 0x0000000005c2b000
  • x/612205568a 格式说明:
    • 612205568=4782856*1024/8 (因为是64 bit所以要/8byte)
    • a为gdb输出其中一种格式(可以参看gdb x现象内存数据根式说明) blog.csdn.net/yasi_xi/art…
    • 最后就是分析上面生成的gdb.txt
    • image.png
    • 直接看不好看所以格式化一下cat gdb.txt|c++filt >demo.txt 可以看到就是srs的class类了
    • image.png
    • 通过统计demo.txt 中各个对象的个数来判断是否是该对象存在内存泄漏,存在比较多个对象有可能是内存泄漏点,当然也有可能实际上你的程序就是这么多正常的对象,所以需要理智的判断。
      • cat demo.txt|awk 'BEGIN{stat[""]=0}{stat[$5]++}END{for(i in stat) print i"="stat[i]}'
    • 通过以上方式确实发现内存泄漏问题,SrsStatisticStream对象在内存中比较多,但是结合代码分析泄漏对象占用的内存比较小,不可能在几个小时内耗尽5G内存,分析结果不符合线上的情况。

分析现象结合业务场景提出猜测

  • 根据内存泄漏的现象,首先想到的是可能因为某个流造成的问题
  • 结合现象发现内存消耗比较严重,可能是内存中存在音视频包造成的问题。
  • 结合srs源码和占内存比较多的业务场景,有2个地方比较耗内存
    • gop cache但是碰到keyframe后会清理之前缓存的音视频包,应该不是这个问题。
    • flv回源会有一个SrsMessageQueue存放音视频包,但有shrink的逻辑会释放内存,只有一种情况会占用内存那就是不发生shrink同时dts回退回造成内存泄漏(有可能)因为下行cdn回源可以存在网络问题,第三方流有可能存在dts回退的情况。
    • 模拟flv回源卡顿,同时让dts回退确实内存会暴涨,猜测是该问题,要是能找到线上内存中的SrsMessageQueue对象会进一步确认问题,带着问题继续分析coredump文件。

虚拟内存分布

  • image.png

c++虚函数表

  • 多态的实现原理
  • 通过demo学习一下虚函数表
#include <iostream>
using namespace std;
class A
{
public:
    int i=20001111; //16进制为1313157
    virtual void func() {}
    virtual void func2() {}
};
class B : public A
{


public:
   int j=10001111;//16进制为989ad7
   void func();
};

void B::func()
{
    cout<<"===B==="<<endl;
}

int main()
{
    cout << sizeof(A) << ", " << sizeof(B); 
    B* b=new B();
    b->func();
    return 0;
}
  • 通过gdb设置断点调试以上简单程序
    • 编译g++ -g -o demo demo.cpp
    • gdb demo
    • 断点调试输出b指针
    • image.png
    • image.png
    • 获取B对象this指针(堆地址)通过p命令获取整个B对象
      • image.png
    • 通过以上可以知道vtable地址就是this指针

接着分析core文件

  • 有了以上知识铺垫,可以很容易分析heap内存获取SrsMessageQueue对象
  • core文件比较大怎么快速获取对象
import gdb, traceback;
class heap(gdb.Command):
    def __init__(self):
        super(heap, self).__init__("all-heap", gdb.COMMAND_DATA)

    def invoke(self, arg, from_tty):
        try: 
            # 从以上pmap可以找到比较大的堆内存的起始地址为0x0000000005c2b000
            next = "0x0000000005c2b000";
            while True:
                #name1 = gdb.execute('x/2xa %s' % (next), to_string=True)
                #print('name2 %s' % (name1))
                name = gdb.execute('x/2xa %s'%(next), to_string=True).split('\t')[1].strip()
                #print('name: %s,%s' % (name,next))
                if name.find('SrsMessageQueue') >0 :
                    print('SrsMessageQueue find %s'%(next))
                    print('SrsMessageQueue find ======%s' % (gdb.execute('x/2xa %s'%(next), to_string=True)))
                next = gdb.parse_and_eval('%s + 16' % (next)).__str__()
        except gdb.error:
            print("error")
            return

heap()

- (gdb) source heap.py
- (gdb) all-heap
- 通过脚本获取SrsMessageQueue对象this指针

image.png 通过SrsMessageQueue->SrsConnection->SrsConsumer->SrsSource->SrsRequest找到出问题流Id

image.png

解决

  • 问题已经定位原因是flv拉流卡顿,同时源流发生dts回退造成音视频包在内存堆积
  • 解决
    • image.png

总结

  • Valgrind,memleak, mtrace等工具解决内存泄漏,如果可以复现可以很好定位,如果偶发的情况没有用武之地。
  • 偶发内存泄漏问题还是需结合业务场景分析源代码找出问题。

收获

  • 了解了内存布局,以及对c++虚函数表的理解。

参考