jni crashes on exit

Anton Tarasov anton.tarasov at jetbrains.com
Tue Feb 4 12:42:34 UTC 2020


Hello,

I'm experiencing a crash in JNI with jdk11(15) on MS Windows. I was able to
reproduce it with a standalone test case.

In short: a java app loads a native lib and forces exit, the native lib
registers an exit handler where it successfully obtains JNIEnv and calls a
function on it.

Please find the compile instructions and the code below.

Build and run the app:

$ java JavaMain

- - - - - - - - - -
In: JNI_OnLoad
In: at_exit_handler
calling: jvm->GetEnv: JNI_EDETACHED
calling: jvm->AttachCurrentThreadAsDaemon: JNI_OK [env != NULL,
env->functions == 00000000]
#
# A fatal error has been detected by the Java Runtime Environment:
#
#  EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x00007ff9bb9165a6,
pid=13912, tid=13020
#
# JRE version: OpenJDK Runtime Environment (15.0+8) (build 15-ea+8-219)
# Java VM: OpenJDK 64-Bit Server VM (15-ea+8-219, mixed mode, sharing,
tiered, compressed oops, g1 gc, windows-amd64)
# Problematic frame:
# C  [main.dll+0x65a6]
#
- - - - - - - - - -

The problem here is that AttachCurrentThreadAsDaemon returns JNI_OK and
non-null JNIEnv* but null env->functions.

The stack trace I obtained is this (JVM is not yet destroyed, there's only
one thread left alive):

- - - - - - - - - -
  main.dll!at_exit_handler() Line 19 C++
  main.dll!__crt_seh_guarded_call<int>::operator()<void <lambda>(void),int
<lambda>(void) & __ptr64,void <lambda>(void)
>(__acrt_lock_and_call::__l3::void <lambda>(void) && setup,
_execute_onexit_table::__l22::int <lambda>(void) & action,
__acrt_lock_and_call::__l4::void <lambda>(void) && cleanup) Line 199 C++
  main.dll!_execute_onexit_table(_onexit_table_t * table) Line 222 C++
  main.dll!common_exit(const int return_code, const _crt_exit_cleanup_mode
cleanup_mode, const _crt_exit_return_mode return_mode) Line 215 C++
  [External Code]
  jvm.dll!os::win32::exit_process_or_thread(os::win32::Ept what, int
exit_code) Line 3987 C++
  jvm.dll!VM_Operation::evaluate() Line 68 C++
  jvm.dll!VMThread::evaluate_operation(VM_Operation * op) Line 414 C++
  jvm.dll!VMThread::loop() Line 548 C++
  jvm.dll!VMThread::run() Line 315 C++
  jvm.dll!Thread::call_run() Line 393 C++
  jvm.dll!thread_native_entry(Thread * thread) Line 460 C++
  [External Code]
- - - - - - - - - -

Now run the app with "skip_exit" option.

$ java JavaMain skip_exit

- - - - - - - - - -
In: JNI_OnLoad
In: at_exit_handler
calling: jvm->GetEnv: JNI_EDETACHED
calling: jvm->AttachCurrentThreadAsDaemon: JNI_ERR
- - - - - - - - - -

Now it's ok, the stack trace (JVM has already been destroyed):

- - - - - - - - - -
  main.dll!at_exit_handler() Line 25 C++
  main.dll!__crt_seh_guarded_call<int>::operator()<void <lambda>(void),int
<lambda>(void) & __ptr64,void <lambda>(void)
>(__acrt_lock_and_call::__l3::void <lambda>(void) && setup,
_execute_onexit_table::__l22::int <lambda>(void) & action,
__acrt_lock_and_call::__l4::void <lambda>(void) && cleanup) Line 199 C++
  main.dll!_execute_onexit_table(_onexit_table_t * table) Line 222 C++
  main.dll!common_exit(const int return_code, const _crt_exit_cleanup_mode
cleanup_mode, const _crt_exit_return_mode return_mode) Line 215 C++
  [External Code]
  java.exe!00007ff6fd4213d7() Unknown
  [External Code]
- - - - - - - - - -

Obtaining JNIEnv from the process exit handler may seem useless, however
the situation is possible when a chain of destructors are called on exit,
causing JNI wrappers to release its refs without checking in which
circumstances the destructor is called (as was the real app case). I'd
expect that JVM always returns JNI_ERR in such a case.

Should I file a bug?

With best regards,
Anton.

INSTRUCTIONS and CODE:

//
// build main.dll with ms cl.exe
//
$ "c:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat"
amd64
$ cl /I %JDK_REPO%/src/java.base/share/native/include /I
%JDK_REPO%/src/java.base/windows/native/include
%JDK_REPO%/build/windows-x86_64-normal-server-release/images/jdk/lib/jvm.lib
/Z7 /LD main.cpp

//
// JavaMain.java
//
public class JavaMain {
    public static void main(String[] args) {
        System.loadLibrary("main");
        if (args.length == 0 || !args[0].equals("skip_exit")) {
            System.exit(0);
        }
    }
}

//
// main.cpp
//
#include <jni.h>
#include <stdio.h>
#include <stdlib.h>

static JavaVM *jvm;

void at_exit_handler() {
    fprintf(stdout, "In: at_exit_handler\n");
    JNIEnv *env;
    fprintf(stdout, "calling: jvm->GetEnv");
    jint res = jvm->GetEnv((void**)&env, JNI_VERSION_1_6);
    if (res == JNI_EDETACHED) {
        fprintf(stdout, ": JNI_EDETACHED\ncalling:
jvm->AttachCurrentThreadAsDaemon");
        res = jvm->AttachCurrentThreadAsDaemon((void **)&env, NULL);
    }
    if (res == JNI_OK) {
        if (env) {
            fprintf(stderr, ": JNI_OK [env != NULL, env->functions ==
%08x]\n", env->functions);
            jint ver = env->GetVersion(); // <-- CRASH !!!
            fprintf(stderr, "env->GetVersion() %d\n", ver);
        } else if (!env) {
            fprintf(stdout, ": JNI_OK [env == NULL]\n");
        }
    } else {
        fprintf(stderr, ": JNI_ERR\n");
    }
}

jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
    fprintf(stdout, "In: JNI_OnLoad\n");

//    fprintf(stdout, "press ENTER to continue...\n");
//    getchar();

    jvm = vm;
    atexit(at_exit_handler);

    return JNI_VERSION_1_6;
}


More information about the hotspot-runtime-dev mailing list