OpenJDK System. Analysis of loadlibrary source code

Posted by drkylec on Tue, 04 Jan 2022 16:36:38 +0100

OpenJDK System. Analysis of loadlibrary source code

System.loadLibrary is used to inform the JVM to load Native so. After so is loaded successfully, you can see that so has been loaded into memory in / proc/self/maps.
Students familiar with system layer development can guess that this is basically equivalent to dlopen/LoadLibrary call. Next, let's analyze it through the OpenJDK source code.
Download the OpenJDK source code: https://github.com/openjdk/jdk
tag: jdk8-b120

1. System.java

Search loadlibrary and find that the runtime has been transferred getRuntime(). loadLibrary0.

public static void loadLibrary(String libname) {
    Runtime.getRuntime().loadLibrary0(Reflection.getCallerClass(), libname);
}

2. Runtime.java

Search loadLibrary0. After passing the security detection and illegal path detection, transfer to classloader loadLibrary.

synchronized void loadLibrary0(Class<?> fromClass, String libname) {
    SecurityManager security = System.getSecurityManager();
    if (security != null) {
        security.checkLink(libname);
    }
    if (libname.indexOf((int)File.separatorChar) != -1) {
        throw new UnsatisfiedLinkError(
"Directory separator should not appear in library name: " + libname);
    }
    ClassLoader.loadLibrary(fromClass, libname, false);
}

3. ClassLoader.java

Search loadLibrary. After searching through various paths, loadLibrary0 is finally called.

static void loadLibrary(Class<?> fromClass, String name,
                        boolean isAbsolute) {
    ClassLoader loader =
        (fromClass == null) ? null : fromClass.getClassLoader();
    if (sys_paths == null) {
        usr_paths = initializePath("java.library.path");
        sys_paths = initializePath("sun.boot.library.path");
    }
    if (isAbsolute) {
        if (loadLibrary0(fromClass, new File(name))) {
            return;
        }
        throw new UnsatisfiedLinkError("Can't load library: " + name);
    }
    if (loader != null) {
        String libfilename = loader.findLibrary(name);
        if (libfilename != null) {
            File libfile = new File(libfilename);
            if (!libfile.isAbsolute()) {
                throw new UnsatisfiedLinkError(
"ClassLoader.findLibrary failed to return an absolute path: " + libfilename);
            }
            if (loadLibrary0(fromClass, libfile)) {
                return;
            }
            throw new UnsatisfiedLinkError("Can't load " + libfilename);
        }
    }
    for (int i = 0 ; i < sys_paths.length ; i++) {
        File libfile = new File(sys_paths[i], System.mapLibraryName(name));
        if (loadLibrary0(fromClass, libfile)) {
            return;
        }
        libfile = ClassLoaderHelper.mapAlternativeName(libfile);
        if (libfile != null && loadLibrary0(fromClass, libfile)) {
            return;
        }
    }
    if (loader != null) {
        for (int i = 0 ; i < usr_paths.length ; i++) {
            File libfile = new File(usr_paths[i],
                                    System.mapLibraryName(name));
            if (loadLibrary0(fromClass, libfile)) {
                return;
            }
            libfile = ClassLoaderHelper.mapAlternativeName(libfile);
            if (libfile != null && loadLibrary0(fromClass, libfile)) {
                return;
            }
        }
    }
    // Oops, it failed
    throw new UnsatisfiedLinkError("no " + name + " in java.library.path");
}

4. ClassLoader.java

Search loadLibrary0. First call NativeLibrary findBuiltinLib judges whether it has been loaded (findBuiltinLib is located in ClassLoader.c, and finally calls dlsym for judgment). After judging through various permission paths, it synchronously calls lib.load, that is, the load method of NativeLibrary.

