| /* | |
| * Copyright (c) 2020, 2024, 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.util.ArrayDeque; | |
| import java.util.Deque; | |
| import java.util.function.BiFunction; | |
| import java.util.function.Function; | |
| import java.util.Map; | |
| import java.util.Set; | |
| import java.util.concurrent.ConcurrentHashMap; | |
| import java.util.concurrent.locks.ReentrantLock; | |
| /** | |
| * 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 static final boolean loadLibraryOnlyIfPresent = ClassLoaderHelper.loadLibraryOnlyIfPresent(); | |
| private final Map<String, NativeLibraryImpl> 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; | |
| /** | |
| * 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 <a href="${docroot}/specs/jni/invocation.html##library-and-version-management"> | |
| * JNI Specification: Library and Version Management</a> | |
| */ | |
| public static NativeLibraries newInstance(ClassLoader loader) { | |
| return new NativeLibraries(loader, loader != null ? null : NativeLibraries.class, loader != null); | |
| } | |
| private NativeLibraries(ClassLoader loader, Class<?> caller, boolean searchJavaLibraryPath) { | |
| this.loader = loader; | |
| this.caller = caller; | |
| this.searchJavaLibraryPath = searchJavaLibraryPath; | |
| } | |
| /* | |
| * 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 the given | |
| * library is determined to be non-loadable, which is system-dependent. | |
| * | |
| * @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) { | |
| try { | |
| if (loadLibraryOnlyIfPresent && !file.exists()) { | |
| return null; | |
| } | |
| name = file.getCanonicalPath(); | |
| } catch (IOException e) { | |
| 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"); | |
| } | |
| acquireNativeLibraryLock(name); | |
| try { | |
| // 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. | |
| * | |
| * Each thread maintains its own stack to hold the list of | |
| * libraries it is loading. | |
| * | |
| * If there is a pending load operation for the library, we | |
| * immediately return success; if the pending load is from | |
| * a different class loader, we raise UnsatisfiedLinkError. | |
| */ | |
| for (NativeLibraryImpl lib : NativeLibraryContext.current()) { | |
| 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); | |
| // 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 = !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; | |
| } finally { | |
| releaseNativeLibraryLock(name); | |
| } | |
| } | |
| /** | |
| * 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; | |
| 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; | |
| } | |
| 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 extends 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; | |
| // 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) { | |
| this.fromClass = fromClass; | |
| this.name = name; | |
| this.isBuiltin = isBuiltin; | |
| } | |
| @Override | |
| public String name() { | |
| return name; | |
| } | |
| @Override | |
| public long find(String name) { | |
| return findEntry0(handle, name); | |
| } | |
| /* | |
| * Unloader::run method is invoked to unload the native library | |
| * when this class loader becomes phantom reachable. | |
| */ | |
| private Runnable unloader() { | |
| return new Unloader(name, handle, isBuiltin); | |
| } | |
| /* | |
| * Loads the named native library | |
| */ | |
| boolean open() { | |
| if (handle != 0) { | |
| throw new InternalError("Native library " + name + " has been loaded"); | |
| } | |
| return load(this, name, isBuiltin, throwExceptionIfFail()); | |
| } | |
| private boolean throwExceptionIfFail() { | |
| if (loadLibraryOnlyIfPresent) return true; | |
| // If the file exists but fails to load, UnsatisfiedLinkException thrown by the VM | |
| // will include the error message from dlopen to provide diagnostic information | |
| File file = new File(name); | |
| return file.exists(); | |
| } | |
| /* | |
| * Close this native library. | |
| */ | |
| void close() { | |
| unload(name, isBuiltin, handle); | |
| } | |
| } | |
| /* | |
| * 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); | |
| final String name; | |
| final long handle; | |
| final boolean isBuiltin; | |
| Unloader(String name, long handle, boolean isBuiltin) { | |
| if (handle == 0) { | |
| throw new IllegalArgumentException( | |
| "Invalid handle for native library " + name); | |
| } | |
| this.name = name; | |
| this.handle = handle; | |
| this.isBuiltin = isBuiltin; | |
| } | |
| @Override | |
| public void run() { | |
| acquireNativeLibraryLock(name); | |
| try { | |
| /* remove the native library name */ | |
| if (!loadedLibraryNames.remove(name)) { | |
| throw new IllegalStateException(name + " has already been unloaded"); | |
| } | |
| NativeLibraryContext.push(UNLOADER); | |
| try { | |
| unload(name, isBuiltin, handle); | |
| } finally { | |
| NativeLibraryContext.pop(); | |
| } | |
| } finally { | |
| releaseNativeLibraryLock(name); | |
| } | |
| } | |
| } | |
| /* | |
| * 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. | |
| private static final Set<String> loadedLibraryNames = | |
| ConcurrentHashMap.newKeySet(); | |
| // reentrant lock class that allows exact counting (with external synchronization) | |
| @SuppressWarnings("serial") | |
| private static final class CountedLock extends ReentrantLock { | |
| private int counter = 0; | |
| public void increment() { | |
| if (counter == Integer.MAX_VALUE) { | |
| // prevent overflow | |
| throw new Error("Maximum lock count exceeded"); | |
| } | |
| ++counter; | |
| } | |
| public void decrement() { | |
| --counter; | |
| } | |
| public int getCounter() { | |
| return counter; | |
| } | |
| } | |
| // Maps native library name to the corresponding lock object | |
| private static final Map<String, CountedLock> nativeLibraryLockMap = | |
| new ConcurrentHashMap<>(); | |
| private static void acquireNativeLibraryLock(String libraryName) { | |
| nativeLibraryLockMap.compute(libraryName, | |
| new BiFunction<>() { | |
| public CountedLock apply(String name, CountedLock currentLock) { | |
| if (currentLock == null) { | |
| currentLock = new CountedLock(); | |
| } | |
| // safe as compute BiFunction<> is executed atomically | |
| currentLock.increment(); | |
| return currentLock; | |
| } | |
| } | |
| ).lock(); | |
| } | |
| private static void releaseNativeLibraryLock(String libraryName) { | |
| CountedLock lock = nativeLibraryLockMap.computeIfPresent(libraryName, | |
| new BiFunction<>() { | |
| public CountedLock apply(String name, CountedLock currentLock) { | |
| if (currentLock.getCounter() == 1) { | |
| // unlock and release the object if no other threads are queued | |
| currentLock.unlock(); | |
| // remove the element | |
| return null; | |
| } else { | |
| currentLock.decrement(); | |
| return currentLock; | |
| } | |
| } | |
| } | |
| ); | |
| if (lock != null) { | |
| lock.unlock(); | |
| } | |
| } | |
| // native libraries being loaded | |
| private static final class NativeLibraryContext { | |
| // Maps thread object to the native library context stack, maintained by each thread | |
| private static Map<Thread, Deque<NativeLibraryImpl>> nativeLibraryThreadContext = | |
| new ConcurrentHashMap<>(); | |
| // returns a context associated with the current thread | |
| private static Deque<NativeLibraryImpl> current() { | |
| return nativeLibraryThreadContext.computeIfAbsent( | |
| Thread.currentThread(), | |
| new Function<>() { | |
| public Deque<NativeLibraryImpl> apply(Thread t) { | |
| return new ArrayDeque<>(8); | |
| } | |
| }); | |
| } | |
| private static NativeLibraryImpl peek() { | |
| return current().peek(); | |
| } | |
| private static void push(NativeLibraryImpl lib) { | |
| current().push(lib); | |
| } | |
| private static void pop() { | |
| // this does not require synchronization since each | |
| // thread has its own context | |
| Deque<NativeLibraryImpl> libs = current(); | |
| libs.pop(); | |
| if (libs.isEmpty()) { | |
| // context can be safely removed once empty | |
| nativeLibraryThreadContext.remove(Thread.currentThread()); | |
| } | |
| } | |
| private static boolean isEmpty() { | |
| Deque<NativeLibraryImpl> context = | |
| nativeLibraryThreadContext.get(Thread.currentThread()); | |
| return (context == null || context.isEmpty()); | |
| } | |
| } | |
| // 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; | |
| } | |
| /* | |
| * Return true if the given library is successfully loaded. | |
| * If the given library cannot be loaded for any reason, | |
| * if throwExceptionIfFail is false, then this method returns false; | |
| * otherwise, UnsatisfiedLinkError will be thrown. | |
| * | |
| * 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 throwExceptionIfFail); | |
| /* | |
| * Unload the named library. JNI_OnUnload, if present, will be invoked | |
| * before the native library is unloaded. | |
| */ | |
| private static native void unload(String name, boolean isBuiltin, long handle); | |
| private static native String findBuiltinLib(String name); | |
| } |