zoukankan      html  css  js  c++  java
  • 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
    ", (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)求堆栈信息和求文件名称行号是两个操作,不能一次性解决这个问题

  • 相关阅读:
    如何学习自动化测试?
    Jenkins中,执行py文件,python找包的路径(找不到自定义包的问题解决)
    数据库的架构设计
    iOS密码框的实现方式
    UISearchController 的大坑
    <第三方>TGRefreshO按照QQ的刷新方式下拉刷新
    关于项目颜色和字体的宏定义
    <iOS 导航栏>第一节:导航栏透明方法实现代码
    <iOS小技巧>UIview指定设置控件圆角
    关于这次KPL春季决赛的感悟
  • 原文地址:https://www.cnblogs.com/yjbjingcha/p/7116061.html
Copyright © 2011-2022 走看看