private static boolean loadLibrary0(Class<?> fromClass, final File file) {
    // Check to see if we're attempting to access a static library
    String name = NativeLibrary.findBuiltinLib(file.getName());
    boolean isBuiltin = (name != null);
    if (!isBuiltin) {
        boolean exists = AccessController.doPrivileged(
            new PrivilegedAction<Object>() {
                public Object run() {
                    return file.exists() ? Boolean.TRUE : null;
                }})
            != null;
        if (!exists) {
            return false;
        }
        try {
            name = file.getCanonicalPath();
        } catch (IOException e) {
            return false;
        }
    }
    ClassLoader loader =
        (fromClass == null) ? null : fromClass.getClassLoader();
    Vector<NativeLibrary> libs =
        loader != null ? loader.nativeLibraries : systemNativeLibraries;
    synchronized (libs) {
        int size = libs.size();
        for (int i = 0; i < size; i++) {
            NativeLibrary lib = libs.elementAt(i);
            if (name.equals(lib.name)) {
                return true;
            }
        }

        synchronized (loadedLibraryNames) {
            if (loadedLibraryNames.contains(name)) {
                throw new UnsatisfiedLinkError
                    ("Native Library " +
                        name +
                        " already loaded in another classloader");
            }
            /* If the library is being loaded (must be by the same thread,
                * because Runtime.load and Runtime.loadLibrary are
                * synchronous). The reason is can occur is that the JNI_OnLoad
                * function can cause another loadLibrary invocation.
                *
                * Thus we can use a static stack to hold the list of libraries
                * we are loading.
                *
                * If there is a pending load operation for the library, we
                * immediately return success; otherwise, we raise
                * UnsatisfiedLinkError.
                */
            int n = nativeLibraryContext.size();
            for (int i = 0; i < n; i++) {
                NativeLibrary lib = nativeLibraryContext.elementAt(i);
                if (name.equals(lib.name)) {
                    if (loader == lib.fromClass.getClassLoader()) {
                        return true;
                    } else {
                        throw new UnsatisfiedLinkError
                            ("Native Library " +
                                name +
                                " is being loaded in another classloader");
                    }
                }
            }
            NativeLibrary lib = new NativeLibrary(fromClass, name, isBuiltin);
            nativeLibraryContext.push(lib);
            try {
                lib.load(name, isBuiltin);
            } finally {
                nativeLibraryContext.pop();
            }
            if (lib.loaded) {
                loadedLibraryNames.addElement(name);
                libs.addElement(lib);
                return true;
            }
            return false;
        }
    }
}

5. ClassLoader.java

NativeLibrary is defined in classloader In Java, the load method is declared as native

native void load(String name, boolean isBuiltin);

6. ClassLoader.c

In classloader Find the definition of the load function in C.

  • If isBuiltin is true, procHandle is called
  • If isBuiltin is false, the JVM is called_ LoadLibrary

Then find JNI in so through findJniFunction_ Onload export function, call it. That is, when writing code, if JNI is defined in C/C + +_ Onload function and export. When the library is loaded, JNI will be called back_ The reason for onload.

/*
 * Class:     java_lang_ClassLoader_NativeLibrary
 * Method:    load
 * Signature: (Ljava/lang/String;Z)V
 */
JNIEXPORT void JNICALL
Java_java_lang_ClassLoader_00024NativeLibrary_load
  (JNIEnv *env, jobject this, jstring name, jboolean isBuiltin)
{
    const char *cname;
    jint jniVersion;
    jthrowable cause;
    void * handle;

    if (!initIDs(env))
        return;

    cname = JNU_GetStringPlatformChars(env, name, 0);
    if (cname == 0)
        return;
    handle = isBuiltin ? procHandle : JVM_LoadLibrary(cname);
    if (handle) {
        JNI_OnLoad_t JNI_OnLoad;
        JNI_OnLoad = (JNI_OnLoad_t)findJniFunction(env, handle,
                                               isBuiltin ? cname : NULL,
                                               JNI_TRUE);
        if (JNI_OnLoad) {
            JavaVM *jvm;
            (*env)->GetJavaVM(env, &jvm);
            jniVersion = (*JNI_OnLoad)(jvm, NULL);
        } else {
            jniVersion = 0x00010001;
        }

        cause = (*env)->ExceptionOccurred(env);
        if (cause) {
            (*env)->ExceptionClear(env);
            (*env)->Throw(env, cause);
            if (!isBuiltin) {
                JVM_UnloadLibrary(handle);
            }
            goto done;
        }

        if (!JVM_IsSupportedJNIVersion(jniVersion) ||
            (isBuiltin && jniVersion < JNI_VERSION_1_8)) {
            char msg[256];
            jio_snprintf(msg, sizeof(msg),
                         "unsupported JNI version 0x%08X required by %s",
                         jniVersion, cname);
            JNU_ThrowByName(env, "java/lang/UnsatisfiedLinkError", msg);
            if (!isBuiltin) {
                JVM_UnloadLibrary(handle);
            }
            goto done;
        }
        (*env)->SetIntField(env, this, jniVersionID, jniVersion);
    } else {
        cause = (*env)->ExceptionOccurred(env);
        if (cause) {
            (*env)->ExceptionClear(env);
            (*env)->SetLongField(env, this, handleID, (jlong)0);
            (*env)->Throw(env, cause);
        }
        goto done;
    }
    (*env)->SetLongField(env, this, handleID, ptr_to_jlong(handle));
    (*env)->SetBooleanField(env, this, loadedID, JNI_TRUE);

 done:
    JNU_ReleaseStringPlatformChars(env, name, cname);
}

