JNI C++调试技巧?

6
我有一个Linux C++应用程序,创建了一个JVM并进行JNI调用。 我对JNI不太熟悉,到目前为止,我发现在开发期间唯一有效的调试应用程序的方法是通过不断试错。 有什么技巧可以用来调试臭名昭著的“Java Runtime Environment发现致命错误”Java虚拟机崩溃? 我如何知道问题是我的代码还是真正的JVM错误?
通常,我所知道的明显事情有:
- 在代码中,在继续进一步之前,始终检查从JNI调用返回的jobject,class和jmethodID值是否为NULL值。 - 在适当的地方调用env->ExceptionCheck()以确保没有未决的异常。
目前,我陷入了一个问题,即错误报告文件中的堆栈跟踪不太有用:
# A fatal error has been detected by the Java Runtime Environment:
#
#  SIGSEGV (0xb) at pc=0x00002b137a99db59, pid=19977, tid=47362673452544
#
# JRE version: 6.0_20-b02
# Java VM: Java HotSpot(TM) 64-Bit Server VM (16.3-b01 mixed mode linux-amd64 )
# Problematic frame:
# V  [libjvm.so+0x40fb59]
  ... <snip> ...
Stack: [0x00007fff1964f000,0x00007fff1974f000],  sp=0x00007fff1974e050,  free space=3fc0000000000000018k
Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)
V  [libjvm.so+0x40fb59]
V  [libjvm.so+0x3ecbe1]
C  [libDataFabric.so+0x1bb5b]  _Jv_JNIEnv::CallObjectMethod(__jobject*, _jmethodID*, ...)+0xe3
etc. ...

好的,我知道问题出在env->CallObjectMethod()这里。在代码跳进JVM之前,我已经在GDB中检查了所有参数,但是没有看到任何明显的NULL或奇怪的值。当然,所有的JNI类,比如jobject,都是不透明的,所以我无法看到它们的指针是指向虚假数据还是真实数据。

针对这种问题,有什么提示/建议/想法吗?


一般来说,更安全的做法是假设错误在你的代码中,而不是在JVM中。 - Eric
3
请注意,在Linux上,JVM本身使用SEGV信号来表示垃圾收集器应该运行。我在gdb中使用“handle SIGSEGV pass noprint nostop”命令,让JVM处理这些事情。 - Edric
@Eric - 那自然是最好的假设。尽管似乎有帮助的崩溃输出会微妙地鼓励您提交错误报告:"...如果您想提交错误报告,请访问:http://java.sun.com/webapps/bugreport/crash.jsp" - Ogre Psalm33
我觉得这个问题应该标记为“社区维基”,因为我不确定是否有一个正确的答案。但是我不知道如何更改它 :-(。 - Ogre Psalm33
编辑它,然后会有一个小复选框可以将其更改为社区维基。 - Peter C
谢谢@Edric!将其发布为答案。我以为我一直遇到随机崩溃,这让我非常沮丧,试图弄清楚原因。我认为你所建议的就是它;它没有崩溃,但它正在发送SIGSEGV信号,我的GDB脚本因此退出。自从我添加了“handle SIGSEGV”以来,我还没有注意到虚假崩溃。 - Loduwijk
2个回答

5

好的,这是我处理上面提到的问题的方法。虽然有些繁琐,但是只要花足够的时间和精力,最终一定会有收获。

  1. 不要假设env->CallMethod(jobj, meth_id, ...)传递的值是正确的。如果程序在这里崩溃,很可能是因为某个难以发现但却非常基本的问题,比如传递的methodId与CallObjectMethod(...)中传递的jobject不匹配。我编写了一个简单的帮助方法std::string getClassInfo(JNIEnv* env, jclass aJavaClass),用于获取类上"toString"方法的MethodID,调用该方法,并将结果作为std::string返回。这告诉我对象是否是我认为的那个对象。
  2. 在JNI调用之间大量添加调试输出语句。特别是输出类名(例如通过上面的方法)将帮助您确定对象是否是您认为的那个对象。
  3. 确保检查null methodIDs并在每个CallMethod(...)之后调用env->ExceptionCheck()。在CallMethod(...)之后检查null是没有用的,因为JNI无法知道null是否是有效的返回类型。
  4. 不要假设JNI在出现问题的第一个迹象时就会崩溃。实际上,在它实际崩溃之前,我通过了几个错误的对象类型。请参见#3,以确保您及早捕捉到问题。

1
这是一篇博客文章,可以帮助你做相反的事情,通过JNI dump找到发生在虚拟机中的行号。对于我来说,这对于调试JNI代码非常有用:http://wig-wag.com/devblog/?p=51 - EdH
2
这是@EdH链接的正确网址:http://www.wigwag.com/devblog/making-sense-of-a-jni-stacktrace/ - pyrho

3

需要注意的是,在Linux环境下,JVM会使用SEGV信号来指示垃圾回收器运行。我在gdb中使用“handle SIGSEGV pass noprint nostop”让JVM处理这些事情。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接