记一次Bug排查

2020-02-04  本文已影响0人  明翼

一 背景

我用c写的一个小程序发现内存泄露,内存泄露的原因比较明显,程序主要的功能实现对另外一个程序生成的tcp或udp流的处理,以插件的方式实现离线告警,mac告警等功能。

二 内存泄露排查

2.1 简单的top排查

想用简单的排查,先用top观察,用top -p pid观察内存占用情况:


top显示的内存占用

由于这个程序是采用插件模式开发,所以就一个个插件卸载,然后观察内存变化,最后在主框架内成功查到一个内存泄露点,解决了大的内存泄露问题,内存泄露点在:

    if (NULL == flow_json) return NULL;
    cJSON * json = cJSON_Parse(flow_json);
    cJSON * create = cJSON_GetObjectItem(json, "CREATE_TIME");
    cJSON * src_ip = cJSON_GetObjectItem(json, "SRC_IP");
    cJSON * dst_ip = cJSON_GetObjectItem(json, "DST_IP");
    cJSON * proto = cJSON_GetObjectItem(json, "PROTOCOL");
    cJSON * port = cJSON_GetObjectItem(json, "DST_PORT");

    if (create == NULL || src_ip == NULL 
        || dst_ip == NULL || proto == NULL
        || port == NULL) {
              //在异常返回的时候,没有销毁json对象,导致!!!!
        cJSON_Delete(json);
        return NULL;
    }

选项说明如下:

VIRT — 进程使用的虚拟内存总量,单位kb。VIRT=SWAP+RES
RES — 进程使用的、未被换出的物理内存大小,单位kb。RES=CODE+DATA
SHR — 共享内存大小,单位kb。比如一些so共享库。
%CPU — 上次更新到现在的CPU时间占用百分比
%MEM — 进程使用的物理内存百分比,手工计算了下,大概和RES内存大小差不多。
TIME+ — 进程使用的CPU时间总计,单位1/100秒

2.2 pidstat工具检查

解决了一个问题,是否还有其他内存泄露,继续从更细微处看内存是否存在泄露:
安装pidstat工具:

 yum install sysstat
pidstat检查

1)第一次批量处理文件,内存有稍微的增长,初期增长是正常的,因为涉及到新数据的部分需要保存到内存中;
2)再次将这批文件处理状态,更改为未处理状态,执行过程中内存占用变化如下:


pidstat检查2

从上图分析内存在增加后,降了下来,说明是正常的,处理完一批文件后,内存没有泄露。

三 程序停不掉问题排查

为了解决内存泄露,特意对程序进行了修改,通过文件来控制是否退出,如果有.exit文件,则程序自动退出,这样就可以方便地使用valgrind进行问题排查了。
在程序停止的时候发现一直停不了。本程序是多线程程序,可以通过多种办法进行排查:

  1. gdb排查
#在线调试程序
gdb -p pid
#查看线程数量
(gdb)info threads
#切换到xx线程
(gdb)thread xx
#线程堆栈查看
(gdb)where 

排查如下:


gdb查看多线程堆栈
  1. pstack排查
1. 通过pstree命令查看这个进程现在的线程情况。
2. 用pstack tid命令排查线程情况。

如下:


pstack排查

通过这两个简单的办法,查到了是清理日志的线程休眠的时间太长,导致线程无法停掉。
解决办法就比较简单,由于不是核心业务线程,直接kill杀掉这个线程即可。

四 valgrind 检测内存泄露

程序是定期跑的程序,对于这种程序不方便用valgrind检测,需要进行改造,上面说通过判断是否有文件方式来进行程序异步停止控制,代码实现很简单:

struct stat statbuf;
//检测退出文件是否存在,存在则删除文件,且设置退出标识
if (!lstat("./.exit", &statbuf)) {
      remove(".exit");
      g_run_flag = QUIT;
}

内存泄露valgrind的执行命令:

valgrind --leak-check=full --show-leak-kinds=all ./flow 

说明其中flow为本次出现bug的程序,通过了几次更改了些小的bug,基本把内存泄露解决。
检测如下:


内存检测结果

重点先看:

 LEAK SUMMARY:
==8920==    definitely lost: 0 bytes in 0 blocks
==8920==    indirectly lost: 0 bytes in 0 blocks
==8920==      possibly lost: 0 bytes in 0 blocks
==8920==    still reachable: 520 bytes in 9 blocks
==8920==         suppressed: 0 bytes in 0 blocks

definitely lost: 确定的内存泄露,需要立刻修复。申请的内存,没有释放,但是程序中的指针变量无法访问到这块内存,则是确定泄露。
indirectly lost: 间接的内存泄露,一般是结构体内部的成员或类里面成员的指针资源没释放,前面一种情况修复后,这种一般自动会消失。
possibly lost: 可能的内存泄露,一般也是需要修复的,谨防部分申请内存后,释放部分内存的情况。
still reachable: 可以访问,未丢失但是也未释放。一般程序程序结束时候,有些变量没有显示释放,如果是配置数据,可以忽略,因为这部分占的内存是不会增加的,随着程序停止,仍然会被OS回收。

还有一些其他错误,通过其他选项检测下,设置选项如下:

valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes  -v ./flow

检测到的信息如下:


其他错误

看下代码:


错误代码

通过提示来看tm变量可能未初始化造成的,初始化下:

struct tm tm = { 0 };
检测情况

五 总结

主要内存泄露的地方多是异常情况判断的时候,没有释放掉内存。所以编写代码的时候,申请内存和释放内存一起写,再配合valgrind等工具检测各种条件下的内存是否泄露。

上一篇 下一篇

猜你喜欢

热点阅读