1 /* 2 * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package jdk.internal.loader; 26 27 import jdk.internal.ref.CleanerFactory; 28 import jdk.internal.util.StaticProperty; 29 30 import java.io.File; 31 import java.io.IOException; 32 import java.security.AccessController; 33 import java.security.PrivilegedAction; 34 import java.util.ArrayDeque; 35 import java.util.Deque; 36 import java.util.HashSet; 37 import java.util.Map; 38 import java.util.Set; 39 import java.util.concurrent.ConcurrentHashMap; 40 41 /** 42 * Native libraries are loaded via {@link System#loadLibrary(String)}, 43 * {@link System#load(String)}, {@link Runtime#loadLibrary(String)} and 44 * {@link Runtime#load(String)}. They are caller-sensitive. 45 * 46 * Each class loader has a NativeLibraries instance to register all of its 47 * loaded native libraries. System::loadLibrary (and other APIs) only 48 * allows a native library to be loaded by one class loader, i.e. one 49 * NativeLibraries instance. Any attempt to load a native library that 50 * has already been loaded by a class loader with another class loader 51 * will fail. 52 */ 53 public final class NativeLibraries { 54 55 private final Map<String, NativeLibrary> libraries = new ConcurrentHashMap<>(); 56 private final ClassLoader loader; 57 private final Class<?> caller; // may be null. If not null, this is used as 58 // fromClass as a fast-path. See loadLibrary(String name). 59 private final boolean searchJavaLibraryPath; 60 61 public NativeLibraries(ClassLoader loader) { 62 this(loader, null, loader != null ? true : false); 63 } 64 public NativeLibraries(ClassLoader loader, Class<?> caller, boolean searchJavaLibraryPath) { 65 if (caller != null && caller.getClassLoader() != loader) { 66 throw new IllegalArgumentException(caller.getName() + " must be defined by " + loader); 67 } 68 this.loader = loader; 69 this.caller = caller; 70 this.searchJavaLibraryPath = searchJavaLibraryPath; 71 } 72 73 /* 74 * Look up the address of the given symbol name from the native libraries 75 * loaded in this NativeLibraries instance. 76 */ 77 public long lookup(String name) { 78 if (libraries.isEmpty()) 79 return 0; 80 81 // the native libraries map may be updated in another thread 82 // when a native library is being loaded. No symbol will be 83 // searched from it yet. 84 for (NativeLibrary lib : libraries.values()) { 85 long entry = lib.findEntry(name); 86 if (entry != 0) return entry; 87 } 88 return 0; 89 } 90 91 /* 92 * Load a native library from the given file. Returns null if file does not exist. 93 * 94 * @param fromClass the caller class calling System::loadLibrary 95 * @param file the path of the native library 96 * @throws UnsatisfiedLinkError if any error in loading the native library 97 */ 98 public NativeLibrary loadLibrary(Class<?> fromClass, File file) { 99 // Check to see if we're attempting to access a static library 100 String name = findBuiltinLib(file.getName()); 101 boolean isBuiltin = (name != null); 102 if (!isBuiltin) { 103 name = AccessController.doPrivileged(new PrivilegedAction<>() { 104 public String run() { 105 try { 106 return file.exists() ? file.getCanonicalPath() : null; 107 } catch (IOException e) { 108 return null; 109 } 110 } 111 }); 112 if (name == null) { 113 return null; 114 } 115 } 116 return loadLibrary(fromClass, name, isBuiltin); 117 } 118 119 /** 120 * Returns a NativeLibrary of the given name. 121 * 122 * @param fromClass the caller class calling System::loadLibrary 123 * @param name library name 124 * @param isBuiltin built-in library 125 * @throws UnsatisfiedLinkError if the native library has already been loaded 126 * and registered in another NativeLibraries 127 */ 128 private NativeLibrary loadLibrary(Class<?> fromClass, String name, boolean isBuiltin) { 129 ClassLoader loader = (fromClass == null) ? null : fromClass.getClassLoader(); 130 if (this.loader != loader) { 131 throw new InternalError(fromClass.getName() + " not allowed to load library"); 132 } 133 134 synchronized (loadedLibraryNames) { 135 // find if this library has already been loaded and registered in this NativeLibraries 136 NativeLibrary cached = libraries.get(name); 137 if (cached != null) { 138 return cached; 139 } 140 141 // cannot be loaded by other class loaders 142 if (loadedLibraryNames.contains(name)) { 143 throw new UnsatisfiedLinkError("Native Library " + name + 144 " already loaded in another classloader"); 145 } 146 147 /* 148 * When a library is being loaded, JNI_OnLoad function can cause 149 * another loadLibrary invocation that should succeed. 150 * 151 * We use a static stack to hold the list of libraries we are 152 * loading because this can happen only when called by the 153 * same thread because this block is synchronous. 154 * 155 * If there is a pending load operation for the library, we 156 * immediately return success; otherwise, we raise 157 * UnsatisfiedLinkError. 158 */ 159 for (NativeLibraryImpl lib : nativeLibraryContext) { 160 if (name.equals(lib.name())) { 161 if (loader == lib.fromClass.getClassLoader()) { 162 return lib; 163 } else { 164 throw new UnsatisfiedLinkError("Native Library " + 165 name + " is being loaded in another classloader"); 166 } 167 } 168 } 169 170 NativeLibraryImpl lib = new NativeLibraryImpl(fromClass, name, isBuiltin); 171 // load the native library 172 nativeLibraryContext.push(lib); 173 try { 174 if (!lib.open()) return null; 175 } finally { 176 nativeLibraryContext.pop(); 177 } 178 // register the loaded native library 179 loadedLibraryNames.add(name); 180 libraries.put(name, lib); 181 return lib; 182 } 183 } 184 185 /** 186 * Loads a native library from the system library path and java library path. 187 * 188 * @param name library name 189 * 190 * @throws UnsatisfiedLinkError if the native library has already been loaded 191 * and registered in another NativeLibraries 192 */ 193 public NativeLibrary loadLibrary(String name) { 194 assert caller != null; 195 return loadLibrary(caller, name); 196 } 197 198 /** 199 * Loads a native library from the system library path and java library path. 200 * 201 * @param name library name 202 * @param fromClass the caller class calling System::loadLibrary 203 * 204 * @throws UnsatisfiedLinkError if the native library has already been loaded 205 * and registered in another NativeLibraries 206 */ 207 public NativeLibrary loadLibrary(Class<?> fromClass, String name) { 208 NativeLibrary lib = findFromPaths(LibraryPaths.SYS_PATHS, fromClass, name); 209 if (lib == null && searchJavaLibraryPath) { 210 lib = findFromPaths(LibraryPaths.USER_PATHS, fromClass, name); 211 } 212 return lib; 213 } 214 215 private NativeLibrary findFromPaths(String[] paths, Class<?> fromClass, String name) { 216 for (String path : paths) { 217 File libfile = new File(path, System.mapLibraryName(name)); 218 NativeLibrary nl = loadLibrary(fromClass, libfile); 219 if (nl != null) { 220 return nl; 221 } 222 libfile = ClassLoaderHelper.mapAlternativeName(libfile); 223 if (libfile != null) { 224 nl = loadLibrary(fromClass, libfile); 225 if (nl != null) { 226 return nl; 227 } 228 } 229 } 230 return null; 231 } 232 233 /** 234 * NativeLibraryImpl denotes a loaded native library instance. 235 * Each NativeLibraries contains a map of loaded native libraries in the 236 * private field {@code libraries}. 237 * 238 * Every native library requires a particular version of JNI. This is 239 * denoted by the private {@code jniVersion} field. This field is set by 240 * the VM when it loads the library, and used by the VM to pass the correct 241 * version of JNI to the native methods. 242 */ 243 static class NativeLibraryImpl implements NativeLibrary { 244 // the class from which the library is loaded, also indicates 245 // the loader this native library belongs. 246 final Class<?> fromClass; 247 // the canonicalized name of the native library. 248 // or static library name 249 final String name; 250 // Indicates if the native library is linked into the VM 251 final boolean isBuiltin; 252 253 // opaque handle to native library, used in native code. 254 long handle; 255 // the version of JNI environment the native library requires. 256 int jniVersion; 257 258 NativeLibraryImpl(Class<?> fromClass, String name, boolean isBuiltin) { 259 this.fromClass = fromClass; 260 this.name = name; 261 this.isBuiltin = isBuiltin; 262 } 263 264 @Override 265 public String name() { 266 return name; 267 } 268 269 @Override 270 public long findEntry(String name) { 271 return findEntry0(this, name); 272 } 273 274 /* 275 * Loads the native library and registers for cleanup when its 276 * associated class loader is unloaded 277 */ 278 boolean open() { 279 if (handle != 0) { 280 throw new InternalError("Native library " + name + " has been loaded"); 281 } 282 283 if (!load(this, name, isBuiltin)) return false; 284 285 // register the class loader for cleanup when unloaded 286 // builtin class loaders are never unloaded 287 ClassLoader loader = fromClass != null ? fromClass.getClassLoader() : null; 288 if (loader != null && 289 loader != ClassLoaders.platformClassLoader() && 290 loader != ClassLoaders.appClassLoader()) { 291 CleanerFactory.cleaner().register(loader, new Unloader(name, handle, isBuiltin)); 292 } 293 return true; 294 } 295 } 296 297 /* 298 * The run() method will be invoked when this class loader becomes 299 * phantom reachable to unload the native library. 300 */ 301 static class Unloader implements Runnable { 302 // This represents the context when a native library is unloaded 303 // and getFromClass() will return null, 304 static final NativeLibraryImpl UNLOADER = 305 new NativeLibraryImpl(null, "dummy", false); 306 307 final String name; 308 final long handle; 309 final boolean isBuiltin; 310 311 Unloader(String name, long handle, boolean isBuiltin) { 312 if (handle == 0) { 313 throw new IllegalArgumentException( 314 "Invalid handle for native library " + name); 315 } 316 317 this.name = name; 318 this.handle = handle; 319 this.isBuiltin = isBuiltin; 320 } 321 322 @Override 323 public void run() { 324 synchronized (NativeLibraries.loadedLibraryNames) { 325 /* remove the native library name */ 326 NativeLibraries.loadedLibraryNames.remove(name); 327 NativeLibraries.nativeLibraryContext.push(UNLOADER); 328 try { 329 unload(name, isBuiltin, handle); 330 } finally { 331 NativeLibraries.nativeLibraryContext.pop(); 332 } 333 } 334 } 335 } 336 337 /* 338 * Holds system and user library paths derived from the 339 * {@code java.library.path} and {@code sun.boot.library.path} system 340 * properties. The system properties are eagerly read at bootstrap, then 341 * lazily parsed on first use to avoid initialization ordering issues. 342 */ 343 static class LibraryPaths { 344 // The paths searched for libraries 345 static final String[] SYS_PATHS = ClassLoaderHelper.parsePath(StaticProperty.sunBootLibraryPath()); 346 static final String[] USER_PATHS = ClassLoaderHelper.parsePath(StaticProperty.javaLibraryPath()); 347 } 348 349 // All native libraries we've loaded. 350 // This also serves as the lock to obtain nativeLibraries 351 // and write to nativeLibraryContext. 352 private static final Set<String> loadedLibraryNames = new HashSet<>(); 353 354 // native libraries being loaded 355 private static Deque<NativeLibraryImpl> nativeLibraryContext = new ArrayDeque<>(8); 356 357 // Invoked in the VM to determine the context class in JNI_OnLoad 358 // and JNI_OnUnload 359 private static Class<?> getFromClass() { 360 if (nativeLibraryContext.isEmpty()) { // only default library 361 return Object.class; 362 } 363 return nativeLibraryContext.peek().fromClass; 364 } 365 366 // JNI FindClass expects the caller class if invoked from JNI_OnLoad 367 // and JNI_OnUnload is NativeLibrary class 368 private static native boolean load(NativeLibraryImpl impl, String name, boolean isBuiltin); 369 private static native void unload(String name, boolean isBuiltin, long handle); 370 private static native String findBuiltinLib(String name); 371 private static native long findEntry0(NativeLibraryImpl lib, String name); 372 }