排查内存泄漏的方法

内存泄漏是怎么产生的呢?

在底层申请内存去读写,总会有忘记释放内存,或是逻辑错误,导致未释放内存,亦或是释放内存的消息被堵塞、被覆盖,导致内存未被释放。

总之,总会有奇奇怪怪的场景导致未释放内存,等到操作很多很多次之后,积多成疾,内存不够了,就会内存泄漏,轻则功能缺失,重则系统崩溃。内存作为系统的存储大脑,作为程序员,不能不察也。

方法1(memleak):

1.首先,记录所有程序的运行轨迹。

memleak,全称memory leak,意思是内存泄漏,memleak的原理就是在函数的入口和出口分别插入__cyg_profile_func_enter和__cyg_profile_func_exit,编译时加上–finstrument-functions -g,就可以把运行的所有程序统统记录下来。
2.筛选想要的函数

内存泄漏主要是由于malloc,free等等函数引起的,所以要记录malloc,free等等函数。

而malloc最终调用glibc内部的__libc_malloc(),那么可以通过设置__libc_malloc为弱符合,
自己实现的malloc设置为强符合,然后去替换原来的__libc_malloc,
可以理解为狸猫换太子,这样就可以记录我们想要的函数。

3.如果是单线程的话,运行结束之后,把数据dump出来,查看数据记录,回溯是哪些接口未释放内存就可以结束了。

但是如果是多线程的话,一个容器装多线程的数据,就会导致不知道是哪个线程产生的数据,所以就得用多线程的存储机制去保存数据,即TSD多线程存储机制,它相当于给每一个线程分配一个key,每一个key对应一个容器,这样通过key进行检索,去存储不同线程的数据。

代码1如下:

#include 
#include 
#include 

//1、创建一个类型为 pthread_key_t 类型的变量
static pthread_key_t pKey;
static pthread_once_t control = PTHREAD_ONCE_INIT;

void destructor(void *arg)
{
    printf("destructor executed in thread [%lx], value:%d\n", pthread_self(), *(int*)arg);//arg即为线程保存的数据
}

void create()
{
    printf("[%lx]create pthread pKey\n", pthread_self());
    pthread_key_create(&pKey, destructor);//成功创建key则返回0,否则返回error number
}

void* threadFunc(void* arg)
{
    int* beforeValue;
    int* afterValue;
    pthread_t tid = pthread_self();
	
    //2、once_control必须被初始化为PTREAD_ONCE_INIT,以确保init只执行一次
    pthread_once(&control, create);
	
    //3、进行线程数据存储。
    //param1为前面声明的 pthread_key_t key,通过pKey来存取ThreadCache中的value。
    //param2为要存储的数据, void*类型说明可以存储任何类型的数据
    pthread_setspecific(pKey, arg);
    printf("[%lx]set beforeValue:%d\n", tid, *(int*)arg);//arg = value;
	
    //4、取出所存储的线程数据。	
    //如果没有线程私有数据值与键关联,pthread_getspecific将返回一个空指针,
    //可以据此来确定是否需要调用pthread_setspecific。
    beforeValue = (int *)pthread_getspecific(pKey);//成功则返回指向TSD的指针,否则返回空指针
    printf("[%lx]get beforeValue:%d\n", tid, *beforeValue);
	
    *beforeValue = (*beforeValue) * 99;
    afterValue = (int *)pthread_getspecific(pKey);
    printf("[%lx]get afterValue:%d\n", tid, *afterValue);
}
int main(void)
{
    int value1 = 1;
    int value2 = 2;
    pthread_t thread1, thread2;
	
    printf( "-->before: value1 = %d, value2 = %d\n", value1, value2 );
     
    pthread_create(&thread1, NULL, threadFunc, &value1);
    pthread_create(&thread2, NULL, threadFunc, &value2);
	
    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);
	
    //5、解除key与线程私有数据地址的关联
    pthread_key_delete(pKey);//成功则返回0,否则返回error number
	
    printf( "-->after: value1 = %d, value2 = %d\n", value1, value2 );
    return 0;
}

运行结果如下:

排查内存泄漏的方法_第1张图片

代码2如下:

#include 
#include 
#include 

pthread_key_t key;

void destructor(void *arg)
{
    printf("destructor executed in thread %lx, param = %d\n", pthread_self(), *(int*)arg);
}

