/* |
|
* Copyright (c) 2020, 2021, 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<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; |
|
// 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 <a href="${docroot}/specs/jni/invocation.html##library-and-version-management"> |
|
* JNI Specification: Library and Version Management</a> |
|
*/ |
|
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 |
|
*/ |
|
@SuppressWarnings("removal") |
|
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<String> loadedLibraryNames = new HashSet<>(); |
|
// native libraries being loaded |
|
private static Deque<NativeLibraryImpl> 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); |
|
} |