Splaktar
Splaktar

Reputation: 5894

How do I diagnose my JNI memory corruption issue?

I have been debugging this issue for a couple days now without any luck. I must be missing something fairly obvious. I am running a Swing application packaged with the JavaFX 2.2 packaging tools and it makes a connection to a C .dll via JNI.

Everything was fine until I wanted to add a function for calling from C back into Java. When I did this, I started getting memory corruption issues. Here is the error, followed by my new JNI code:

#
# A fatal error has been detected by the Java Runtime Environment:
#
#  EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x7c82c912, pid=7424, tid=4828
#
# JRE version: Java(TM) SE Runtime Environment (7.0_40-b43) (build 1.7.0_40-b43)
# Java VM: Java HotSpot(TM) Client VM (24.0-b56 interpreted mode windows-x86 )
# Problematic frame:
# C  [ntdll.dll+0x2c912]
#
# Core dump written.
#
# If you would like to submit a bug report, please visit:
#   http://bugreport.sun.com/bugreport/crash.jsp
#

---------------  T H R E A D  ---------------

Current thread (0x008e9800):  JavaThread "main" [_thread_in_vm, id=4828, stack(0x00030000,0x00130000)]

siginfo: ExceptionCode=0xc0000005, reading address 0x00000000

Registers:
EAX=0x10d3b510, EBX=0x008e0000, ECX=0x00000000, EDX=0x00000000
ESP=0x000ff98c, EBP=0x000ff998, ESI=0x10d3b508, EDI=0x10d5d000
EIP=0x7c82c912, EFLAGS=0x00010246

Top of Stack: (sp=0x000ff98c)
0x000ff98c:   008e0000 00000008 008e0004 000ff9d0
0x000ff99c:   7c8338a2 00000000 10d5d000 000ff9c4
0x000ff9ac:   00000000 00001000 008e0178 008e0000
0x000ff9bc:   0cff0304 0706ff12 00001000 10a80000
0x000ff9cc:   00000000 000ffbfc 7c82b46b 038e0000
0x000ff9dc:   00008000 00007ff4 008e5458 00007ff4
0x000ff9ec:   7c829dc9 008e0178 008e0178 10c375c0
0x000ff9fc:   7c8274b9 77e6958b 000ffa2c 000ffa0c 

Instructions: (pc=0x7c82c912)
0x7c82c8f2:   3d 00 fe 00 00 0f 87 75 dc ff ff 80 7d 14 00 0f
0x7c82c902:   85 53 82 02 00 8b 4e 0c 8d 46 08 8b 10 89 4d 08
0x7c82c912:   8b 09 3b 4a 04 89 55 0c 0f 85 86 4f 01 00 3b c8
0x7c82c922:   0f 85 7e 4f 01 00 56 53 e8 c9 d6 ff ff 8b 45 0c 


Register to memory mapping:

EAX=0x10d3b510 is an unknown value
EBX=0x008e0000 is an unknown value
ECX=0x00000000 is an unknown value
EDX=0x00000000 is an unknown value
ESP=0x000ff98c is pointing into the stack for thread: 0x008e9800
EBP=0x000ff998 is pointing into the stack for thread: 0x008e9800
ESI=0x10d3b508 is an unknown value
EDI=0x10d5d000 is an unknown value


Stack: [0x00030000,0x00130000],  sp=0x000ff98c,  free space=830k
Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)
C  [ntdll.dll+0x2c912]
C  [ntdll.dll+0x338a2]
C  [ntdll.dll+0x2b46b]
C  [MSVCR100.dll+0x10269]
V  [jvm.dll+0x145f0c]
V  [jvm.dll+0x76d81]
V  [jvm.dll+0x76f8c]
V  [jvm.dll+0x772ea]
V  [jvm.dll+0x8b674]
V  [jvm.dll+0x188b8a]
V  [jvm.dll+0x156226]
V  [jvm.dll+0x48950]
V  [jvm.dll+0x4b236]
V  [jvm.dll+0x4c094]
V  [jvm.dll+0x4c205]
V  [jvm.dll+0x9de75]
V  [jvm.dll+0xa3cae]
V  [jvm.dll+0xa3b20]
V  [jvm.dll+0xa6d30]
V  [jvm.dll+0xa72f8]
V  [jvm.dll+0x70dfe]
V  [jvm.dll+0x71666]
V  [jvm.dll+0x71927]
V  [jvm.dll+0x6dac0]
...

---------------  P R O C E S S  ---------------

