linux中内存泄漏的检测(五)记录内存泄漏的代码

到目前为止,先后通过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)求堆栈信息和求文件名行号是两个操作,不能一次性解决问题

时间: 2024-10-25 08:11:04

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

在Linux中通过Top运行进程查找最高内存和CPU使用率

按内存使用情况查找前15个进程,在批处理模式下为“top” 使用top命令查看有关当前状态,系统使用情况的更详细信息:正常运行时间,负载平均值和进程总数. 分类:Linux命令操作系统 2016-07-27 00:00:00 类似于前面的技巧有关找出由RAM和CPU使用率最高的进程 ,还可以使用top命令来查看相同的信息. 也许有相比前一个这种方法的一个额外的优势:顶级的“头”,提供有关当前状态和使用该系统的额外信息:正常运行时间,平均负载和进程总数,仅举几例例子. 按顶部查找按内存使用的进程

《Linux Device Drivers》第十五章 内存映射和DMA——note

简单介绍 很多类型的驱动程序编程都须要了解一些虚拟内存子系统怎样工作的知识 当遇到更为复杂.性能要求更为苛刻的子系统时,本章所讨论的内容迟早都要用到 本章的内容分成三个部分 讲述mmap系统调用的实现过程 讲述怎样跨越边界直接訪问用户空间的内存页 讲述了直接内存訪问(DMA)I/O操作,它使得外设具有直接訪问系统内存的能力 Linux的内存管理 地址类型 Linux是一个虚拟内存系统,这意味着用户程序所使用的地址与硬件使用的物理地址是不等同的 有了虚拟内存,在系统中执行的程序能够分配比物理内存很

Linux中安装MongoDB出现的问题记录

mongoDB安装完成后,运行sudo service mongod start 查看程序状态:ps ajx | grep mongod   ,启动失败 查看失败信息提示,终端命令:tail -f /var/log/mongodb/mongod.log 错误提示:Failed to unlink socket file /tmp/mongodb-27017.sock errno:1 Operation not permitted 解决办法:手动删除sock文件,终端命令:rm /tmp/mong

[转]浅谈C/C++内存泄露及其检测工具

转自:http://www.cnblogs.com/taoxu0903/archive/2007/10/27/939261.html 对于一个c/c++程序员来说,内存泄漏是一个常见的也是令人头疼的问题.已经有许多技术被研究出来以应对这个问题,比如 Smart Pointer,Garbage Collection等.Smart Pointer技术比较成熟,STL中已经包含支持Smart Pointer的class,但是它的使用似乎并不广泛,而且它也不能解决所有的问题:Garbage Collec

Linux中的保护机制

Linux中的保护机制 在编写漏洞利用代码的时候,需要特别注意目标进程是否开启了NX.PIE等机制,例如存在NX的话就不能直接执行栈上的数据,存在PIE 的话各个系统调用的地址就是随机化的. 一:canary(栈保护) 栈溢出保护是一种缓冲区溢出攻击缓解手段,当函数存在缓冲区溢出攻击漏洞时,攻击者可以覆盖栈上的返回地址来让shellcode能够得到执行.当启用栈保护后,函数开始执行的时候会先往栈里插入cookie信息,当函数真正返回的时候会验证cookie信息是否合法,如果不合法就停止程序运行.

linux中内存泄漏的检测(四)记录泄漏的大小

<linux中内存泄漏的检测(三)定制化的new/delete>讲到,利用C++的函数重载的特性,使C++的代码,也能方便地为new/delete加上用于检测内存泄漏的统计代码.然而,也因此引入的新的问题. 目前的统计方式仅仅统计申请/释放内存的次数,并没有统计每次申请/释放内存的大小. 这种方法对于C来说是够用了,因为在C中申请和释放的大小是相同的,而在C++中就不一定了. 考虑以下两种情况: (1)申请了子类的空间却只释放了父类的空间 father *pF = new son; delet

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

什么是内存泄漏 内存泄漏是指程序动态申请的内存在使用完后没有释放,导致这段内存不能被操作系统回收再利用. 例如这段程序,申请了4个字节的空间但没有释放,有4个字节的内存泄漏. #include <iostream> using namespace std; int main() { int *p = new int(1); cout <<*p<<endl; return 0 } 随着时间的推移,泄漏的内存越来越多,可用的内存越来越少,轻则性能受损,重则系统崩溃. 一般情

linux中内存泄漏的检测(二)定制化的malloc/free

<linux中内存泄漏的检测(一)最简单的方法>介绍了最简单的内存泄漏检测方法,这种方法虽然简单,却有很多现实的问题,导致它不能用于实际的生产中. 直接使用这种方法肯定是不现实的,因为: (1)把整个工程里所有调用malloc/free的地方都改成my_malloc/my_free,代码改动很大. (2)通常动态库和静态库的代码是没有权限修改的. 今天就来解决这个问题,动态地决定让程序使用自己的还是系统的内存管理接口. wrap选项 不希望修改产品代码,那么用于申请/释放内存的接口还是mall

Linux中的常用内存问题检测工具

原文地址:http://blog.csdn.net/jinzhuojun/article/details/46659155 C/C++等底层语言在提供强大功能及性能的同时,其灵活的内存访问也带来了各种纠结的问题.如果crash的地方正是内存使用错误的地方,说明你人品好.如果crash的地方内存明显不是consistent的,或者内存管理信息都已被破坏,并且还是随机出现的,那就比较麻烦了.当然,祼看code打log是一个办法,但其效率不是太高,尤其是在运行成本高或重现概率低的情况下.另外,静态检查