1 /* 2 * Copyright (c) 2020, 2024, 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.misc.VM; 28 import jdk.internal.ref.CleanerFactory; 29 import jdk.internal.util.StaticProperty; 30 31 import java.io.File; 32 import java.io.IOException; 33 import java.security.AccessController; 34 import java.security.PrivilegedAction; 35 import java.util.ArrayDeque; 36 import java.util.Deque; 37 import java.util.function.BiFunction; 38 import java.util.function.Function; 39 import java.util.Map; 40 import java.util.Set; 41 import java.util.concurrent.ConcurrentHashMap; 42 import java.util.concurrent.locks.ReentrantLock; 43 44 /** 45 * Native libraries are loaded via {@link System#loadLibrary(String)}, 46 * {@link System#load(String)}, {@link Runtime#loadLibrary(String)} and 47 * {@link Runtime#load(String)}. They are caller-sensitive. 48 * 49 * Each class loader has a NativeLibraries instance to register all of its 50 * loaded native libraries. System::loadLibrary (and other APIs) only 51 * allows a native library to be loaded by one class loader, i.e. one 52 * NativeLibraries instance. Any attempt to load a native library that 53 * has already been loaded by a class loader with another class loader 54 * will fail. 55 */ 56 public final class NativeLibraries { 57 private static final boolean loadLibraryOnlyIfPresent = ClassLoaderHelper.loadLibraryOnlyIfPresent(); 58 private final Map<String, NativeLibraryImpl> libraries = new ConcurrentHashMap<>(); 59 private final ClassLoader loader; 60 // caller, if non-null, is the fromClass parameter for NativeLibraries::loadLibrary 61 // unless specified 62 private final Class<?> caller; // may be null 63 private final boolean searchJavaLibraryPath; 64 65 /** 66 * Creates a NativeLibraries instance for loading JNI native libraries 67 * via for System::loadLibrary use. 68 * 69 * 1. Support of auto-unloading. The loaded native libraries are unloaded 70 * when the class loader is reclaimed. 71 * 2. Support of linking of native method. See JNI spec. 72 * 3. Restriction on a native library that can only be loaded by one class loader. 73 * Each class loader manages its own set of native libraries. 74 * The same JNI native library cannot be loaded into more than one class loader. 75 * 76 * This static factory method is intended only for System::loadLibrary use. 77 * 78 * @see <a href="${docroot}/specs/jni/invocation.html##library-and-version-management"> 79 * JNI Specification: Library and Version Management</a> 80 */ 81 public static NativeLibraries newInstance(ClassLoader loader) { 82 return new NativeLibraries(loader, loader != null ? null : NativeLibraries.class, loader != null); 83 } 84 85 private NativeLibraries(ClassLoader loader, Class<?> caller, boolean searchJavaLibraryPath) { 86 this.loader = loader; 87 this.caller = caller; 88 this.searchJavaLibraryPath = searchJavaLibraryPath; 89 } 90 91 /* 92 * Find the address of the given symbol name from the native libraries 93 * loaded in this NativeLibraries instance. 94 */ 95 public long find(String name) { 96 if (libraries.isEmpty()) 97 return 0; 98 99 // the native libraries map may be updated in another thread 100 // when a native library is being loaded. No symbol will be 101 // searched from it yet. 102 for (NativeLibrary lib : libraries.values()) { 103 long entry = lib.find(name); 104 if (entry != 0) return entry; 105 } 106 return 0; 107 } 108 109 /* 110 * Load a native library from the given file. Returns null if the given 111 * library is determined to be non-loadable, which is system-dependent. 112 * 113 * @param fromClass the caller class calling System::loadLibrary 114 * @param file the path of the native library 115 * @throws UnsatisfiedLinkError if any error in loading the native library 116 */ 117 public NativeLibrary loadLibrary(Class<?> fromClass, File file) { 118 // Check to see if we're attempting to access a static library 119 String name = findBuiltinLib(file.getName()); 120 boolean isBuiltin = (name != null); 121 if (!isBuiltin) { 122 try { 123 if (loadLibraryOnlyIfPresent && !file.exists()) { 124 return null; 125 } 126 name = file.getCanonicalPath(); 127 } catch (IOException e) { 128 return null; 129 } 130 } 131 return loadLibrary(fromClass, name, isBuiltin); 132 } 133 134 /** 135 * Returns a NativeLibrary of the given name. 136 * 137 * @param fromClass the caller class calling System::loadLibrary 138 * @param name library name 139 * @param isBuiltin built-in library 140 * @throws UnsatisfiedLinkError if the native library has already been loaded 141 * and registered in another NativeLibraries 142 */ 143 private NativeLibrary loadLibrary(Class<?> fromClass, String name, boolean isBuiltin) { 144 ClassLoader loader = (fromClass == null) ? null : fromClass.getClassLoader(); 145 if (this.loader != loader) { 146 throw new InternalError(fromClass.getName() + " not allowed to load library"); 147 } 148 149 acquireNativeLibraryLock(name); 150 try { 151 // find if this library has already been loaded and registered in this NativeLibraries 152 NativeLibrary cached = libraries.get(name); 153 if (cached != null) { 154 return cached; 155 } 156 157 // cannot be loaded by other class loaders 158 if (loadedLibraryNames.contains(name)) { 159 throw new UnsatisfiedLinkError("Native Library " + name + 160 " already loaded in another classloader"); 161 } 162 163 /* 164 * When a library is being loaded, JNI_OnLoad function can cause 165 * another loadLibrary invocation that should succeed. 166 * 167 * Each thread maintains its own stack to hold the list of 168 * libraries it is loading. 169 * 170 * If there is a pending load operation for the library, we 171 * immediately return success; if the pending load is from 172 * a different class loader, we raise UnsatisfiedLinkError. 173 */ 174 for (NativeLibraryImpl lib : NativeLibraryContext.current()) { 175 if (name.equals(lib.name())) { 176 if (loader == lib.fromClass.getClassLoader()) { 177 return lib; 178 } else { 179 throw new UnsatisfiedLinkError("Native Library " + 180 name + " is being loaded in another classloader"); 181 } 182 } 183 } 184 185 NativeLibraryImpl lib = new NativeLibraryImpl(fromClass, name, isBuiltin); 186 // load the native library 187 NativeLibraryContext.push(lib); 188 try { 189 if (!lib.open()) { 190 return null; // fail to open the native library 191 } 192 // auto unloading is only supported for JNI native libraries 193 // loaded by custom class loaders that can be unloaded. 194 // built-in class loaders are never unloaded. 195 boolean autoUnload = !VM.isSystemDomainLoader(loader) && loader != ClassLoaders.appClassLoader(); 196 if (autoUnload) { 197 // register the loaded native library for auto unloading 198 // when the class loader is reclaimed, all native libraries 199 // loaded that class loader will be unloaded. 200 // The entries in the libraries map are not removed since 201 // the entire map will be reclaimed altogether. 202 CleanerFactory.cleaner().register(loader, lib.unloader()); 203 } 204 } finally { 205 NativeLibraryContext.pop(); 206 } 207 // register the loaded native library 208 loadedLibraryNames.add(name); 209 libraries.put(name, lib); 210 return lib; 211 } finally { 212 releaseNativeLibraryLock(name); 213 } 214 } 215 216 /** 217 * Loads a native library from the system library path and java library path. 218 * 219 * @param name library name 220 * 221 * @throws UnsatisfiedLinkError if the native library has already been loaded 222 * and registered in another NativeLibraries 223 */ 224 public NativeLibrary loadLibrary(String name) { 225 assert name.indexOf(File.separatorChar) < 0; 226 return loadLibrary(caller, name); 227 } 228 229 /** 230 * Loads a native library from the system library path and java library path. 231 * 232 * @param name library name 233 * @param fromClass the caller class calling System::loadLibrary 234 * 235 * @throws UnsatisfiedLinkError if the native library has already been loaded 236 * and registered in another NativeLibraries 237 */ 238 public NativeLibrary loadLibrary(Class<?> fromClass, String name) { 239 assert name.indexOf(File.separatorChar) < 0; 240 241 NativeLibrary lib = findFromPaths(LibraryPaths.SYS_PATHS, fromClass, name); 242 if (lib == null && searchJavaLibraryPath) { 243 lib = findFromPaths(LibraryPaths.USER_PATHS, fromClass, name); 244 } 245 return lib; 246 } 247 248 private NativeLibrary findFromPaths(String[] paths, Class<?> fromClass, String name) { 249 for (String path : paths) { 250 File libfile = new File(path, System.mapLibraryName(name)); 251 NativeLibrary nl = loadLibrary(fromClass, libfile); 252 if (nl != null) { 253 return nl; 254 } 255 libfile = ClassLoaderHelper.mapAlternativeName(libfile); 256 if (libfile != null) { 257 nl = loadLibrary(fromClass, libfile); 258 if (nl != null) { 259 return nl; 260 } 261 } 262 } 263 return null; 264 } 265 266 /** 267 * NativeLibraryImpl denotes a loaded native library instance. 268 * Each NativeLibraries contains a map of loaded native libraries in the 269 * private field {@code libraries}. 270 * 271 * Every native library requires a particular version of JNI. This is 272 * denoted by the private {@code jniVersion} field. This field is set by 273 * the VM when it loads the library, and used by the VM to pass the correct 274 * version of JNI to the native methods. 275 */ 276 static class NativeLibraryImpl extends NativeLibrary { 277 // the class from which the library is loaded, also indicates 278 // the loader this native library belongs. 279 final Class<?> fromClass; 280 // the canonicalized name of the native library. 281 // or static library name 282 final String name; 283 // Indicates if the native library is linked into the VM 284 final boolean isBuiltin; 285 286 // opaque handle to native library, used in native code. 287 long handle; 288 // the version of JNI environment the native library requires. 289 int jniVersion; 290 291 NativeLibraryImpl(Class<?> fromClass, String name, boolean isBuiltin) { 292 this.fromClass = fromClass; 293 this.name = name; 294 this.isBuiltin = isBuiltin; 295 } 296 297 @Override 298 public String name() { 299 return name; 300 } 301 302 @Override 303 public long find(String name) { 304 return findEntry0(handle, name); 305 } 306 307 /* 308 * Unloader::run method is invoked to unload the native library 309 * when this class loader becomes phantom reachable. 310 */ 311 private Runnable unloader() { 312 return new Unloader(name, handle, isBuiltin); 313 } 314 315 /* 316 * Loads the named native library 317 */ 318 boolean open() { 319 if (handle != 0) { 320 throw new InternalError("Native library " + name + " has been loaded"); 321 } 322 323 return load(this, name, isBuiltin, throwExceptionIfFail()); 324 } 325 326 @SuppressWarnings("removal") 327 private boolean throwExceptionIfFail() { 328 if (loadLibraryOnlyIfPresent) return true; 329 330 // If the file exists but fails to load, UnsatisfiedLinkException thrown by the VM 331 // will include the error message from dlopen to provide diagnostic information 332 return AccessController.doPrivileged(new PrivilegedAction<>() { 333 public Boolean run() { 334 File file = new File(name); 335 return file.exists(); 336 } 337 }); 338 } 339 340 /* 341 * Close this native library. 342 */ 343 void close() { 344 unload(name, isBuiltin, handle); 345 } 346 } 347 348 /* 349 * The run() method will be invoked when this class loader becomes 350 * phantom reachable to unload the native library. 351 */ 352 static class Unloader implements Runnable { 353 // This represents the context when a native library is unloaded 354 // and getFromClass() will return null, 355 static final NativeLibraryImpl UNLOADER = 356 new NativeLibraryImpl(null, "dummy", false); 357 358 final String name; 359 final long handle; 360 final boolean isBuiltin; 361 362 Unloader(String name, long handle, boolean isBuiltin) { 363 if (handle == 0) { 364 throw new IllegalArgumentException( 365 "Invalid handle for native library " + name); 366 } 367 368 this.name = name; 369 this.handle = handle; 370 this.isBuiltin = isBuiltin; 371 } 372 373 @Override 374 public void run() { 375 acquireNativeLibraryLock(name); 376 try { 377 /* remove the native library name */ 378 if (!loadedLibraryNames.remove(name)) { 379 throw new IllegalStateException(name + " has already been unloaded"); 380 } 381 NativeLibraryContext.push(UNLOADER); 382 try { 383 unload(name, isBuiltin, handle); 384 } finally { 385 NativeLibraryContext.pop(); 386 } 387 } finally { 388 releaseNativeLibraryLock(name); 389 } 390 } 391 } 392 393 /* 394 * Holds system and user library paths derived from the 395 * {@code java.library.path} and {@code sun.boot.library.path} system 396 * properties. The system properties are eagerly read at bootstrap, then 397 * lazily parsed on first use to avoid initialization ordering issues. 398 */ 399 static class LibraryPaths { 400 // The paths searched for libraries 401 static final String[] SYS_PATHS = ClassLoaderHelper.parsePath(StaticProperty.sunBootLibraryPath()); 402 static final String[] USER_PATHS = ClassLoaderHelper.parsePath(StaticProperty.javaLibraryPath()); 403 } 404 405 // All native libraries we've loaded. 406 private static final Set<String> loadedLibraryNames = 407 ConcurrentHashMap.newKeySet(); 408 409 // reentrant lock class that allows exact counting (with external synchronization) 410 @SuppressWarnings("serial") 411 private static final class CountedLock extends ReentrantLock { 412 413 private int counter = 0; 414 415 public void increment() { 416 if (counter == Integer.MAX_VALUE) { 417 // prevent overflow 418 throw new Error("Maximum lock count exceeded"); 419 } 420 ++counter; 421 } 422 423 public void decrement() { 424 --counter; 425 } 426 427 public int getCounter() { 428 return counter; 429 } 430 } 431 432 // Maps native library name to the corresponding lock object 433 private static final Map<String, CountedLock> nativeLibraryLockMap = 434 new ConcurrentHashMap<>(); 435 436 private static void acquireNativeLibraryLock(String libraryName) { 437 nativeLibraryLockMap.compute(libraryName, 438 new BiFunction<>() { 439 public CountedLock apply(String name, CountedLock currentLock) { 440 if (currentLock == null) { 441 currentLock = new CountedLock(); 442 } 443 // safe as compute BiFunction<> is executed atomically 444 currentLock.increment(); 445 return currentLock; 446 } 447 } 448 ).lock(); 449 } 450 451 private static void releaseNativeLibraryLock(String libraryName) { 452 CountedLock lock = nativeLibraryLockMap.computeIfPresent(libraryName, 453 new BiFunction<>() { 454 public CountedLock apply(String name, CountedLock currentLock) { 455 if (currentLock.getCounter() == 1) { 456 // unlock and release the object if no other threads are queued 457 currentLock.unlock(); 458 // remove the element 459 return null; 460 } else { 461 currentLock.decrement(); 462 return currentLock; 463 } 464 } 465 } 466 ); 467 if (lock != null) { 468 lock.unlock(); 469 } 470 } 471 472 // native libraries being loaded 473 private static final class NativeLibraryContext { 474 475 // Maps thread object to the native library context stack, maintained by each thread 476 private static Map<Thread, Deque<NativeLibraryImpl>> nativeLibraryThreadContext = 477 new ConcurrentHashMap<>(); 478 479 // returns a context associated with the current thread 480 private static Deque<NativeLibraryImpl> current() { 481 return nativeLibraryThreadContext.computeIfAbsent( 482 Thread.currentThread(), 483 new Function<>() { 484 public Deque<NativeLibraryImpl> apply(Thread t) { 485 return new ArrayDeque<>(8); 486 } 487 }); 488 } 489 490 private static NativeLibraryImpl peek() { 491 return current().peek(); 492 } 493 494 private static void push(NativeLibraryImpl lib) { 495 current().push(lib); 496 } 497 498 private static void pop() { 499 // this does not require synchronization since each 500 // thread has its own context 501 Deque<NativeLibraryImpl> libs = current(); 502 libs.pop(); 503 if (libs.isEmpty()) { 504 // context can be safely removed once empty 505 nativeLibraryThreadContext.remove(Thread.currentThread()); 506 } 507 } 508 509 private static boolean isEmpty() { 510 Deque<NativeLibraryImpl> context = 511 nativeLibraryThreadContext.get(Thread.currentThread()); 512 return (context == null || context.isEmpty()); 513 } 514 } 515 516 // Invoked in the VM to determine the context class in JNI_OnLoad 517 // and JNI_OnUnload 518 private static Class<?> getFromClass() { 519 if (NativeLibraryContext.isEmpty()) { // only default library 520 return Object.class; 521 } 522 return NativeLibraryContext.peek().fromClass; 523 } 524 525 /* 526 * Return true if the given library is successfully loaded. 527 * If the given library cannot be loaded for any reason, 528 * if throwExceptionIfFail is false, then this method returns false; 529 * otherwise, UnsatisfiedLinkError will be thrown. 530 * 531 * JNI FindClass expects the caller class if invoked from JNI_OnLoad 532 * and JNI_OnUnload is NativeLibrary class. 533 */ 534 private static native boolean load(NativeLibraryImpl impl, String name, 535 boolean isBuiltin, 536 boolean throwExceptionIfFail); 537 /* 538 * Unload the named library. JNI_OnUnload, if present, will be invoked 539 * before the native library is unloaded. 540 */ 541 private static native void unload(String name, boolean isBuiltin, long handle); 542 private static native String findBuiltinLib(String name); 543 }