到眼下为止,先后通过wrap malloc、new函数重载和计算指针内存大小的方法。基本上满足了对内存泄漏检測的须要。

假设发现了内存泄漏。那么就要找到内存泄漏的地方而且修正它了。

茫茫代码。如何去找?假设能依据未释放的内存找到申请它的地方就好了。

我们今天就是要做这个事情。

想要依据内存地址查出申请者的信息。那么在一開始申请的时候就要建立地址与申请者之间的映射。

1.内存地址

内存地址,是一个unsigned long型的数值,用void *来存储也能够。

为了避免类型转换。我使用了void *

2.申请者信息

申请者的信息比較复杂,不是一个类型能够搞定的。它包括哪些内容呢?

在C情况下,主要是须要知道谁调用了__wrap_malloc

但在C++情况下。调用__wrap_malloc的一定是new。这没有什么意义,还须要知道是谁调用了new。再进一步说,new有可能是在构造函数中被调用的,那么非常有可能我们真正须要知道的是谁调用了构造函数。

由此可见,仅仅知道是谁调用了__wrap_malloc不够的,我们须要的是整个栈信息。

整个栈包括了非常多内容,在这里。我们仅仅记录栈的深度(int)和每一层的符号名(char **)。

符号名在整个程序中是唯一的(无论C还是C++)且相对位置是确定的(动态库除外)。当程序结束时再依据符号名反推出调用者的文件名称和行号。

为什么不直接获取文件名称和行号?

因为求符号名的实现比較简单。

3.映射方式

说到映射。首先想到的是map、hash这种东西。

但须要说明的是,这里是__wrap_malloc函数,是每次程序动态分配空间时必定会走到的地方。

这有什么关系呢?想象一下,在因为某个动态申请内存的操作来到了这个函数。而在这个函数里又不小心申请了一次内存,会如何呢?在-Wl,--wrap,malloc的作用下又来到了这里,于是开启了“鸡生蛋、蛋生鸡”的死循环中,直到——stack overflow。

所以,在这个函数里能使用的。仅仅能使用栈空间或者全局空间,假设一定要使用堆空间,也必须显示地使用__real_malloc取代new或者malloc。因为在map、hash中会不可避免地使用动态内存空间的情况,还是放弃吧。

怎么办呢?为了避免节外生枝,我这里使用了最简单可是有点笨的方法——数组。

struct memory_record
{
void * addr;
size_t count;
int depth;
char **symbols;
}mc[1000];

4.如何获取栈中的符号?

gcc给我们提对应的函数,依照要求调用即可。

char* stack[20] = {0};
mc[i].depth = backtrace(reinterpret_cast<void ** >(stack), sizeof(stack)/sizeof(stack[0]));
if (mc[i].depth){
mc[i].symbols = backtrace_symbols(reinterpret_cast<void**>(stack), mc[i].depth);
}

backtrace函数用于获取栈的深度(depth),以及每一层栈地址(stack)。

backtrace_symbols函数依据栈地址返回符号名(symbols)。

须要注意的是。backtrace_symbols返回的是符号的数组,这个数组的空间是由backtrace_symbols分配的,但须要调用者释放。

为什么这里backtrace_symbols分配了内存却没有引起stack overflow呢?下面是我的推測:

backtrace_symbols函数和wrap机制都是GNU提供的,属性亲戚关系。

既然是亲戚。那么大家通融一下,让backtrace_symbols绕过wrap机制直接使用内存也是有可能的。

源码:

