记录下使用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的情况,后续需要再确认