上海长宁网站建设,网页版qq游戏大厅,wordpress页脚设置,WordPress高级微信机器人前面介绍的mtrace也好#xff0c;bcc也罢#xff0c;其实都是hook技术的一种实现#xff0c;但是mtrace本身使用场景上有局限#xff0c;而bcc环境依赖则十分复杂。因此#xff0c;这些调试手段只适用于开发环境用来调试#xff0c;对于生产环境#xff0c;均不是一个非…前面介绍的mtrace也好bcc也罢其实都是hook技术的一种实现但是mtrace本身使用场景上有局限而bcc环境依赖则十分复杂。因此这些调试手段只适用于开发环境用来调试对于生产环境均不是一个非常好的方法。
但其实所谓的hook技术本身并不复杂说白了就是重载malloc/free函数让代码在调用malloc函数时先调用我们自己定义的malloc由自定义的malloc完成一些前置统计工作后再去调用系统的malloc从而完成一些内存的统计分析。明白了这一点其实我们可以轻而易举地自己实现一套hook机制嵌入到代码中这样带来的好处显而易见
不依赖其他检测工具可以非常方便地在开发环境甚至在线上进行调试可以在代码里通过开关的方式控制是否检测内存泄漏如线上默认关闭如果怀疑出现内存泄漏则将开关打开可以非常方便地定位问题系统无关可以针对不同操作系统有不同的实现这对于跨平台的应用非常有帮助。
对于C语言我们可以用dlsym去修改动态链接的函数备注dlsym函数是GNU扩展但dlsym的最大劣势仍然是只能在Linux下使用那么有没有一套跨平台的方案可以在所有平台上运行呢其实也是有的。
那就是通过宏定义的方式将malloc/free这些函数替换成我们自己实现的钩子函数。比如我们有如下定义
//mcheck.h
#ifndef _MCHECK_H_
#define _MCHECK_H_#includestddef.hvoid mcheck_initialize();
void mcheck_terminate();void *malloc_hook(size_t size, const char *file, int line);
void *calloc_hook(size_t nmemb, size_t size, const char *file, int line);
void *realloc_hook(void *ptr, size_t size, const char *file, int line);
void free_hook(void *p, const char *file, int line);#define malloc(size) malloc_hook(size, __FILE__, __LINE__)
#define calloc(nmemb, size) calloc_hook(nmemb, size, __FILE__, __LINE__)
#define realloc(ptr, size) realloc_hook(ptr, size, __FILE__, __LINE__)
#define free(p) free_hook(p, __FILE__, __LINE__)#endif由于宏定义是直接替换因此有了上面的代码在执行malloc的时候实际上执行的是malloc_hook函数。而我们只需要在实现malloc_hook/free_hook的时候加上一些统计信息就行了。
在此之前我们封装一个链表用来存储每次申请内存的地址以及申请内存的大小在每次申请内存的时候向链表添加一条数据每次释放的时候将对应的记录删除掉那么当程序结束如果链表还有数据那就是没有释放的泄露部分的内存。
当然你也可以用其他的数据接口来存储这里为了简单演示就直接使用链表了。
// meminfo.h
#ifndef _MEMINFO_H_
#define _MEMINFO_H_#includestddef.h
#includestdio.h
#includestdlib.htypedef enum mcheck_errcode_t
{MCHECK_SUCCESS,MCHECK_FAILED,
}mcheck_errcode_t;typedef struct mcheck_caller_t
{const char *file;int line;const char *func;
} mcheck_caller_t;typedef struct mcheck_meminfo {void *address;size_t size;mcheck_caller_t caller;
}mcheck_meminfo;typedef struct mcheck_mem_list {mcheck_meminfo mem_info;struct mcheck_mem_list *next;
}mcheck_mem_list;int mcheck_mem_list_create(mcheck_mem_list **list);
int mcheck_mem_list_add(mcheck_mem_list *mlist, mcheck_meminfo mem_info);
mcheck_mem_list *mcheck_mem_list_get(mcheck_mem_list *mlist, void *address);
int mcheck_mem_list_delete(mcheck_mem_list *mlist, void *address);
int mcheck_list_size(mcheck_mem_list *mlist);
void mcheck_list_report_leak(mcheck_mem_list *mlist);
void mcheck_list_destory(mcheck_mem_list *mlist);
void init_report_file();#endif定义如下
//meminfo.c
#include meminfo.hint mcheck_mem_list_create(mcheck_mem_list **mlist){if (*mlist ! NULL) {mcheck_list_destory(*mlist);}*mlist (mcheck_mem_list *)calloc(1, sizeof(mcheck_mem_list));if (*mlist NULL) {return MCHECK_FAILED;}(*mlist)-next NULL;return MCHECK_SUCCESS;
}int mcheck_mem_list_add(mcheck_mem_list *mlist, mcheck_meminfo mem_info)
{mcheck_mem_list *node NULL;mcheck_mem_list_create(node);node-mem_info mem_info;if (mlist NULL){mlist node;return MCHECK_SUCCESS;}while (mlist-next ! NULL){mlist mlist-next;}mlist-next node;return MCHECK_SUCCESS;
}mcheck_mem_list *mcheck_mem_list_get(mcheck_mem_list *mlist, void *address){if (mlist NULL) {return NULL;}mcheck_mem_list *node mlist;while (mlist ! NULL) {if (address mlist-mem_info.address) {return node;}mlist mlist-next;}return NULL;
}int mcheck_mem_list_delete(mcheck_mem_list *mlist, void *address){if (mlist NULL) {return MCHECK_FAILED;}mcheck_mem_list *current mlist;mcheck_mem_list *prev mlist;while (current ! NULL) {if (current-mem_info.address address) {if (mlist current) {if (current-next NULL) {mlist NULL;} else {mlist mlist-next;}} else {prev-next current-next;}free(current);return MCHECK_SUCCESS;}prev current;current current-next;}return MCHECK_FAILED;
}int mcheck_list_size(mcheck_mem_list *mlist){if (mlist NULL) {return 0;}int size 0;while(mlist-next ! NULL){size;mlist mlist-next;}return size;
}void mcheck_list_destory(mcheck_mem_list *mlist){if (mlist NULL) {return;}mcheck_mem_list *current mlist;mcheck_mem_list *prev mlist;while (prev ! NULL) {current current-next;free(prev);prev current;}mlist NULL;
}static char *get_report_filename(){char *fname getenv(MCHECK_TRACE);if (fname NULL) {fname mcheck.rpt;}return fname;
}void init_report_file(){char *fname get_report_filename();FILE *fp NULL;fp fopen(fname, w);fclose(fp);
}static void write_report_file(char *message){char *fname get_report_filename();FILE *fp NULL;fp fopen(fname, a);fprintf(fp, %s\n, message);fflush(stdout);fclose(fp);
}void mcheck_list_report_leak(mcheck_mem_list *mlist){if (mcheck_list_size(mlist) 0){write_report_file(All memory was free, congratulations, well done!);return;}write_report_file(Memory Not Free:);write_report_file(-----------------------------);write_report_file(\tAddress\t\tSize\t\tCaller);mlist mlist-next;while (mlist ! NULL){mcheck_meminfo mem_info mlist-mem_info;char message[1024] {0};sprintf(message, \t%p\t%lu\tat\t%s:%d[%s], mem_info.address, mem_info.size, mem_info.caller.file, mem_info.caller.line, mem_info.caller.func);write_report_file(message);mlist mlist-next;}
}以上代码逻辑比较简单 就不一一解释了。接下来实现一下钩子函数
#includestdio.h
#includemalloc.h
#include meminfo.hstatic mcheck_mem_list *mlist NULL;void mcheck_initialize()
{init_report_file();mcheck_mem_list_create(mlist);
}void mcheck_terminate(){mcheck_list_report_leak(mlist);mcheck_list_destory(mlist);
}void *malloc_hook(size_t size, const char *file, int line)
{void *p malloc(size);char buff[128] {0};mcheck_meminfo meminfo {0};mcheck_caller_t caller {0};caller.file file;caller.line line;caller.func malloc;meminfo.address p;meminfo.size size;meminfo.caller caller;mcheck_mem_list_add(mlist, meminfo);return p;
}void *calloc_hook(size_t nmemb, size_t size, const char *file, int line)
{void *p calloc(nmemb, size);char buff[128] {0};mcheck_meminfo meminfo {0};mcheck_caller_t caller {0};caller.file file;caller.line line;caller.func calloc;meminfo.address p;meminfo.size nmemb*size;meminfo.caller caller;mcheck_mem_list_add(mlist, meminfo);return p;
}void *realloc_hook(void *ptr, size_t size, const char *file, int line)
{void *p realloc(ptr, size);char buff[128] {0};mcheck_meminfo meminfo {0};mcheck_caller_t caller {0};caller.file file;caller.line line;caller.func realloc;meminfo.address p;meminfo.size size;meminfo.caller caller;mcheck_mem_list_add(mlist, meminfo);mcheck_mem_list_delete(mlist, ptr);return p;
}void free_hook(void *p, const char *file, int line){mcheck_mem_list_delete(mlist, p);free(p);
}如上所示我们会在每次申请内存之前收集其调用栈信息并且封装了mcheck_initialize和mcheck_terminate两个接口函数我们只需要在程序里调用这两个函数就能检测出内存泄露的问题。
类似与下面这种
#include mcheck.hint main(void){mcheck_initialize();//your codemcheck_terminate();return 0;
}下面我们用一个具体的示例演示一下
#include mcheck.hint main(void){mcheck_initialize();void *p1 malloc(10);void *p2 malloc(20);free(p1);//free(p2);void *p3 malloc(30);free(p3);void *p4 calloc(1, 64);void *p5 malloc(32);void *p6 realloc(p5, 128);mcheck_terminate();return 0;我们注意在第1行包含了头文件并在第4行和14行分别调用了mcheck的接口这样的话就可以检测出这个过程中出现的内存泄露问题以上代码运行会产生一个名为mcheck.rpt的报告内容如下
Memory Not Free:
-----------------------------Address Size Caller0x1fde0b0 20 at mcheck_sample.c:6[malloc]0x1fde300 64 at mcheck_sample.c:11[calloc]0x1fde390 128 at mcheck_sample.c:13[realloc]它告诉我们第61113行分别出现了内存泄露大小是多少调用的那么函数申请的内存还是比较详细的。
但是这种方式也有缺陷。首先就是调用栈只有一层不能打印出更深层的调用栈不利于复杂程序的问题排查。其次是如果要使mcheck生效必须每个.c里都要include该头文件对于第三方库是没有办法检测到的因此使用面也是比较有限。 本专栏知识点是通过零声教育的系统学习进行梳理总结写下文章对C/C课程感兴趣的读者可以点击链接查看详细的服务C/CLinux服务器开发/高级架构师