#include <iostream>
using namespace std; #include "string.h"
#include <stdio.h>
#include <malloc.h>
#include <execinfo.h> #if(defined(_X86_) && !defined(__x86_64))
#define _ALLOCA_S_MARKER_SIZE 4
#elif defined(__ia64__) || defined(__x86_64)
#define _ALLOCA_S_MARKER_SIZE 8
#endif size_t count = 0; int backtrace(void **buffer, int size); struct memory_record
{
void * addr;
size_t count;
int depth;
char **symbols;
}mc[1000]; extern "C"
{
void* __real_malloc(int c);
void * __wrap_malloc(size_t size)
{
void *p = __real_malloc(size);
size_t w = *((size_t*)((char*)p - _ALLOCA_S_MARKER_SIZE));
cout<<"malloc "<<p<<endl;
for(int i = 0; i < 1000; i++)
{
if(mc[i].count == 0)
{
count += w;
mc[i].addr = p;
mc[i].count = w;
char* stack[20] = {0};
mc[i].depth = backtrace(reinterpret_cast<void**>(stack), sizeof(stack)/sizeof(stack[0]));
if (mc[i].depth){
mc[i].symbols = backtrace_symbols(reinterpret_cast<void**>(stack), mc[i].depth);
}
break;
}
}
return p;
} void __real_free(void *ptr);
void __wrap_free(void *ptr)
{
cout<<"free "<<ptr<<endl;
size_t w = *((size_t*)((char*)ptr - _ALLOCA_S_MARKER_SIZE));
for(int i = 0; i < 1000; i++)
{
if(mc[i].addr == ptr)
{
mc[i].count -= w;
count -= w;
if(mc[i].symbols)
__real_free(mc[i].symbols);
break;
}
}
__real_free(ptr);
}
} void *operator new(size_t size)
{
return malloc(size);
} void operator delete(void *ptr)
{
free(ptr);
} void print_leaked_memory()
{
if(count != 0)
cout<<"memory leak!"<<endl;
for(int i = 0; i < 1000; i++)
{
if(mc[i].count != 0)
{
cout<<mc[i].addr<<' '<<mc[i].count<<endl;
if (mc[i].symbols){
for(size_t j = 0; j < mc[i].depth; j++){
printf("===[%d]:%s\n", (j+1), mc[i].symbols[j]);
}
}
__real_free(mc[i].symbols);
}
}
} class A
{
int *p1;
public:
A(){p1 = new int;}
~A(){delete p1;}
}; int main(void)
{
memset(mc, 0, sizeof(mc));
count = 0;
int *p1 = new int(4);
int *p2 = new int(5);
delete p1;
print_leaked_memory();
return 0;
}

编译命令:

g++ -o test test.cpp -g -Wl,--wrap,malloc -Wl,--wrap,free

执行:

./test | grep "===" | cut -d"[" -f3 | tr -d "]" | addr2line -e test

方法分析:

长处:

(1)在程序执行结束时。打印程序内存泄漏情况以及导致泄漏发生的代码所在的文件及行号

(2)C/C++都适用

(3)须要改动产品源码即可实现功能

(4)对一起链接的全部.o和静态库都有效

缺点:

(1)对动态库不适用

(2)求堆栈信息和求文件名称行号是两个操作,不能一次性解决这个问题

