malloc hook进行内存泄漏检测

记录下使用malloc的hook形式,写个小的demo,并记录遇到的问题

1. 实现代码:

   CMakeLists.txt和相应的memory_leak.cpp文件

cmake_minimum_required(VERSION 3.14)
project(demo)

set(_SRC
    memory_leak.cpp)

add_library(memory_leak SHARED ${_SRC})
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <execinfo.h>

//实际内存申请的函数
extern void *__libc_malloc(size_t size);
int enable_malloc_hook = 1;
 
extern void __libc_free(void* p);
int enable_free_hook = 1;

void *malloc(size_t size) 
{
	if (enable_malloc_hook) 
    {
		enable_malloc_hook = 0;
        
        void *p = __libc_malloc(size); //重载达到劫持后 实际内存申请
		// void *caller = __builtin_return_address(0); // 0
		
		// char buff[128] = {0};
		// sprintf(buff, "./mem/%p.mem", p);

        // FILE *fp = fopen(buff, "w");
		// // fprintf(fp, "[+%p] --> addr:%p, size:%ld\n", caller, p, size);

        // void* trace[16];
        // char** messages = (char**)NULL;
        // int i, trace_size = 0;
        // trace_size  = backtrace(trace, 16);
        // messages    = backtrace_symbols(trace, trace_size);
        // for (i=0; i<trace_size; ++i) 
        // {
        //     fprintf(fp, "[bt][%s]\n", messages[i]);
        // }

		// fflush(fp);
        
		fclose(fp); //free
		printf("=====Malloc: %p\n", p);

		enable_malloc_hook = 1;
		
        return p;
	} 
    else 
    {
		return __libc_malloc(size);
	}

	return NULL;
}

void free(void *p) 
{
	if (NULL == p)   // 这里加这个判断是因为backtrace函数的调用不知道为什么会调用free并传递的是0x0指针
    {
        return;
    }

    if (enable_free_hook) 
    {
        printf("=====free: %p\n", p);

		enable_free_hook = 0;
 
		char buff[128] = {0};
		sprintf(buff, "./mem/%p.mem", p);
 
		if (unlink(buff) < 0) 
        { // no exist
			printf("double free: %p\n", p);
		}
        __libc_free(p);
        
		// rm -rf p.mem
		enable_free_hook = 1;
	} 
    else 
    {
		__libc_free(p);
	}
}

测试函数:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <thread>
#include <chrono>
int main()
{

    // while (true)
    {
        std::this_thread::sleep_for(std::chrono::milliseconds(500));
        void * ptr1 = malloc(10);
    }

    return 0;
}

使用方式:

        export LD_PRELOAD=./libmemory_leak.so

        ./demo

2. 遇到问题

  •  为什么memory_leak使用cpp后续,直接执行demo就直接死机

        Segmentation fault (core dumped)

        但是使用.c后缀编译就没问题

        原因:

                应该和CPP的签名有关系,直接在使用memory_leak.cpp编译,并使用

        extern "C"

        {

                //所有代码

        }

        使用OK

  • 上面代码直接运行是可以的,但是为什么gdb的时候会出现死机,感觉是在递归调用,但是逻辑中已经添加了递归的限制,会反复输出二次释放的信息

        直接将memory_leak.cpp的源码直接嵌套在main.cpp中,就可以gdb了,为什么?

        实际使用中应该就使使用独立的动态库然后LD_PRELOAD的方式进行的呀

        

  • 想要直接gdb ./demo | tee log.txt,通过log.txt查看究竟是否真的二次释放了

 可以看到第一个free之前都没有调用malloc,为什么没有调用malloc就调用了free呢?

 猜测:难道除了系统了free还有别的资源free函数被覆盖了,但是这个资源却不是通过malloc申请的?

3. 好的方案

这里有已经实现好的检测方案:

    GitHub - efficios/memleak-finder: Simple memory leak finder.

大家可以在这个基础上进行自定义修改,满足自己的工程需求。

不过在使用这个代码的过程中,遇到一些问题

3.1. 程序卡住

        直接将库应用到项目代码,会出现程序卡住不动的情况,查看堆栈发现:

第一种堆栈现场:       

 感觉是这个线程一直等在这里,是不是锁被一直占用?

第二程堆栈现场:

直接屏蔽掉接口:

add_mh(void *ptr, size_t alloc_size, const void *caller)

del_mh(void *ptr, const void *caller)

程序就可以正常运行了()

        查看完整堆栈会发现,好几个线程都在竞争锁mh_mutex,不过这里只有一把锁,怎么感觉是死锁了呢?

        为什么屏蔽掉上面两个接口,就有好了呢?

        

3.2. 误报输出很多leak

程序启动后,输出一大堆leak,但是我当前感觉不太像是leak的情况,后续需要再确认

猜你喜欢

转载自blog.csdn.net/bocai1215/article/details/130160501