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