linux如何定位内存泄漏,快速定位内存泄漏的套路(linux)

快速定位内存泄漏的套路(linux)

快速定位内存泄漏的套路(linux)

https://blog.csdn.net/xieyihua1994/article/details/105248362/

背景

偶然间发现一个模块挂掉了,并且没有生成core文件。这就让我很奇怪,因为一般如果是段错误导致程序挂掉,是会生成core文件的(我已经开启了coredump ulimit -c unlimited)。通过dmesg查看内核日志,发现是由于OOM kill机制导致的。如图:

c830a0bfb94f0e7c4f7d73e8492f2cd3.png

既然发现了问题就一定要解决。通过查阅资料以及分析log终于定位到了内存泄漏的代码部分。本章我会结合自己的理解,一步一步的带大家分析,希望能够帮助到大家。

什么是OOM kill 机制?

简单的说就是当你的内存不足时,linux 内核为了不影响所有进程的正常使用,会启动该机制。首先会依据一些条件(进程内存占用大小,进程运行的时间等,一般都是那些内存占用比较多的进程)选出bad process。将其kill,释放它占用的内存。

暴力分析法

如果对于整体代码比较熟悉时,出现了内存泄漏,我们是可以估摸出大概位置的。比如:昨天还没有内存泄漏,今天就有了。那么这个bug肯定是某某在今天commit的。只要查看一下log就能知道大概位置。之后通过注释法(依次注释接口),也能够很快的定位到问题代码行。

valgrind 工具

如果对代码不是很熟悉(我就是这种情况,代码是外包人员写的,现在交付不管了),那我们最好的方式就是引用一些工具了。 网上推荐的工具有很多,我在这里使用的是valgrind。该工具的功能强大:

memcheck :检测程序中的内存问题,如内存泄漏,越界,非法指针等

callgrind:检测程序代码的运行时间和调用过程,以及分析程序性能。

cachegrind:分析CPU的cache命中率,丢失率,用于进行代码优化。

helgrind:用于检测多线程程序中出现的竞争问题

Massif:堆栈分析器,它能测量程序在堆栈中使用了多少内存,告诉我们堆块,堆管理和栈的大小。

本篇主要从内存问题分析介绍,有时间我再研究一下其它功能。

案例分析 test.c:

#include

#include

#include

int main()

{

char * p = malloc(1024);

p=NULL;

return 0;

}1

2

3

4

5

6

7

8

9

上面的代码一眼就看出内存泄漏的问题。之后我们通过执行以下命令进行编译调试:

59280aed29a2648fbd023c29d0054dba.png

gcc test.c -g 其中-g是为了保留符号表,可以定位到代码中具体某一行。

valgrind --tool=memcheck --leak-check=full --show-leak-kinds=all --undef-value-errors=no --log-file=log ./a.out

tool=memcheck 表示检测内存问题。

leak-check=full 表示完全检测内存泄漏

–log-file=log 表示信息会输入到log文件中(有时文件内容比较多,这样方便分析)

再查看log文件,内容如下:(内容较少)

==9393== Memcheck, a memory error detector

==9393== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.

==9393== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info

==9393== Command: ./a.out

==9393== Parent PID: 90214

==9393==

==9393==

==9393== HEAP SUMMARY: //关键信息,表示你的程序内存泄漏的大小

==9393== in use at exit: 1,024 bytes in 1 blocks

==9393== total heap usage: 1 allocs, 0 frees, 1,024 bytes allocated

==9393==

==9393== 1,024 bytes in 1 blocks are definitely lost in loss record 1 of 1

==9393== at 0x4C29BC3: malloc (vg_replace_malloc.c:299)

==9393== by 0x40052E: main (test.c:6)

==9393==

==9393== LEAK SUMMARY:

==9393== definitely lost: 1,024 bytes in 1 blocks

==9393== indirectly lost: 0 bytes in 0 blocks

==9393== possibly lost: 0 bytes in 0 blocks

==9393== still reachable: 0 bytes in 0 blocks

==9393== suppressed: 0 bytes in 0 blocks

==9393==

==9393== For counts of detected and suppressed errors, rerun with: -v

==9393== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

分析:

由于程序简单,日志内容也比较少。如果log文件内容比较大,分析步骤也是一样的。

第一步:

搜索:HEAP SUMMARY关键字,表示当你程序结束时,内存泄漏的信息统计。很清晰的看到程序有1024个字节没有释放。之后的内容就是泄漏的详细信息(在代码的哪一行,以及泄漏多少字节)

