内存泄漏是怎么产生的呢?
在底层申请内存去读写,总会有忘记释放内存,或是逻辑错误,导致未释放内存,亦或是释放内存的消息被堵塞、被覆盖,导致内存未被释放。
总之,总会有奇奇怪怪的场景导致未释放内存,等到操作很多很多次之后,积多成疾,内存不够了,就会内存泄漏,轻则功能缺失,重则系统崩溃。内存作为系统的存储大脑,作为程序员,不能不察也。
方法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;
}
运行结果如下:
代码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;
}
运行结果如下:
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
未完待续。。。