Mac OS X开发之内存泄漏测试
Xcode提供了Instruments工具用于对应用程序进行各种性能相关的测试,其中也包含内存泄漏测试,但它是GUI程序,不便于进行自动化测试,所以这里暂不关注它。以后会有机会详解它的使用方法。
Xcode另带了一个命令行工具leaks,是专为内存泄漏测试而生的。Mac OS X 10.7及以后的版本,操作系统也自带leaks命令。本文将着重介绍它的使用方法。
1. 原理
执行man leaks可以看到leaks工具的帮助。leaks的命令行如下:
leakspid |partial-executable-name [-nocontext] [-nostacks] [-excludesymbol] [-traceaddress]
最主要输入参数是一个进程名或PID,也就是说被测试进程必须是正在运行的。帮助中还简要的描述了leaks的工作原理。
leaks identifies leaked memory -- memory that the application has allocated, but has been lost and cannot be freed. Specifically,leaks examines a specified process"s memory for values that may be pointers to malloc-allocated buffers. Any buffer reachable from a pointer in writable global memory (e.g., __DATA segments), a register, or on the stack is assumed to be memory in use. Any buffer reachable from a pointer in a reachable malloc-allocated buffer is also assumed to be in use. The buffers which are not reachable are leaks; the buffers could never be freed because no pointer exists in memory to the buffer, and thus free() could never be called for these buffers. Such buffers waste memory; removing them can reduce swapping and memory usage. Leaks are particularly dangerous for long-running programs, for eventually the leaks could fill memory and cause the application to crash.
简单地说这个命令会检查被测进程地址空间里的每一个分配的内存块,如果没有任何指针指向某个内存块,就认为这是一个被遗忘的内存,最后会把它报告为泄漏的内存块。这也是很合理的,没有指针指向它,就意味着它不会再有机会被释放,自然就是泄漏掉的了。所以理论上讲,它不会有误报。
leaks命令的工作原理决定了在使用它时会有一些小陷阱。稍有不慎,就会导致不能正确地检测到泄漏。
2. 测试普通程序
首先,它要求被测试程序当时还在运行,对于长时间运行的程序来说,这不是问题,但是对于运行时间只有几秒或一秒都不到的程序,这就是问题了。我们项目实际测试的时候,碰到的就是这个问题。解决办法是,修改被测程序,增加一个命令行参数"-ChkMemLeak",在指定该参数时,程序中在即将退出之前sleep两秒钟,然后用以下方式运行程序和leaks命令。
MallocStackLogging=1 MallocScribble=1 ./TargetApp -ChkMemLeak & leaks TargetApp > MemLeak.log这儿的测试方法就是用后台方式运行被测程序,因为已经在程序中sleep两秒钟,这样随即运行的leaks命令就可以找到相应的进程进行内存扫描。
但这样leaks是不是就能精准地找到所有的内存泄漏呢?如果leaks进入的过早,被测程序还在执行工作逻辑,判断有没有内存泄漏就为时过早了。因此上述方法只能保证leaks能找到被测程序,但无法保证正确性。根据这个需求,再次优化被测程序,在sleep之前,生成一个ChkMemLeakGo.txt文件,以表明自己已经完成工作逻辑,是时候评判是是否有内存泄漏了。整个测试过程调整如下:
MallocStackLogging=1 MallocScribble=1 ./TargetApp -ChkMemLeak & while [ ! -f ./ChkMemLeakGo.txt ] do echo wait > /dev/null done leaks TargetApp > MemLeak.log
如此一来,就相当精准地让leaks在最合适的时机进入被测进程进行检测。
运气好的话,找到的泄漏点是发生在主模块中,在MemLeak.log中可以找到完整的栈回溯,可以据此很容易的找到需要修改的代码段。但是当找到的泄漏点是发生在一个library里的时候,有时MemLeak.log中给出的栈回溯没有符号,看起来像这个样子。根据这样的栈回溯,想找到相关的代码,就要大费周折了。
Leak: 0x102007e00 size=4096 zone: DefaultMallocZone_0x100118000 0x00000000 0x00000000 0x00000000 0x00000000 ................ ... Call stack: [thread 0x7fff77df8300]: | 0x8 | start | main main.cpp:135 | 0x10800230 | 0x10800389 | 0x10800478 | malloc | malloc_zone_malloc
结合leaks的工作原理,不难想到其中原由。因为leaks进入的太晚,虽然能看到library里泄漏的内存块,但是library已经不在内存中,就无从知晓栈回溯上各个函数地址对应的symbol了。
为了解决这个问题,试过让leaks进入的时机适当提早,也就是在library被unload掉之前执行leaks。但这样一来,有一些之前能看到的泄漏就看不到了,原因是library还在,相应的内存块还被某些指针引用着,自然就不算泄漏了。于是我们就限入了这样一个困局,如果leaks进入太早,就看不到所有泄漏,如果进入太晚,能看到所有泄漏,但看不到library的符号。
最终想到的解决办法是:在即将退出程序,sleep两秒钟之前,重新load刚刚被unload掉的library。这样,因为library已经被unload过一次,该发生的泄漏已经发生;而且此时library还在内存,leaks能够正确地用library的符号表示栈回溯。
3. 测试Launch Daemon
- 上一篇: sizeof 计算发生在编译时
- 下一篇: C语言中 sizeof 运算的值是在编译时还是运行时确定?