linux上内存泄漏查看方法之——让系统调用自己定义的new

发布时间:2014-10-23 23:31:00
来源:分享查询网

        内存泄漏是一种非常讨厌的问题,严重却又难查。在windows平台上开发,有许多工具可以帮助查找内存泄漏。在linux平台上却没这么简单。一种查内存泄漏的方法就是通过重载申请和释放内存的操作。重载函数中除了完成原有的申请/释放操作,还要对该操作作记录。通过标记出所有申请内存和释放内存的地方并做统计的方法来查找内存漏泄。          虽然重载内存操作函数可以用于查找内存泄漏,但对于一个已经完成的程序,直接封装“申请内存”和“释放内存的”功能很不现实,原因有二:          (1)必须把代码中所有用到的地方替换成新定义的接口,使用的方法可以是new/delete、malloc/free、……。如果工程很大,这个工作量很大,且容易引入新问题          (2)对于库里面的申请和释放内存地地方,不可能拿到代码然后也改一遍,或者要求库管理者专门提供一个库            怎样才能在申请和释放内存时,让系统自动地调用自己定义的函数呢?本文以申请内存为例,提供以下三种方法,代码将稍后补齐。     一、wrap 链接选项中使用—wrap选择。例如在编译选项中加入这样一句话,LFLAGS  += -Wl,--wrap,func,程序中所以调用func的地方都会先调用__wrap_func,没有定义才会去调用func;程序中所有调用__real_func的地方,如果没有定义__real_func,都会去调用func。 本文仅以分配空间为例。参考man ld。 (1)__wrap_malloc 此方法用于检测malloc。通过在makefile中加入CFLAGS  += -Wl,--wrap,malloc,在调用malloc中自动调用__wrap_malloc。然后在__wrap_malloc中调用__real_malloc。__real_malloc只声明不定义的,系统就会自动调用malloc代替__real_malloc。 测试代码中,打开宏CFLAGS += -DWRAP_MALLOC 调用过程:malloc -> __wrap_malloc -> __real_malloc -> malloc (2)把new重载为malloc,再调用malloc 此方法用于检测new。先通过void* operator new(size_t size)重载操作符new,但重载函数中调用malloc以完成分配空间的功能。结合(1),最终调用malloc分配空间 New和malloc不完全一样。Malloc仅仅是内存分配,new包括内存分配和对象构造两部分,分别由运算符new和函数new完成。我们仅需要重载运算符new。函数new的重载很复杂,也不需要。 测试代码中,打开宏CFLAGS += -DWRAP_MALLOC和CFLAGS += -DWRAP_NEW_1 调用过程:new -> operator new -> malloc -> __wrap_malloc -> __real_malloc -> malloc (3)__wrap_new        此方法用于检测new。使用(1)的方法,在makefile中加入#LFLAGS += -Wl,--wrap,new。 实际上该方法不可行。在调用__real_new时会提示找不到new,可能是因为__wrap_new和__real_new都是C函数。 (4)测试代码 main.cpp #include <iostream> #include <stdlib.h> #include <stdio.h> using namespace std; #ifdef WRAP_NEW_1 extern "C" {void* __wrap_malloc(int c);} void* operator new(size_t size) { cout<<"operator new"<<endl; void *p=malloc(size); return p; } #endif int main() { while(1) { #if defined WRAP_NEW_1 || defined WRAP_NEW_2 int *t = new int; #elif defined WRAP_MALLOC int *t = (int*)malloc(sizeof(int)); #endif #if 1 *t = 4; cout<<*t<<endl; #endif sleep(1); } return 0; } wrap.cpp #include <iostream> using namespace std; extern "C" { #ifdef WRAP_MALLOC void* __real_malloc(int c); void* __wrap_malloc(int c) { void *ret = __real_malloc(c); // printf("__wrap_malloc c = %d\n", c); cout<<"__wrap_malloc size = "<<c<<endl; return ret; } #endif #ifdef WRAP_NEW_2 void* __real_new(int c); void* __warp_new(int c) { void *ret = __real_new(c); cout<<"__wrap_new size = "<<c<<endl; return ret; } #endif } makefile LFLAGS += -Wl,--wrap,malloc #LFLAGS += -Wl,--wrap,new #重载wrap_malloc CFLAGS += -DWRAP_MALLOC #重载new CFLAGS += -DWRAP_NEW_1 #重载wrap_new #CFLAGS += -DWRAP_NEW_2 objects=main.o wrap.o test:$(objects) g++ $(LFLAGS) -g -o test $(objects) %.o:%.cpp @echo g++ -c $< $(CFLAGS) g++ $(CFLAGS) -g -c $< clean: rm test -f rm *.o -f   二、LD_PRELOAD 另写一个函数,重载操作符new。把它编成动态库new.so。在环境变量中设置该库的加载做优先级最高。这种方法更灵活。参考:man 8 ld.so LD_PRELOAD A whitespace-separated list of additional, user-specified,  ELF  shared  libraries  to  be  loaded before all others.  This can be used to selectively override functions in other shared libraries. For set-user-ID/set-group-ID ELF binaries, only libraries in the standard search directories that are also set-user-ID will be loaded.        加载.so的顺序是LD_PRELOAD -> LD_LIBRARY_PATH -> /etc/ld.so.cache -> /lib -> /usr/lib。        LD_PRELOAD是一个环境变量,只要使用LD_PRELOAD=new.so把new.so的路径加入到这个环境变量中,就会优先加载。若没有效果,打印echo $LD_PRELOAD看看环境变量是否设置成功。在redhat上似乎需要先把new.so的读写权限全部开启,再设置环境变量。所添加的路径最好用绝对路径。        有一个奇怪现象,即使环境变量已经设置成功,在运行宿主程序之前还要加上这句话,原因未知。例如:LD_PRELOAD=/root/new.so /root/test 三、对LD_PRELOAD方法的改进 LD_PRELOAD方法非常方便灵活,动态链接库编好之后,可以只在需要调用的时候才把它放进去。但它也有缺点,它不能处理malloc出来的内存。不能在new.so中重载malloc,因为new.so最终要调用malloc来分配空间,如果在new.so中自己定义一个malloc会陷入调用的死循环。 Malloc还是要通过方法一来重载,在test的wrap.cpp中处理,最终调用__real_malloc来分配内存。这样,对new出来的内存和对malloc出来的内存的处理方法不统一——一个在test的wrap.cpp中处理,一个在new.so中的new.cpp中处理。 为了统一处理,最好是让new.so的operator new也使用wrap.cpp中的__wrap_malloc。那些对内存统计的操作,统一由__wrap_malloc来处理。 把operator new用__wrap_malloc()来实现,编译能通过,但在运行时会提示“找不到符号__wrap_malloc”,因为new.so不能从challenge中找到所需要的接口。幸好发现了ld提供的—export-dynamic,参考man ld: -E --export-dynamic When creating a dynamically linked executable, add all symbols to  the  dynamic  symbol  table.   The dynamic symbol table is the set of symbols which are visible from dynamic objects at run time.        If  you  do  not  use  this option, the dynamic symbol table will normally contain only those symbols which are referenced by some dynamic object mentioned in the link.        If you use "dlopen" to load a dynamic object which needs to refer back to the symbols defined by  the program,  rather  than some other dynamic object, then you will probably need to use this option when linking the program itself.        You can also use the dynamic list to control what symbols should be added to the dynamic symbol table if the output format supports it.  See the description of dynamic-list. 在宿主程序中加入这样一个链接选项LFLAGS += -Wl,-E,这样new.so就可以访问challenge中的函数__wrap_malloc了。 测试代码: main.cpp #include <iostream> #include <stdlib.h> #include <stdio.h> using namespace std; extern "C"{ void *__warp_malloc(size_t c); } /* extern "C" { void* __real_malloc(int c); void* __wrap_malloc(int c) { void *ret = __real_malloc(c); printf("__wrap_malloc c = %d\n", c); // cout<<"__wrap_malloc size = "<<c<<endl; // void *ret = NULL; return ret; } }*/ int main() { while(1) { #ifdef SO_NEW int *t2 = (int*)malloc(sizeof(int)); int *t = NULL; t = new int; #elif defined SO_MALLOC int *t = NULL; t = (int*)malloc(sizeof(int)); #endif #if 1 *t = 5; cout<<*t<<endl; #endif sleep(1); } return 0; } wrap.cpp #include <iostream> #include <stdio.h> //#include "heap.h" using namespace std; extern "C" { void* __real_malloc(size_t c); void* __wrap_malloc(size_t c) { void *ret = __real_malloc(c); printf("__wrap_malloc c = %d\n", c); // cout<<"__wrap_malloc size = "<<c<<endl; // void *ret = NULL; return ret; } } makefile LFLAGS += -Wl,--wrap,malloc #LFLAGS += -lmylib #LFLAGS += -static LFLAGS += -Wl,-E #CFLAGS += -DSO_MALLOC CFLAGS += -DSO_NEW objects=wrap.o main.o test:$(objects) # mips-linux-gnu-g++ $(LFLAGS) -g -o test $(objects) g++ $(LFLAGS) -g -o test $(objects) %.o:%.cpp # mips-linux-gnu-g++ $(CFLAGS) -g -c $< g++ $(CFLAGS) -g -c $< clean: rm test -f rm *.o -f new.cpp #include <exception> #include <new> #include <cstdlib> #include <cstdio> extern "C" { void *__wrap_malloc(int c); } /* extern "C" { void *__real_malloc(int c); void *__wrap_malloc(int c) { // printf("wrap_malloc\n"); void *p = __real_malloc(c); return p; } } */ void* operator new(size_t size) { // printf("operator new %d\n", size); // void *p=__wrap_malloc(size); void *p=__wrap_malloc((int)size); // if (p == 0) // did malloc succeed? // throw std::bad_alloc(); // ANSI/ISO compliant behavior // void *p = NULL; return p; } /* void* malloc(size_t size) { printf("operator malloc\n"); void *p = NULL; return p; } */ build #! make clean make #g++ -el -Wall -fPIC -shared -Wl,--wrap,malloc -o new.so new.cpp #mips-linux-gnu-g++ -EL -Wall -fPIC -shared -o new.so new.cpp g++ -Wall -fPIC -shared -o new.so new.cpp #sudo xcp new.so /usr/lib/ #LD_PRELOEAD=./new.so\ ./test  

返回顶部
查看电脑版