Ogre Psalm33 Ogre Psalm33 - 9 months ago 68
C++ Question

JNI C++ Debugging Techniques?

I have a Linux C++ application that creates a JVM and makes JNI calls. I am new to JNI, and so far I the only effective way I have found to debug my application during development is by trial and error. What are some techniques to use to debug the infamous "A fatal error has been detected by the Java Runtime Environment" Java VM crashes? How do I know if the problem is my code or a genuine JVM bug?

In general, the obvious thing I know so far are:

  • In the code, always check jobject, class, and jmethodID values returned from JNI calls for NULL values before proceeding further.

  • Call env->ExceptionCheck() where appropriate to ensure there are no pending exceptions.

Currently, I'm stuck on an issue where the stack trace in the error report file is less than helpful:

# 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. ...

Ok, so I know that it's dying in env->CallObjectMethod(). I checked all the parameters to that in GDB before it dives into the JVM code, but I don't see any obvious NULL or strange values. And of course, all the JNI classes, such as jobject, are unhelpfully opaque, so I can't see if their pointers are pointing to bogus or real data.

Any tips/suggestions/ideas for this kind of problem?

Answer Source

Ok, so here's how I approached the problem I mentioned above. Somewhat tedious, but, given enough time and effort, it eventually paid off.

  1. Don't assume that env->CallMethod(jobj, meth_id, ...) is being passed correct values. If this is where it is crashing, chances are high that some hard-to-find but fundamental issue is at fault, such as the methodId being passed does not match the jobject being passed to CallObjectMethod(...). I wrote a simple helper method std::string getClassInfo(JNIEnv* env, jclass aJavaClass) that gets the MethodID for "toString" on a class, calls that method, and returns the result as a std::string. That told me weather an object was what I thought it was or not.
  2. Liberally sprinkle debug output statements between your JNI calls. Especially outputting class names (such as via the above method) will help you figure out weather objects are what you think they are.
  3. Make sure you're checking for null methodIDs and calling env->ExceptionCheck() after each CallMethod(...). Checking for null after CallMethod(...) won't help, because the JNI can't know if null is a valid return type.
  4. Don't assume that the JNI will crash at the first sign of trouble. I was actually passing the wrong object type through several JNI calls before it actually crashed. See #3 to make sure you catch the issue early.