void *child1(void *arg)
{
    pthread_t tid = pthread_self();
    printf("thread1 %lx entering\n", tid);
    pthread_setspecific(key, (void *)tid);

    sleep(2);//让出cpu
    printf("thread1 %lx returned %d\n", tid, *(int*)pthread_getspecific(key));
}

void *child2(void *arg)
{
    pthread_t tid = pthread_self();
    printf("thread2 %lx entering\n", tid);
    pthread_setspecific(key, (void *)tid);
	
    sleep(1);
    printf("thread2 %lx returned %p\n", tid, pthread_getspecific(key));
}

int main(int argc, char *argv[])
{
    pthread_t tid1, tid2;
    printf("main thread %lx entering\n", pthread_self());

    pthread_key_create(&key, destructor);

    pthread_create(&tid1, NULL, child1, NULL);
    pthread_create(&tid2, NULL, child2, NULL);

    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);

    pthread_key_delete(key);
	
    printf("main thread %lx returned\n", pthread_self());
    return 0;
}

运行结果如下:

排查内存泄漏的方法_第2张图片

4.最后通过如下命令进行解析:

addr2line -e xxx -a 0xxxxx -fp -s

也可以通过脚本进行解析,代码如下:

#!/bin/sh
 
if [ $# != 3 ]; then
    echo 'Usage: addr2line.sh executefile addressfile functionfile'
    exit
fi;
 
cat $2 | while read line
      do
          if [ "$line" = 'Enter' ]; then
              read line1
              read line2
              addr2line -e $1 -f $line1 -s >> $3
              echo "-----> call" >> $3
              addr2line -e $1 -f $line2 -s | sed 's/^/    /' >> $3
              echo >> $3
          elif [ "$line" = 'Exit' ]; then
              read line1
              read line2
              addr2line -e $1 -f $line2 -s | sed 's/^/    /' >> $3
              echo "<----- return" >> $3
              addr2line -e $1 -f $line1 -s >> $3
              echo >> $3
          fi;
      done

memleak源码地址: MemLeak download | SourceForge.net

malloc.c 源码地址:malloc.c source code [glibc/malloc/malloc.c] - Woboq Code Browser

参考:

gcc -finstrument-functions 追踪函数调用,获取程序的执行流程_耿小渣的进阶之路-CSDN博客

线程特定数据TSD总结_烂笔头-CSDN博客_线程tsd

记一次内存泄漏DUMP分析(转发) - PanPan003 - 博客园

MemLeak学习笔记_一个人像一支队伍-CSDN博客_memleak

linux 内存检测工具之memleak_yuzeze的专栏-CSDN博客_memleak

使用memleak检查和调试内存泄漏_weixin_34413065的博客-CSDN博客

Glibc:浅谈 malloc() 函数具体实现_加号减减号的博客-CSDN博客___libc_malloc

方法2(GDB):

gdb是linux下非常强大的调试工具,不但可以用来调试,还可以用来输出所有内存的内容,即dump内存。

procrank  //跟踪内存变化
1.ps ax | grep -Eai "xxx"
2.对比
a.cat /proc/$pidOfProcess/maps(smaps)
b.pmap(pmaps)  –x $pidOfProcess  //pageMap
3.gdb attach $pidOfProcess  //GDB; set sysroot; attach $pidOfProcess
4.info sharedlib
5.dump memory xx.dump 0x8位 0x8位
6.strings –n 10 xx.dump

方法3(调用set_new_handler接口):

代码会在堆区的动态分配内存空间,导致系统内存耗尽时自动调用set_new_handler参数列表中的函数,并打印出来。

// new_handler example
#include      // std::cout
#include       // std::exit
#include           // std::set_new_handler
 
void no_memory () {
  std::cout << "Failed to allocate memory!\n";
  std::exit (1);
}
 
int main () {
  std::set_new_handler(no_memory);
  std::cout << "Attempting to allocate 1 GiB...";
  char* p = new char [1024*1024*1024];
  std::cout << "Ok\n";
  delete[] p;
  return 0;
}

参考:

C++中的set_new_handler函数_其疾如风 其徐如林 侵略如火 不动如山-CSDN博客_set_new_handler

未完待续。。。

你可能感兴趣的:(C++,c++)