1 拦截 malloc free函数
通过拦截 malloc,free函数,然后以宏定义的方式,改造malloc, free: malloc的时候根据指针地址写一个文件,
free的时候根据指针地址删除这个文件。最后根据留下的文件就可以知道哪个new没有释放内存了。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void *_malloc(size_t size, const char *filename, int line) {
void *ptr = malloc(size);
char buff[128] = {0};
sprintf(buff, "./mem/%p.mem", ptr);
FILE *fp = fopen(buff, "w");
fprintf(fp, "[+]addr: %p, filename: %s, line: %d\n", ptr, filename, line);
fflush(fp);
fclose(fp);
return ptr;
}
void _free(void *ptr, const char *filename, int line) {
char buff[128] = {0};
sprintf(buff, "./mem/%p.mem", ptr);
if (unlink(buff) < 0) { // filename no exist;
printf("double free: %p\n", ptr);
return ;
}
return free(ptr);
}
#define malloc(size) _malloc(size, __FILE__, __LINE__)
#define free(ptr) _free(ptr, __FILE__, __LINE__)
方法1的弊端是,最好是用在一个.c文件中,如果文件较多的话就不太适用。
2 hook malloc/free
同方法1,不过这里使用的是dlsym 来hook malloc/free来代替方法1,通过caller = __builtin_return_address(0)获取
调用者,就算是在不同的文件里也适用
typedef void *(*malloc_t)(size_t size);
typedef void *(*free_t)(size_t size);
malloc_t malloc_f = NULL;
free_t free_f = NULL;
int enable_malloc_hook = 1;
int enable_free_hook = 1;
void *malloc(size_t size){
void p;
if(enable_malloc_hook){ // 避免 递归调用 加标志enable_malloc_hook
enable_malloc_hook = 0;
p = malloc_f(size);
printf("malloc\n");
void caller = __builtin_return_address(0);
printf("caller = %p\n", caller);
char buff[128] = {0};
sprintf(buff, "./%p.mem", p); // filename
FILE *fp = fopen(buff, "w");
fprintf(fp, "[+]%p:%d addr: %p, size: %ld \n", caller, __LINE__, p, size); //这里的 LINE 不符合要求
///
/// 用这个命令找到有malloc没有free的地方: addr2line -f -e 应用程序 -a caller(指针地址) ==> 函数+line
///
fflush(fp);
enable_malloc_hook = 1;
} else {
p = malloc_f(size);
}
return p;
}
void free(void *ptr){
if(enable_free_hook){
enable_free_hook = 0;
char buff[128] = {0};
sprintf(buff, "./%p.mem", ptr); // filename
if(unlink(buff) < 0){ // file not exist; double free
printf("double free: %p\n", ptr);
//return ptr;
}
enable_free_hook = 1;
} else {
free_f(reinterpret_cast<size_t>(ptr));
}
printf("free\n");
//return ptr;
}
void Init_hook(void){
if(malloc_f == NULL){
malloc_f = (malloc_t)dlsym(RTLD_NEXT, "malloc"); //这里得到函数调用的入口
}
if(free_f == NULL){
free_f = (free_t)dlsym(RTLD_NEXT, "free");
}
}
int main(){
Init_hook();
void *p1 = malloc(5);
void *p2 = malloc(15);
void *p3 = malloc(25);
free(p1);
free(p2);
return 0;
}
3 封装底层的malloc free
用更加底层的函数申请内存,释放内存
libc中含有 malloc free ,所以它们不是系统调用
所以可以参考如下:
extern void * __libc_malloc(size_t size)
extern void * __libc_free(void * p)
void * malloc(size_t size) {
__libc_malloc(x); // 这里相当于用了一个跟家底层的接口
}
其它部分类似方法2
4 使用 bpftrace工具
bpftrace要求 linux5.4及以上支持
bpftrace // 可以不用管对方的代码 ,在内核允许的情况下
uprobe 可以挂函数名(内存)、网络、进程调度、内核执行流程等都可以的
在目标程序memleak同级目录下touch一个mem.bt的文件,输入下面的内容,
uprobe:/lib/x86_64-linux-gnu/libc.so.6:malloc
/comm == "memleak" /
{
printf("malloc\n");
}
uprobe:/lib/x86_64-linux-gnu/libc.so.6:free
/comm == "memleak" /
{
printf("free\n");
}
然后:
1 一个终端运行 bpftrace mem.bt
2 接着另一个终端 ./memleak (memleak 是类似标题1中的程序名)
memleak.c // gcc memleak.c -o memleak
int main(){
void *p1 = malloc(5);
void *p2 = malloc(15);
void *p3 = malloc(25);
free(p1);
free(p2);
return 0;
}
如下图:可以查内存泄漏了malloc与 free 的数量不一样,说明有内存没有释放
5 还是bpf工具,监测进程
稍稍改变一下测试程序
int main() {
while(1){
void *p1 = malloc(5);
void *p2 = malloc(5);
void *p3 = malloc(5);
free(p1);
free(p2);
sleep(1);
}
}
运行上面的测试程序,查看进程id
对应进程id是111806,touch一个mempid.bt修改内容如下
uprobe:/lib/x86_64-linux-gnu/libc.so.6:malloc
/pid == 111806/
{
printf("malloc\n");
}
uprobe:/lib/x86_64-linux-gnu/libc.so.6:free
/pid == 111806/
{
printf("free\n");
}
运行测试程序:bpftrace mempid.bt,可得如下输出结果
通过过bpftrace方法查到内存泄露的情况,适用于测试看不到源码的程序。
文章参考与<零声教育>的C/C++linux服务期高级架构系统教程学习:链接