使用Valgrind找出Android中Native程序内存泄露问题

Android程序通常使用Java程序编写,由于Dalvik虚拟机集成了垃圾回收机制,所以内存使用比较不容易出错,通常就是一个本该被释放的对象却被另一个对象长时间持有着。对于这类问题,可以使用MAT工具,在Eclipse下结合DDMS进行分析。

但是,目前任然有很多Android的应用程序,出于性能或者是安全的考虑,还包含了通过JNI调用的Native程序。这些Native程序使用C或C++语言编写,并没有Java语言的垃圾回收机制,而且可以使用指针直接对内存进行操作,因此更容易出现内存泄露、缓冲区溢出等内存使用方面的错误。对于这类问题,也有工具可以帮忙查找,它就是Valgrind。

安装Valgrind

Valgrind是基于源代码发布的,官网上并没有编译好的直接给Android平台的版本可以使用,因此需要自己编译。笔者预先编译了一个版本,可以从这里拿到。

将压缩包解包,里面包含有许多文件,安装步骤如下:

1)确保你是root用户;

2)请将名字为“valgrind”的文件,拷贝到“/system/xbin”目录下;

3)在“/system/lib”目录下创建一个名为“valgrind”的新目录,并将压缩包中剩下的所有文件都拷贝到这个目录下。

4)将以上所有文件的权限属性都改成775。

特别说明,如果/system目录在系统启动时,被mount成read only模式的话,请用read write模式重新mount一下就行了:

mount -o remount,rw /system

运行Valgrind

如果想用Valgrind工具进行测试,必须满足两个要求:

1)被测程序最好是Debug版本的(当然release版本的也可以,但是发现问题后定位问题代码会比较麻烦);

2)被测试程序必须要由Valgrind工具来启动。

另外,目前Valgrind只能在支持armv7及以上指令集的ARM CPU上工作,所以不能在较老的设备上运行。

测试可执行文件

如果你要测试的是一个可执行的原生程序的话,那么非常简单,只需要直接键入以下命令:

valgrind <YOUR PROGRAM NAME> -v --error-limit=no --trace-children=yes --track-fds=yes --log-fie=/sdcard/<YOUR PROGRAM NAME>.%p.valgrind.log --tool=memcheck --leak-check=full --track-origins=yes

稍微说明一下这些参数的意思:

1)       -v或者--verbose,如果带这个参数的话,那么Valgrind会将测试过程中的详细信息打印出来;

2)       --eror-limit=<no|yes>,若为yes,则当这轮测试检测出了超过10,000,000个错误,或者有1,000个不同类型的错误的话,就停止报错;如果是no的话,则没有这个限制,无论有多少个错误或多少类型的错误,都会报告出来。默认,如果不特别指定的话,这个选项的值是yes;

3)       --trace-children=<no|yes>,如果是yes的话,则Valgrind不仅会检查被启动程序的主进程,还会检查由主进程fork出来的子进程。默认的值是no;

4)       --track-fds=<no|yes>,若为yes则Valgrind还会记录所有打开的文件句柄,这样有助于查找到潜在的句柄泄露问题,默认值是no;

5)       --log-file=<LOG FILE NAME>,通过设定该参数,将Valgrind发现的问题记录到指定的文件中。特别说明一下,参数中有一个“%p”的参数,Valgrind会自动将其替换成当前进程的ID。前面也提到了,Valgrind会追踪子进程,因此如果文件名不区分进程号的话,所有的问题都会被记录到一个文件中,会比较混乱,不利于后面的分析;

6)       --tool=<TOOL NAME>,Valgrind除了可以用来检查内存错误外,还有其它功能。所以,这个参数是指定Valgrind到底使用哪个分析工具。默认情况下,Valgrind缺省会运行memcheck工具。因此,这个参数指不指定无所谓;

7)       --leak-check=<no|summary|full>,如果设置成summary,则只会显示发现了多少个问题,不会显示细节;如果设置成yes或full,则会显示每个错误的细节。默认值是summary。

8)       --track-origins=<no|yes>,默认值是no,则Valgrind只知道某个变量是否未被初始化就被使用了,但并不知道这个未初始化变量定义在哪里。如果设置成yes,则会保存每个未初始化变量的位置;