linux中内存泄漏的检測(五)记录内存泄漏的代码的更多相关文章

  1. Linux中生成Core Dump系统异常信息记录文件的教程

    Linux中生成Core Dump系统异常信息记录文件的教程 http://www.jb51.net/LINUXjishu/473351.html

  2. linux中内存泄漏的检測(一)最简单的方法

    什么是内存泄漏 内存泄漏是指程序动态申请的内存在使用完后没有释放,导致这段内存不能被操作系统回收再利用. 比如这段程序,申请了4个字节的空间但没有释放,有4个字节的内存泄漏. #include < ...

  3. Linux基础教程 linux中使用find命令搜索文件常用方法记录

    find是linux非常强大的搜索命令,通过man find查看find手册,可以发现find的说明一屏接一屏,估计要看完也得花不少时间.兄弟连Linux培训 小编总结了下,整理出find常用的使用方 ...

  4. Linux中Postfix邮件认证配置(五)

    Postfix+Dovecot+Sasl工作原理 1.A用户使用MUA客户端借助smtp协议登陆smtpd服务器,需要先进行用户和密码认证,而SMTPD服务器端支持sasl认证,例如有一个sasl客户 ...

  5. Linux中java项目环境部署,简单记录一下

    这里只是简单的记录一下linux环境下面如何快速的搭配好环境,使你的项目能在linux环境上面运行. 很多时候,我们都是用windows环境进行配置调试的,而真正很多服务器都是在linux服务器上面的 ...

  6. C++内存泄露检測原理

    转自:http://hi.baidu.com/jasonlyy/item/9ca0cecf2c8f113a99b4981c 本文针对 linux 下的 C++ 程序的内存泄漏的检測方法及事实上现进行探 ...

  7. linux中的 IO端口映射和IO内存映射

    参考自:http://blog.csdn.net/zyhorse2010/article/details/6590488 CPU地址空间 (一)地址的概念 1)物理地址:CPU地址总线传来的地址,由硬 ...

  8. Cocos2d-x教程(34)-三维物体OBB碰撞检測算法

    欢迎增加Cocos2d-x 交流群:193411763 个中心点.1个旋转矩阵和3个1/2边长(注:一个旋转矩阵包括了三个旋转轴,若是二维的OBB包围盒则是一个中心点,两个旋转轴,两个1/2边长). ...

  9. Canny边缘检測算法原理及其VC实现具体解释(一)

    图象的边缘是指图象局部区域亮度变化显著的部分,该区域的灰度剖面一般能够看作是一个阶跃,既从一个灰度值在非常小的缓冲区域内急剧变化到还有一个灰度相差较大的灰度值.图象的边缘部分集中了图象的大部分信息,图 ...

随机推荐

  1. Laravel API 允许跨域访问

    服务器A请求服务器B的接口,那么一般会出现跨域问题.全解跨域请求处理办法 XMLHttpRequest cannot load http://api.console.vms3.com/api/user ...

  2. [Recompose] Stream a React Component from an Ajax Request with RxJS

    Loading data using RxJS is simple using Observable.ajax. This lesson shows you how to take the ajax ...

  3. 三星N900(note3)刷机包 颓废N0.8.1 修复已知BUG 集成谷歌服务

    ROM介绍 8.1更新信息:攻克了来电后点击HOME出现SECPHONE已经停止的问题 去掉了桌面隐藏信息的选项,官方最新底包暂不支持这功能 增加了网友们须要验证的谷歌服务(不须要的同学同步什么的都关 ...

  4. ZOJ 2588 Burning Bridges(无向连通图求割边)

    题目地址:ZOJ 2588 由于数组开小了而TLE了..这题就是一个求无向连通图最小割边.仅仅要推断dfn[u]是否<low[v],由于low指的当前所能回到的祖先的最小标号,增加low[v]大 ...

  5. xml方式封装通信数据方法

    xml方式封装通信数据方法 public static function xmlToEncode($data) { $xml = ""; foreach($data as $key ...

  6. vue26-2.0循环

    3. 循环 2.0里面默认就可以添加重复数据 arr.forEach(function(item,index){ }); 去掉了隐式一些变量 $index $key 之前: v-for="( ...

  7. LINQ查询基础

    一.什么是LINQ LINQ是Language Integrate Query的缩写,意为语言集成查询,是微软在.Net Framework 4.5版中推出的主要特性之一. 它为开发人员提供了统一的数 ...

  8. ASE加、解密

    AES已经变成目前对称加密中最流行算法之一:AES可以使用128.192.和256位密钥,并且用128位分组加密和解密数据. /** * 加密 * * @param content 需要加密的内容 * ...

  9. dubbo问题求解

    各位大牛好,小弟公司开发中遇到一个很奇怪的问题望有大神指教一下,实在是已经搞了3天了一点头绪没有,公司使用的是eclipse+maven+zookeper+dubbo主要是dubbo的问题,刚开始使用 ...

  10. React-router 4 总结

    React-Router 4: BrowserRouter包裹整个应用 Router路由对应渲染的组件,可嵌套 Link跳转专用 首先 然后 其他组件: url参数 Route组建参数可用冒号标识参数 ...