Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8238676

jni crashes on accessing it from process exit hook

    Details

    • Subcomponent:
    • Resolved In Build:
      b13
    • CPU:
      x86_64
    • OS:
      windows_10

      Backports

        Description

        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.

        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;
        }

          Attachments

            Issue Links

              Activity

                People

                • Assignee:
                  dholmes David Holmes
                  Reporter:
                  ant Anton Tarasov
                • Votes:
                  0 Vote for this issue
                  Watchers:
                  5 Start watching this issue

                  Dates

                  • Created:
                    Updated:
                    Resolved: