Debugging Android JNI with CheckJNI (用CheckJNI来调试Android JNI代码)

本文介绍了使用CheckJNI工具来帮助开发者找出Android应用程序中JNI相关的常见错误。CheckJNI能够捕捉到如数组分配错误、指针错误、类名错误等多种问题,并且在Android姜饼系统中已经集成了该工具。此外,文章提供了如何启用CheckJNI的方法,以及一些典型错误示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

[原文出处] https://android-developers.googleblog.com/2011/07/debugging-android-jni-with-checkjni.html
Debugging Android JNI with CheckJNI
19 July 2011
[This post is by Elliott Hughes, a Software Engineer on the Dalvik team — Tim Bray]
作者是Dalvik团队的软件工程师
Although most Android apps run entirely on top of Dalvik, some use the Android NDK to include native code using JNI. Native code is harder to get right than Dalvik code, and when you have a bug, it’s often a lot harder to find and fix it. Using JNI is inherently tricky (there’s precious little help from the type system, for example), and JNI functions provide almost no run-time checking. Bear in mind also that the developer console’s crash reporting doesn’t include native crashes, so you don’t even necessarily know how often your native code is crashing.
虽然大多数Android应用程序完全在Dalvik之上运行,但有一些使用Android NDK来包含使用JNI的本地代码。本地代码比Dalvik 代码更难写对,当你有个bug的时候 ,找到并解决它经常是很困难的。使用JNI很棘手(例如:只能从类型系统中获取到珍贵而可怜的帮助),同时JNI函数几乎不提供运行时检查。也要记住开发者控制台的崩溃报告不包括本地代码的崩溃日志,所以你都甚至不必知道你的本地代码多长时间崩溃一次。

What CheckJNI can do
CheckJNI 能做什么

To help, there’s CheckJNI. It can catch a number of common errors, and the list is continually increasing. In Gingerbread, for example, CheckJNI can catch all of the following kinds of error:
为了帮助JNI的开发,所以有了CheckJNI这个工具。它能捕获很多的常见错误,并且捕获错误列表还在不断增加。在姜饼系统中,它可以捕获下列所有错误:

Arrays: attempting to allocate a negative-sized array.
数组:试图分配一个负值大小的数组

Bad pointers: passing a bad jarray/jclass/jobject/jstring to a JNI call, or passing a NULL pointer to a JNI call with a non-nullable argument.
不良指针:传一个错误的 jarray/jclass/jobject/jstring 的指针给一个JNI调用,或者传递一个空指针给一个参数不能为空的JNI调用

Class names: passing anything but the “java/lang/String” style of class name to a JNI call.
类名字:传递任何类名给一个JNI调用 ,除了“java/lang/String”类型的类名字

Critical calls: making a JNI call between a GetCritical and the corresponding ReleaseCritical.
关键调用:在GetCritical和相应的ReleaseCritical之间进行JNI调用。

Direct ByteBuffers: passing bad arguments to NewDirectByteBuffer.
直接ByteBuffers:将错误的参数传递给NewDirectByteBuffer。

Exceptions: making a JNI call while there’s an exception pending.
异常:在异常挂起时进行JNI调用。

JNIEnv*s: using a JNIEnv* from the wrong thread.
JNIEvn s:在错误的线程中使用JNIEnv

jfieldIDs: using a NULL jfieldID, or using a jfieldID to set a field to a value of the wrong type (trying to assign a StringBuilder to a String field, say), or using a jfieldID for a static field to set an instance field or vice versa, or using a jfieldID from one class with instances of another class.
jfieldIDs:使用NULL jfieldID,或使用jfieldID将字段设置为错误类型的值(尝试将StringBuilder分配给String字段),或使用jfieldID作为静态字段来设置实例字段,或反之亦然,或者使用来自一个具有另一个类的实例的类的jfieldID。

jmethodIDs: using the wrong kind of jmethodID when making a Call*Method JNI call: incorrect return type, static/non-static mismatch, wrong type for ‘this’ (for non-static calls) or wrong class (for static calls).
jmethodIDs:使用错误类型的jmethodID进行JNI方法调用:不正确的返回类型,静态/非静态不匹配,’this’错误类型(非静态调用)或错误类(静态调用)。

References: using DeleteGlobalRef/DeleteLocalRef on the wrong kind of reference.
引用错误:在错误的引用类型上使用DeleteGlobalRef/DeleteLocalRef

Release modes: passing a bad release mode to a release call (something other than 0, JNI_ABORT, or JNI_COMMIT).
发布模式:将不良发布模式传递给发布调用(除0,JNI_ABORT或JNI_COMMIT之外的其他内容)。

Type safety: returning an incompatible type from your native method (returning a StringBuilder from a method declared to return a String, say).
类型安全:从你的本地方法中返回一个类型不兼容的返回值(从一个声明返回String的方法中返回一个StringBuilder)