Java Threads: ( => current thread )
  0x10c88400 JavaThread "Framework Connection" [_thread_in_native, id=5452, stack(0x117a0000,0x118a0000)]
  0x10c7e800 JavaThread "TimerQueue" daemon [_thread_blocked, id=5544, stack(0x11680000,0x11780000)]
  0x10bca800 JavaThread "AWT-EventQueue-0" [_thread_blocked, id=7748, stack(0x11560000,0x11660000)]
  0x10bbc800 JavaThread "Image Fetcher 0" daemon [_thread_blocked, id=808, stack(0x11460000,0x11560000)]
  0x10ab0400 JavaThread "AWT-Windows" daemon [_thread_in_native, id=3040, stack(0x112b0000,0x113b0000)]
  0x10b58800 JavaThread "AWT-Shutdown" [_thread_blocked, id=5728, stack(0x111b0000,0x112b0000)]
  0x0f56b800 JavaThread "Java2D Disposer" daemon [_thread_blocked, id=5440, stack(0x110b0000,0x111b0000)]
  0x0f52e000 JavaThread "Service Thread" daemon [_thread_blocked, id=7628, stack(0x10880000,0x10980000)]
  0x0f528400 JavaThread "C1 CompilerThread0" daemon [_thread_blocked, id=5092, stack(0x10780000,0x10880000)]
  0x0f526800 JavaThread "Attach Listener" daemon [_thread_blocked, id=1612, stack(0x10680000,0x10780000)]
  0x0f525000 JavaThread "Signal Dispatcher" daemon [_thread_blocked, id=8040, stack(0x10580000,0x10680000)]
  0x0f523800 JavaThread "Surrogate Locker Thread (Concurrent GC)" daemon [_thread_blocked, id=7456, stack(0x10480000,0x10580000)]
  0x0f513000 JavaThread "Finalizer" daemon [_thread_blocked, id=6404, stack(0x10380000,0x10480000)]
  0x0f50d000 JavaThread "Reference Handler" daemon [_thread_blocked, id=4892, stack(0x10280000,0x10380000)]
=>0x008e9800 JavaThread "main" [_thread_in_vm, id=4828, stack(0x00030000,0x00130000)]

Other Threads:
  0x0f50b800 VMThread [stack: 0x10180000,0x10280000] [id=5676]
  0x0f538c00 WatcherThread [stack: 0x10980000,0x10a80000] [id=3400]

VM state:not at safepoint (normal execution)

VM Mutex/Monitor currently owned by a thread: None

Heap
 par new generation   total 36864K, used 10756K [0x03380000, 0x05b80000, 0x05b80000)
  eden space 32768K,  32% used [0x03380000, 0x03e01100, 0x05380000)
  from space 4096K,   0% used [0x05380000, 0x05380000, 0x05780000)
  to   space 4096K,   0% used [0x05780000, 0x05780000, 0x05b80000)
 concurrent mark-sweep generation total 90112K, used 0K [0x05b80000, 0x0b380000, 0x0b380000)
 concurrent-mark-sweep perm gen total 12288K, used 7375K [0x0b380000, 0x0bf80000, 0x0f380000)

Card table byte_map: [0x00fa0000,0x01010000] byte_map_base: 0x00f86400

Polling page: 0x008f0000

Code Cache  [0x01080000, 0x010d0000, 0x03080000)
 total_blobs=184 nmethods=0 adapters=155 free_code_cache=32453Kb largest_free_block=33232576

Compilation events (0 events):
No events

GC Heap History (0 events):
No events

Deoptimization events (0 events):
No events

Internal exceptions (10 events):
Event: 0.547 Thread 0x008e9800 Threw 0x03996270 at C:\jdk7u2_32P\jdk7u40\hotspot\src\share\vm\prims\jni.cpp:717
Event: 1.082 Thread 0x008e9800 Threw 0x03b85978 at C:\jdk7u2_32P\jdk7u40\hotspot\src\share\vm\prims\jni.cpp:717
Event: 1.082 Thread 0x008e9800 Threw 0x03b85b10 at C:\jdk7u2_32P\jdk7u40\hotspot\src\share\vm\prims\jni.cpp:717
Event: 1.082 Thread 0x008e9800 Threw 0x03b85c78 at C:\jdk7u2_32P\jdk7u40\hotspot\src\share\vm\prims\jni.cpp:717
Event: 1.266 Thread 0x10d23400 Threw 0x03d44b08 at C:\jdk7u2_32P\jdk7u40\hotspot\src\share\vm\prims\jni.cpp:717
Event: 1.266 Thread 0x10d23400 Threw 0x03d44ca0 at C:\jdk7u2_32P\jdk7u40\hotspot\src\share\vm\prims\jni.cpp:717
Event: 1.266 Thread 0x10d23400 Threw 0x03d44e08 at C:\jdk7u2_32P\jdk7u40\hotspot\src\share\vm\prims\jni.cpp:717
Event: 2.082 Thread 0x008e9800 Threw 0x03b863f8 at C:\jdk7u2_32P\jdk7u40\hotspot\src\share\vm\prims\jni.cpp:717
Event: 2.082 Thread 0x008e9800 Threw 0x03b86590 at C:\jdk7u2_32P\jdk7u40\hotspot\src\share\vm\prims\jni.cpp:717
Event: 2.082 Thread 0x008e9800 Threw 0x03b866f8 at C:\jdk7u2_32P\jdk7u40\hotspot\src\share\vm\prims\jni.cpp:717

Events (10 events):
Event: 2.091 loading class 0x10d25cb0
Event: 2.091 loading class 0x10d25cb0 done
Event: 2.092 loading class 0x10d28388
Event: 2.092 loading class 0x10d28388 done
Event: 2.095 loading class 0x10d283e8
Event: 2.095 loading class 0x10d283e8 done
Event: 2.096 loading class 0x10c44670
Event: 2.096 loading class 0x10c44670 done
Event: 2.096 loading class 0x10d27cc8
Event: 2.096 loading class 0x10d27cc8 done