7. ClassLoader.c

isBuiltin is true. In the initIDs function, the variable procHandle is assigned getProcessHandle()

static jboolean initIDs(JNIEnv *env)
{
    if (handleID == 0) {
        jclass this =
            (*env)->FindClass(env, "java/lang/ClassLoader$NativeLibrary");
        if (this == 0)
            return JNI_FALSE;
        handleID = (*env)->GetFieldID(env, this, "handle", "J");
        if (handleID == 0)
            return JNI_FALSE;
        jniVersionID = (*env)->GetFieldID(env, this, "jniVersion", "I");
        if (jniVersionID == 0)
            return JNI_FALSE;
        loadedID = (*env)->GetFieldID(env, this, "loaded", "Z");
        if (loadedID == 0)
             return JNI_FALSE;
        procHandle = getProcessHandle();
    }
    return JNI_TRUE;
}

8. jni_util_md.c

Continue when isBuiltin is true. In JNI_ util_ Finding the definition of getProcessHandle in md.c is actually equivalent to calling dlopen. The first parameter is NULL, which means that the global symbol table of the process is opened. Later dlsym will find it in the global symbol table, and the second parameter is RTLD_LAZY means delayed binding. The operating system relocates the function address only when a function call actually occurs, which can save the time of relocating the exported function address.

void* getProcessHandle() {
    static void *procHandle = NULL;
    if (procHandle != NULL) {
        return procHandle;
    }
    procHandle = (void*)dlopen(NULL, RTLD_LAZY);
    return procHandle;
}

9. jvm.cpp

isBuiltin is false. Call to JVM_LoadLibrary, after parameter judgment and other operations, transfer os::dll_load.
Next, call different systems into their implementation files:

  • Os_ in Linux linux. OS:: Linux:: dlopen in CPP_ Helper, and finally call dlopen(filename, RTLD_LAZY)
  • Os_ in Windows windows. CPP, and finally call LoadLibrary(name)
  • Os_ in Solars solaris. CPP, and finally call dlopen(filename, RTLD_LAZY)
  • Os_ in BSD bsd. CPPP, and finally call dlopen(filename, RTLD_LAZY)

Note: in Unix like Linux, Solars and BSD systems, dlopen is finally set and RTLD is set_ Lazy delay loading. Windows also has dll delay loading technology, which can delay loading the whole dll, but it can only be realized through the Link process control of the compiler, and the library needs to be imported dynamically. However, the LoadLibrary function cannot delay loading. From the source code of OpenJDK, it can be seen that delay loading is not started for windows, Therefore, it is less efficient to load the shared library in windows (all global variables and functions of Linux and other systems are exported by default, while Windows Dll is not exported by default, and the export needs to be specified manually, so the amount is generally not large).

JVM_ENTRY_NO_ENV(void*, JVM_LoadLibrary(const char* name))
  //%note jvm_ct
  JVMWrapper2("JVM_LoadLibrary (%s)", name);
  char ebuf[1024];
  void *load_result;
  {
    ThreadToNativeFromVM ttnfvm(thread);
    load_result = os::dll_load(name, ebuf, sizeof ebuf);
  }
  if (load_result == NULL) {
    char msg[1024];
    jio_snprintf(msg, sizeof msg, "%s: %s", name, ebuf);
    // Since 'ebuf' may contain a string encoded using
    // platform encoding scheme, we need to pass
    // Exceptions::unsafe_to_utf8 to the new_exception method
    // as the last argument. See bug 6367357.
    Handle h_exception =
      Exceptions::new_exception(thread,
                                vmSymbols::java_lang_UnsatisfiedLinkError(),
                                msg, Exceptions::unsafe_to_utf8);

    THROW_HANDLE_0(h_exception);
  }
  return load_result;
JVM_END

Topics: Java JDK jvm Back-end