UTF-8: passing an invalid Modified UTF-8 byte sequence to a JNI call.
UTF-8:将无效的修改后的UTF-8字节序列传递给JNI调用。

If you’ve written any amount of native code without CheckJNI, you’re probably already wishing you’d known about it. There’s a performance cost to using CheckJNI (which is why it isn’t on all the time for everybody), but it shouldn’t change the behavior in any other way.
如果你在没有CheckJNI的情况下编写了大量的本地代码,那么你可能已经希望你要是早知道它多好啊。 使用CheckJNI有一个性能成本(这就是为什么它不是为每个人都在运行),但它不应该以任何其他方式改变行为。

Enabling CheckJNI
If you’re using the emulator, CheckJNI is on by default. If you’re working with an Android device, use the following adb command:
如果你正在使用模拟器:CheckJNI默认就是打开的。如果你正使用一台Android设备,使用下面的adb命令打开它:

adb shell setprop debug.checkjni 1

This won’t affect already-running apps, but any app launched from that point on will have CheckJNI enabled. (Changing the property to any other value or simply rebooting will disable CheckJNI again.) In this case, you’ll see something like this in your logcat output the next time each app starts:
这不会影响已经运行的应用程序,但从这一点开始的任何应用程序都将启用CheckJNI。 (将属性更改为任何其他值或仅简单地重新启动设备将会再次禁用CheckJNI。)在这种情况下,下次每个应用程序启动时,您将在logcat输出中看到类似的内容:

D Late-enabling CheckJNI

If you don’t see this, your app was probably already running; you just need to force stop it and start it again.
如果你没有看到这个,可能你的应用已经正在运行了,你只要把它强制关闭然后重新启动它就可以了。

Example
Here’s the output you get if you return a byte array from a native method declared to return a String:
如果从声明为返回String的本地方法返回一个字节数组,则可以获得以下输出:

W JNI WARNING: method declared to return 'Ljava/lang/String;' returned '[B'
W              failed in LJniTest;.exampleJniBug
I "main" prio=5 tid=1 RUNNABLE
I   | group="main" sCount=0 dsCount=0 obj=0x40246f60 self=0x10538
I   | sysTid=15295 nice=0 sched=0/0 cgrp=default handle=-2145061784
I   | schedstat=( 398335000 1493000 253 ) utm=25 stm=14 core=0
I   at JniTest.exampleJniBug(Native Method)
I   at JniTest.main(JniTest.java:11)
I   at dalvik.system.NativeStart.main(Native Method)
I 
E VM aborting

Without CheckJNI, you’d just die via SIGSEGV, with none of this output to help you!
没有CheckJNI,没有这个输出来帮助你,你会死于SIGSEGV!

New JNI documentation

We’ve also recently added a page of JNI Tips that explains some of the finer points of JNI. If you write native methods, even if CheckJNI isn’t rejecting your code, you should still read that page. It covers everything from correct usage of the JavaVM and JNIEnv types, how to work with native threads, local and global references, dealing with Java exceptions in native code, and much more, including answers to frequently-asked JNI questions.
我们最近还添加了一个JNI Tips的页面,解释了JNI的一些更精细的点。 如果你编写本地方法,即使CheckJNI不拒绝你的代码,你仍然应该阅读该页面。 它涵盖了从正确使用JavaVM和JNIEnv类型,如何使用本机线程,本地和全局引用,处理本机代码中的Java异常等等所有的一切,包括常见JNI问题的答案。

What CheckJNI can’t do

There are still classes of error that CheckJNI can’t find. Most important amongst these are misuses of local references. CheckJNI can spot if you stash a JNIEnv* somewhere and then reuse it on the wrong thread, but it can’t detect you stashing a local reference (rather than a global reference) and then reusing it in a later native method call. Doing so is invalid, but currently mostly works (at the cost of making life hard for the GC), and we’re still working on getting CheckJNI to spot these mistakes.
仍然有CheckJNI找不到的错误种类。 其中最重要的是局部引用的滥用。 CheckJNI可以发现某个地方是否存在JNIEnv *,然后将其重新使用在错误的线程上,但是它无法检测到你占用局部引用(而不是全局引用),然后在后来的本地方法调用中重用它。 这样做是无效的,但目前大多数工作(代价是使GC的生存变得艰难),我们仍在努力让CheckJNI发现这些错误。

We’re hoping to have more checking, including for local reference misuse, in a future release of Android. Start using CheckJNI now, though, and you’ll be able to take advantage of our new checks as they’re added.
我们希望在未来的Android版本中进行更多的检查,包括局部引用滥用。 不过,现在开始使用CheckJNI,你以后就可以利用我们新的检查,当他们被添加进去的时候。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值