I have a .h file to declare my globals:

extern jclass javaEntryPointClass;
extern jobject javaEntryPointObject;
extern JavaVM* cachedJVM;

I define my globals in my .c file:

// Required definition of the global variables declared in .h
jclass javaEntryPointClass = NULL;
jobject javaEntryPointObject = NULL;
JavaVM* cachedJVM = NULL;

I have a JNI_OnLoad function to save the pointer to the JavaVM:

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved)
{
    cachedJVM = jvm;

    return JNI_VERSION_1_6;
}

I have another function that is called from Java where I store away the pointer to the jclass:

JNIEXPORT jint JNICALL Java_com_foo_FrameworkServices_Connect(
    JNIEnv *env, jobject obj, jstring string)
{
    jclass cls1 = NULL;
    PSTR szCmdLine = NULL;
    jboolean isCopy = FALSE;

    const char *str = (*env)->GetStringUTFChars(env, string, &isCopy);
    szCmdLine = (CHAR*)str;

    cls1 = (*env)->GetObjectClass(env, obj);
    if (cls1 == NULL)
        return -1;

    javaEntryPointClass = (*env)->NewGlobalRef(env, cls1);
    if (javaEntryPointClass == NULL)
        return -2;

    javaEntryPointObject = (*env)->NewGlobalRef(env, obj);
    if (javaEntryPointObject == NULL)
        return -3;

    SomeLongRunningFunctionThatNeverEndsUntilTheProgramDoes(szCmdLine);

    (*env)->ReleaseStringUTFChars(env, string, str);

    return 0;
}

Then later in my native code, after all of my socket connections have been initialized and I'm ready to start accepting all JNI calls from Java, I use a callback method to let Java know that I am ready:

    JNIEnv *env = NULL;
    jmethodID mid = NULL;
    int envStatus = 0;
    int attached = 0;

    // Get a current handle to the JNI environment.
    envStatus = (*cachedJVM)->GetEnv(cachedJVM, (void **)&env, JNI_VERSION_1_6);
    if (envStatus == JNI_EDETACHED)
    {
        // If we're not attached, try to attach to the current thread.
        (*cachedJVM)->AttachCurrentThread(cachedJVM, (void **) &env, NULL);
            attached = 1;
    }

    // Make sure the JNIEnv object we have isn't NULL.
    if (env != NULL)
    {
            mid = (*env)->GetMethodID(env, javaEntryPointClass, "callback", "()V");
            if (mid != NULL)
            {
                    // Call Java to tell it that GUI is ready to process requests.
                    (*env)->CallVoidMethod(env, javaEntryPointObject, mid);
            }

            // Free the global references so that Java can garbage collect.
            if (javaEntryPointClass != NULL)
            {
                    (*env)->DeleteGlobalRef(env, javaEntryPointClass);
                    javaEntryPointClass = NULL;
            }
            if (javaEntryPointObject != NULL)
            {
                    (*env)->DeleteGlobalRef(env, javaEntryPointObject);
                    javaEntryPointObject = NULL;
            }
    }

    // Detach the current thread from the JavaVM. Must be done before exiting thread.
    if (attached == 1)
        (*cachedJVM)->DetachCurrentThread(cachedJVM);
...

Now I know functionally that this works. Functionally, my application is fine. But 1 out of 20 or so times, it will crash shortly after this native code completes. It looks like it is likely corrupting the heap every time it is run. But only sometimes does that corruption cause a crash.

What am I missing here? I'm deleting my global reference and nulling out the pointer. I'm attaching and detaching from the thread. Walking through the debugger, things look fairly good.

Upvotes: 1

Views: 3249

Answers (2)

Splaktar
Splaktar

Reputation: 5894

This turned out to be heap corruption many levels of libraries deep. All of the JNI code (after some helpful comments here) was clean and problem free.

The problem turned out to be the passing of an extra argument in a string. This string gets passed down a couple levels (C libraries) before being parsed and used to configure the backend processing. I was passing an argument that was unknown to the final library and rather than provide an error message, the library was corrupting the heap when trying to parse this string.

In the end, it had nothing to do with Java or JNI. It was just another artifact of building on top of poorly written underlying C libraries. This sort of thing is unfortunately common as time is not budgeted for clean up or refactoring. I'm just glad that I can go back to Java now :)

Thank you for your help with this. I really went through the JNI bits with a fine toothed comb and learned quite a bit.

Upvotes: 2

manuell
manuell

Reputation: 7620

IIRC, you must pass an object when you call an instance method. You may want to replace CallVoidMethod by CallStaticVoidMethod.

Ok, it seems your problem is not in the CallVoidMethod call. My advices:

  1. try to narrow down the problem by progressively commenting out portions of code
  2. process exception after each JNI call: Use ExceptionOccured, ExceptionDescribe and finally ExceptionClear.
  3. double check your usage of malloc/free (including strdup, new, delete...)

Upvotes: 5

Related Questions