通过log可以看到,在main函数中(test.c的第6行出现了内存泄漏)。大功告成!!!!

如果第一步就找到了泄露位置,那么证明你很幸运。当无法简单一目了然的分析时(log文件内容很多,待会上实际图),你可以参考第二步。

第二步:

搜索:LEAK SUMMARY关键字。表示内存泄漏的类型:

definitely lost:确定的内存泄漏,已经不能访问这块内存

indirectly lost:指向该内存的指针都位于内存泄露处

possibly lost:可能的内存泄露,仍然存在某个指针能够访问某快内存,但该指针指向的已经不是该内存首位置

still reachable:内存指针还在还有机会使用或者释放,指针指向的动态内存还没有被释放就退出了

当进程不会自动停止还能够测试吗?

答案是肯定的。刚开始我也怀疑,于是自己试了一试。只要你运行之后,通过ctrl+c停止,就可以了。同样会生成详细信息(勇于尝试)

实战

上面的列子比较简单,所以很容易分析。现在通过分析项目中的log文件,来加强巩固,先上图:

03fd3744d019030987a717f7d64cadc7.png

如图所示,实际工作中生成的log信息是很多的(8万多行)。

第一步:找HEAP SUMMARY关键字,如图:

295f2fd25ed21e358d806b06dd92f8f8.png

如图所示:大概知道当程序结束时大约还有165M的内存没有释放。并且泄漏的记录有58763条之多。

想要从中找到准确的内存泄漏的地方,还是很难的。

于是我建议执行第二步如图:

87d2666831e07cfb1d8ab79fa6ff0840.png

之后再执行valgrind --tool=memcheck --leak-check=full --show-leak-kinds=all --undef-value-errors=no --log-file=log-2 taskname调试,比如第一次调试,你让它运行10分钟,第二次让它运行30分钟。同样的环境,如果存在内存泄漏,那么肯定大小不一样。

第二次测试结果:

50e71b26a20c6b6ad24fce1540de6213.png

之后发现差距主要是在indireactly lost中,之后就在两个文件中搜索are indirectly lost in loss关键字。如图:

log-2文件:

5bf8f7756b84e99e71048a26bac39fee.png

log文件:

2d2c12f7663967ed076cd68a8f59020a.png

通过定位就很快的找到是video_task.cpp中349行的av_read_frame接口导致的内存泄漏。代码下:

349 if(av_read_frame(pFormatCtx, packet) >= 0)

350 {

351 timeout = 0;

352 if (packet->stream_index == videoindex) {

353 int got_frame = 0;

354

355 avcodec_decode_video2(pCodecCtx, pFrame,&got_frame, packet);

356 if (got_frame)

357 {

358 if (pFrame->key_frame)

359 {

360 int width = pFrame->width;

361 int height = pFrame->height;

362 tmp_img = cv::Mat::zeros( height*3/2, width, CV_8UC1 );

363 memcpy( tmp_img.data, pFrame->data[0], width*height );

364 memcpy( tmp_img.data + width*height, pFrame->data[1], width*height/4 );

365 memcpy( tmp_img.data + width*height*5/4, pFrame->data[2], width*height/4 );

366 cv::cvtColor( tmp_img, bgr, 101 );

367 set_image(bgr);

368 }

369 }

370 }

371 // av_packet_unref(packet);

372 }1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

由于对ffmpeg库的不熟悉,网上稍微一搜索,就找到了av_read_frame引起内存泄漏的相关博客,加上371行的av_packet_unref(packet);即可。

至此,内存泄漏的问题就解决了。希望能够帮助到你。

==================================== 杂谈 =====================================

问题

在这个分析和解决的过程中,刚开始我也有自己的一些疑问,不知道你是否和我一样,有不同见解的可以留言一起讨论一下:

通过log文件分析,为什么会存在那么多没有释放的内存?

归根而言,还是编码导致的。

如果编码足够规范,我觉得应该是可以避免的,但是想做到真的很难。

比如,你的项目中你需要引用一些模块。难道你会对每一个API的实现原理都去研究吗?实际上,我们只要能够实现功能就可以了。比如在ffmpeg库中avformat_find_stream_info接口其实也会产生内存泄漏(我看过很多的blog,基很少有人会去释放)但是为什么没有显现出来呢?那是因为在项目中它只会被执行一次。不会随着时间的推移,造成更大的损失。(当然原则上是不允许的)。所以也就容易被我们忽略。

在一些大厂中,一般会进行编译静态检测,这是一个非常好的方式,可以将bug尽早发现。

  • 2
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值