/* * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package jdk.internal.loader; import jdk.internal.misc.VM; import jdk.internal.ref.CleanerFactory; import jdk.internal.util.StaticProperty; import java.io.File; import java.io.IOException; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.ArrayDeque; import java.util.Deque; import java.util.HashSet; import java.util.Objects; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; /** * Native libraries are loaded via {@link System#loadLibrary(String)}, * {@link System#load(String)}, {@link Runtime#loadLibrary(String)} and * {@link Runtime#load(String)}. They are caller-sensitive. * * Each class loader has a NativeLibraries instance to register all of its * loaded native libraries. System::loadLibrary (and other APIs) only * allows a native library to be loaded by one class loader, i.e. one * NativeLibraries instance. Any attempt to load a native library that * has already been loaded by a class loader with another class loader * will fail. */ public final class NativeLibraries { private final Map libraries = new ConcurrentHashMap<>(); private final ClassLoader loader; // caller, if non-null, is the fromClass parameter for NativeLibraries::loadLibrary // unless specified private final Class caller; // may be null private final boolean searchJavaLibraryPath; // loading JNI native libraries private final boolean isJNI; /** * Creates a NativeLibraries instance for loading JNI native libraries * via for System::loadLibrary use. * * 1. Support of auto-unloading. The loaded native libraries are unloaded * when the class loader is reclaimed. * 2. Support of linking of native method. See JNI spec. * 3. Restriction on a native library that can only be loaded by one class loader. * Each class loader manages its own set of native libraries. * The same JNI native library cannot be loaded into more than one class loader. * * This static factory method is intended only for System::loadLibrary use. * * @see * JNI Specification: Library and Version Management */ public static NativeLibraries jniNativeLibraries(ClassLoader loader) { return new NativeLibraries(loader); } /** * Creates a raw NativeLibraries instance that has the following properties: * 1. Native libraries loaded in this raw NativeLibraries instance are * not JNI native libraries. Hence JNI_OnLoad and JNI_OnUnload will * be ignored. No support for linking of native method. * 2. Native libraries not auto-unloaded. They may be explicitly unloaded * via NativeLibraries::unload. * 3. No relationship with class loaders. * * This static factory method is restricted for JDK trusted class use. */ public static NativeLibraries rawNativeLibraries(Class trustedCaller, boolean searchJavaLibraryPath) { return new NativeLibraries(trustedCaller, searchJavaLibraryPath); } private NativeLibraries(ClassLoader loader) { // for null loader, default the caller to this class and // do not search java.library.path this.loader = loader; this.caller = loader != null ? null : NativeLibraries.class; this.searchJavaLibraryPath = loader != null ? true : false; this.isJNI = true; } /* * Constructs a NativeLibraries instance of no relationship with class loaders * and disabled auto unloading. */ private NativeLibraries(Class caller, boolean searchJavaLibraryPath) { Objects.requireNonNull(caller); if (!VM.isSystemDomainLoader(caller.getClassLoader())) { throw new IllegalArgumentException("must be JDK trusted class"); } this.loader = caller.getClassLoader(); this.caller = caller; this.searchJavaLibraryPath = searchJavaLibraryPath; this.isJNI = false; } /* * Find the address of the given symbol name from the native libraries * loaded in this NativeLibraries instance. */ public long find(String name) { if (libraries.isEmpty()) return 0; // the native libraries map may be updated in another thread // when a native library is being loaded. No symbol will be // searched from it yet. for (NativeLibrary lib : libraries.values()) { long entry = lib.find(name); if (entry != 0) return entry; } return 0; } /* * Load a native library from the given file. Returns null if file does not exist. * * @param fromClass the caller class calling System::loadLibrary * @param file the path of the native library * @throws UnsatisfiedLinkError if any error in loading the native library */ public NativeLibrary loadLibrary(Class fromClass, File file) { // Check to see if we're attempting to access a static library String name = findBuiltinLib(file.getName()); boolean isBuiltin = (name != null); if (!isBuiltin) { name = AccessController.doPrivileged(new PrivilegedAction<>() { public String run() { try { return file.exists() ? file.getCanonicalPath() : null; } catch (IOException e) { return null; } } }); if (name == null) { return null; } } return loadLibrary(fromClass, name, isBuiltin); } /** * Returns a NativeLibrary of the given name. * * @param fromClass the caller class calling System::loadLibrary * @param name library name * @param isBuiltin built-in library * @throws UnsatisfiedLinkError if the native library has already been loaded * and registered in another NativeLibraries */ private NativeLibrary loadLibrary(Class fromClass, String name, boolean isBuiltin) { ClassLoader loader = (fromClass == null) ? null : fromClass.getClassLoader(); if (this.loader != loader) { throw new InternalError(fromClass.getName() + " not allowed to load library"); } synchronized (loadedLibraryNames) { // find if this library has already been loaded and registered in this NativeLibraries NativeLibrary cached = libraries.get(name); if (cached != null) { return cached; } // cannot be loaded by other class loaders if (loadedLibraryNames.contains(name)) { throw new UnsatisfiedLinkError("Native Library " + name + " already loaded in another classloader"); } /* * When a library is being loaded, JNI_OnLoad function can cause * another loadLibrary invocation that should succeed. * * We use a static stack to hold the list of libraries we are * loading because this can happen only when called by the * same thread because this block is synchronous. * * If there is a pending load operation for the library, we * immediately return success; otherwise, we raise * UnsatisfiedLinkError. */ for (NativeLibraryImpl lib : nativeLibraryContext) { if (name.equals(lib.name())) { if (loader == lib.fromClass.getClassLoader()) { return lib; } else { throw new UnsatisfiedLinkError("Native Library " + name + " is being loaded in another classloader"); } } } NativeLibraryImpl lib = new NativeLibraryImpl(fromClass, name, isBuiltin, isJNI); // load the native library nativeLibraryContext.push(lib); try { if (!lib.open()) { return null; // fail to open the native library } // auto unloading is only supported for JNI native libraries // loaded by custom class loaders that can be unloaded. // built-in class loaders are never unloaded. boolean autoUnload = isJNI && !VM.isSystemDomainLoader(loader) && loader != ClassLoaders.appClassLoader(); if (autoUnload) { // register the loaded native library for auto unloading // when the class loader is reclaimed, all native libraries // loaded that class loader will be unloaded. // The entries in the libraries map are not removed since // the entire map will be reclaimed altogether. CleanerFactory.cleaner().register(loader, lib.unloader()); } } finally { nativeLibraryContext.pop(); } // register the loaded native library loadedLibraryNames.add(name); libraries.put(name, lib); return lib; } } /** * Loads a native library from the system library path and java library path. * * @param name library name * * @throws UnsatisfiedLinkError if the native library has already been loaded * and registered in another NativeLibraries */ public NativeLibrary loadLibrary(String name) { assert name.indexOf(File.separatorChar) < 0; assert caller != null; return loadLibrary(caller, name); } /** * Loads a native library from the system library path and java library path. * * @param name library name * @param fromClass the caller class calling System::loadLibrary * * @throws UnsatisfiedLinkError if the native library has already been loaded * and registered in another NativeLibraries */ public NativeLibrary loadLibrary(Class fromClass, String name) { assert name.indexOf(File.separatorChar) < 0; NativeLibrary lib = findFromPaths(LibraryPaths.SYS_PATHS, fromClass, name); if (lib == null && searchJavaLibraryPath) { lib = findFromPaths(LibraryPaths.USER_PATHS, fromClass, name); } return lib; } /** * Unloads the given native library * * @param lib native library */ public void unload(NativeLibrary lib) { if (isJNI) { throw new UnsupportedOperationException("explicit unloading cannot be used with auto unloading"); } Objects.requireNonNull(lib); synchronized (loadedLibraryNames) { NativeLibraryImpl nl = libraries.remove(lib.name()); if (nl != lib) { throw new IllegalArgumentException(lib.name() + " not loaded by this NativeLibraries instance"); } // unload the native library and also remove from the global name registry nl.unloader().run(); } } private NativeLibrary findFromPaths(String[] paths, Class fromClass, String name) { for (String path : paths) { File libfile = new File(path, System.mapLibraryName(name)); NativeLibrary nl = loadLibrary(fromClass, libfile); if (nl != null) { return nl; } libfile = ClassLoaderHelper.mapAlternativeName(libfile); if (libfile != null) { nl = loadLibrary(fromClass, libfile); if (nl != null) { return nl; } } } return null; } /** * NativeLibraryImpl denotes a loaded native library instance. * Each NativeLibraries contains a map of loaded native libraries in the * private field {@code libraries}. * * Every native library requires a particular version of JNI. This is * denoted by the private {@code jniVersion} field. This field is set by * the VM when it loads the library, and used by the VM to pass the correct * version of JNI to the native methods. */ static class NativeLibraryImpl implements NativeLibrary { // the class from which the library is loaded, also indicates // the loader this native library belongs. final Class fromClass; // the canonicalized name of the native library. // or static library name final String name; // Indicates if the native library is linked into the VM final boolean isBuiltin; // Indicate if this is JNI native library final boolean isJNI; // opaque handle to native library, used in native code. long handle; // the version of JNI environment the native library requires. int jniVersion; NativeLibraryImpl(Class fromClass, String name, boolean isBuiltin, boolean isJNI) { assert !isBuiltin || isJNI : "a builtin native library must be JNI library"; this.fromClass = fromClass; this.name = name; this.isBuiltin = isBuiltin; this.isJNI = isJNI; } @Override public String name() { return name; } @Override public long find(String name) { return findEntry0(this, name); } Runnable unloader() { return new Unloader(name, handle, isBuiltin, isJNI); } /* * Loads the named native library */ boolean open() { if (handle != 0) { throw new InternalError("Native library " + name + " has been loaded"); } return load(this, name, isBuiltin, isJNI); } } /* * The run() method will be invoked when this class loader becomes * phantom reachable to unload the native library. */ static class Unloader implements Runnable { // This represents the context when a native library is unloaded // and getFromClass() will return null, static final NativeLibraryImpl UNLOADER = new NativeLibraryImpl(null, "dummy", false, false); final String name; final long handle; final boolean isBuiltin; final boolean isJNI; Unloader(String name, long handle, boolean isBuiltin, boolean isJNI) { assert !isBuiltin || isJNI : "a builtin native library must be JNI library"; if (handle == 0) { throw new IllegalArgumentException( "Invalid handle for native library " + name); } this.name = name; this.handle = handle; this.isBuiltin = isBuiltin; this.isJNI = isJNI; } @Override public void run() { synchronized (loadedLibraryNames) { /* remove the native library name */ if (!loadedLibraryNames.remove(name)) { throw new IllegalStateException(name + " has already been unloaded"); } nativeLibraryContext.push(UNLOADER); try { unload(name, isBuiltin, isJNI, handle); } finally { nativeLibraryContext.pop(); } } } } /* * Holds system and user library paths derived from the * {@code java.library.path} and {@code sun.boot.library.path} system * properties. The system properties are eagerly read at bootstrap, then * lazily parsed on first use to avoid initialization ordering issues. */ static class LibraryPaths { // The paths searched for libraries static final String[] SYS_PATHS = ClassLoaderHelper.parsePath(StaticProperty.sunBootLibraryPath()); static final String[] USER_PATHS = ClassLoaderHelper.parsePath(StaticProperty.javaLibraryPath()); } // All native libraries we've loaded. // This also serves as the lock to obtain nativeLibraries // and write to nativeLibraryContext. private static final Set loadedLibraryNames = new HashSet<>(); // native libraries being loaded private static Deque nativeLibraryContext = new ArrayDeque<>(8); // Invoked in the VM to determine the context class in JNI_OnLoad // and JNI_OnUnload private static Class getFromClass() { if (nativeLibraryContext.isEmpty()) { // only default library return Object.class; } return nativeLibraryContext.peek().fromClass; } // JNI FindClass expects the caller class if invoked from JNI_OnLoad // and JNI_OnUnload is NativeLibrary class private static native boolean load(NativeLibraryImpl impl, String name, boolean isBuiltin, boolean isJNI); private static native void unload(String name, boolean isBuiltin, boolean isJNI, long handle); private static native String findBuiltinLib(String name); private static native long findEntry0(NativeLibraryImpl lib, String name); }