当启动起来之后,让你的程序运行一会,这样可以尽可能多的发现潜在的问题。

特别提一下,Valgrind是一个动态扫描工具,只有在当有问题的代码被执行到的情况下,才能发现问题。这点完全不同于其它的一些静态代码扫描工具。

测试JNI调用的原生程序

如果你要测试的程序是一个JNI调用的原生程序的话,那么测试的方法会有一点复杂。

首先,要创建一个脚本,本例将其命名为“start_valgrind.sh”,内容如下:

#!/system/bin/sh

PACKAGE="<YOUR PACKAGE NAME>"

VGPARAMS='-v --error-limit=no --trace-children=yes --track-fds=yes --log-file=/sdcard/<YOUR PACKAGE NAME>.%p.valgrind.log --tool=memcheck --leak-check=full --track-origins=yes'

export TMPDIR=/data/data/$PACKAGE

exec valgrind $VGPARAMS $*

请将<YOUR PACKAGE NAME>替换成你要测试程序的包名。

然后,将这个脚本拷贝到设备上的“/data/local”目录下,并将其权限属性设置成775。

接下来,在设备上使用“setprop”命令,设置一个属性,这个属性的名字就是在你的包名前面加上“wrap.”

setprop wrap.<YOUR PACKAGE NAME> “logwrapper /data/local/start_valgrind.sh”

这个命令必须要在root用户下运行才能起效果。

一个Android应用程序在启动的时候会查找有没有属性名为“wrap.<本应用程序包名>”的属性。如果存在的话,则用这个属性的值来重新启动自己。但是,这种做法有一个小的限制,属性名不能够太长,不能超过31个字符,这也就意味着你应用程序的包名最好不要超过26字符,否则会面临被截断的问题。如果想详细了解其实现流程,可以参考这里

“logwrapper”是Android系统中自带的一个小程序,用来将后面参数中指定程序的标准输出重定向到logcat上。如需了解详情,请参考这里

在测试之前还要确保你要测试的应用程序没有在偷偷的在后台跑,可以使用下面的命令强制让它退出:

adb shell am force-stop <YOUR PACKAGE NAME>

好了,到此一切都准备就绪了,可以开始动手测试了,直接点击你应用程序的图标就行了。使用Valgrind对程序进行测试,会让程序启动和相应速度都慢很多,请耐心等待。这时,在“/sdcard”目录下,应该就可以看到Valgrind生成的测试日志了。

Example

下面举个例子来具体操作一下,这里使用Android-NDK中自带的,最简单的“hello-jni”范例程序,稍作一点修改。

在“hello-jni.c”文件中,故意埋下一些错误的内存使用,并在“Java_com_example_hellojni_HelloJni_stringFromJNI”函数中调用它们:

static void memory_overrun()
{
    char *p = malloc(1);
    *(short*)p = 2;

    free(p);
}

static void strcpy_overrun()
{
    char *p = malloc(sizeof(char) * 5);
    strcpy(p, "hello");
}

static void memory_free_wild_pointer()
{
    char *p;
    free(p);
}

jstring
Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env,
                                                  jobject thiz )
{
    …
memory_overrun();
strcpy_overrun();
memory_free_wild_pointer();
    return (*env)->NewStringUTF(env, "Hello from JNI !  Compiled with ABI " ABI ".");
}

还有一点,要特别强调一下,Valgrind是通过扫描程序退出之前的堆内存使用情况来判断有没有内存泄露问题的,所以你的程序必须要退出之后,Valgrind才会生成最终的测试报告。但是Android应用一般是不会退出的,只会切换到后台,因此可以在你代码适当的地方加上退出的代码(“System.exit(0)”),让你的程序正常退出。

最后,把Valgrind工具生成的完整日志打开来看看,发现了什么:

可以看到,三个问题都发现了,并且还指出了出错的位置,还是非常方便的。

当然,Valgrind也不是万能的,它说你的程序没问题,不代表真的没有问题。但是,比起阅读源代码来发现潜在问题来说,通过这种方法要容易许多。

  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值