一、打印调用栈
程序开发和调试很多时候都离不开调用栈的打印,尤其是调试复杂的程序,比如异步程序时或基于回调的程序时,看代码还是挺费时间的,效率还不高。
这时打印一下调用栈往往事半功倍。
在linux上打印调用栈有backtrace, backtrace_symbols, backtrace_symbols_fd等函数可以用,在shell中man backtrace就可以看到相关解释。
在android中则有 / ,用法简单: android::CallStack cs; cs.update(2); cs.dump();
update函数会调用_Unwind_Backtrace来获得调用栈,这时调用栈只是一些函数地址。
只有调用dump时,才会将这些函数地址转换成可读性比较好的函数名,文件名,甚至行号等。
dump先尝试用dladdr来将函数地址转换成函数名(在c++中是被mangling过的),如果成功,则对其进行demangling,得到函数的原型。
如果dladdr失败,则会读取进程的maps文件,解析该文件,得到进程中被加载的动态库的地址范围,然后根据要转换的函数地址,找到对应的动态库,将该动态库的路径打印出来。
二、检测内存泄漏
内存泄漏的检测如果没有工具的支持估计只能靠阅读代码和猜了。
linux上有valgrind,已经比较成熟了,它在android上也同样有效,用NDK将其交叉编译之后就可以使用了。
本人在项目中使用过的另外一种方法是,用android sdk中的bionic里面提供的memory leak检测工具。
这两个文件中包含了我们所需要的工具。
具体用法是:
(1) 将头文件dumpnativeheap.h 加入到源文件中。
(2) 在合适的位置调用dumpNativeHeapToFile(char *pFile),这个函数会将当前时刻堆上的内存分配情况以及进程的maps保存到你指定的文件中。
这里所说的合适的位置需要开发人员自己定,比如你想测试一个播放引擎有没有内存泄漏,则可以在播放之前,调用一次,在播放结束之后,再调用一次。这样就会得到两个文件。
(3)交叉编译你的程序,并推送到android设备上。
(4) 重启系统,使其使用libc_malloc_debug_leak.so,而不是libc.so。
adb shell setprop libc.debug.malloc 1 =》 将malloc库的debug level设置成memory leak detection
adb shell stop
adb shell start
(5)在android 上运行你的程序,如(2)所说,你将得到两个文件。(6)将这两个文件做diff,得到一个diff文件。
(7)将第二个文件的maps部分拷贝到一个新文件中。这里我们基本上可以认为两个文件的maps是相同的。比如我们要测试播放引擎的内存泄漏,我们在dump的时候一般是在所需的动态库都加载完之后哦才dump,所以两个maps应该是相同的。
(8)把你的可执行程序或动态库(注意,这里应该都是debug版本,即带有符号表信息的版本)拷贝到android SDK的target symbol 目录下。
比如android/4.0.0_sdk/out/target/product/generic/symbols/data/memleaktest, 这里的/data/memleaktest和android设备上我们的可执行程序的路径一致。
(9)执行网上的脚本