/* * Copyright (c) 2022, 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 java.lang.foreign; import jdk.internal.access.JavaLangAccess; import jdk.internal.access.SharedSecrets; import jdk.internal.foreign.MemorySessionImpl; import jdk.internal.foreign.Utils; import jdk.internal.javac.Restricted; import jdk.internal.loader.BuiltinClassLoader; import jdk.internal.loader.NativeLibraries; import jdk.internal.loader.NativeLibrary; import jdk.internal.loader.RawNativeLibraries; import jdk.internal.reflect.CallerSensitive; import jdk.internal.reflect.Reflection; import java.lang.invoke.MethodHandles; import java.nio.file.FileSystems; import java.nio.file.Path; import java.util.NoSuchElementException; import java.util.Objects; import java.util.Optional; import java.util.function.BiFunction; /** * A symbol lookup retrieves the address of a symbol in one or more libraries. * A symbol is a named entity, such as a function or a global variable. *

* A symbol lookup is created with respect to a particular library (or libraries). * Subsequently, the {@link SymbolLookup#find(String)} method takes the name of a symbol * and returns the address of the symbol in that library. *

* The address of a symbol is modeled as a zero-length * {@linkplain MemorySegment memory segment}. The segment can be used in different ways: *

* *

Obtaining a symbol lookup

* * The factory methods {@link #libraryLookup(String, Arena)} and * {@link #libraryLookup(Path, Arena)} create a symbol lookup for a library known to * the operating system. The library is specified by either its name or a path. * The library is loaded if not already loaded. The symbol lookup, which is known as a * library lookup, and its lifetime is controlled by an {@linkplain Arena arena}. * For instance, if the provided arena is a confined arena, the library associated with * the symbol lookup is unloaded when the confined arena is {@linkplain Arena#close() closed}: * * {@snippet lang = java: * try (Arena arena = Arena.ofConfined()) { * SymbolLookup libGL = SymbolLookup.libraryLookup("libGL.so", arena); // libGL.so loaded here * MemorySegment glGetString = libGL.findOrThrow("glGetString"); * ... * } // libGL.so unloaded here *} *

* If a library was previously loaded through JNI, i.e., by {@link System#load(String)} * or {@link System#loadLibrary(String)}, then the library was also associated with * a particular class loader. The factory method {@link #loaderLookup()} creates * a symbol lookup for all the libraries associated with the caller's class loader: * * {@snippet lang=java : * System.loadLibrary("GL"); // libGL.so loaded here * ... * SymbolLookup libGL = SymbolLookup.loaderLookup(); * MemorySegment glGetString = libGL.findOrThrow("glGetString"); * } * * This symbol lookup, which is known as a loader lookup, is dynamic with * respect to the libraries associated with the class loader. If other libraries are * subsequently loaded through JNI and associated with the class loader, then the loader * lookup will expose their symbols automatically. *

* Note that a loader lookup only exposes symbols in libraries that were previously * loaded through JNI, i.e., by {@link System#load(String)} or {@link System#loadLibrary(String)}. * A loader lookup does not expose symbols in libraries that were loaded in the course * of creating a library lookup: * * {@snippet lang = java: * libraryLookup("libGL.so", arena).find("glGetString").isPresent(); // true * loaderLookup().find("glGetString").isPresent(); // false *} * * Note also that a library lookup for library {@code L} exposes symbols in {@code L} * even if {@code L} was previously loaded through JNI (the association with * a class loader is immaterial to the library lookup): * * {@snippet lang = java: * System.loadLibrary("GL"); // libGL.so loaded here * libraryLookup("libGL.so", arena).find("glGetString").isPresent(); // true *} * *

* Finally, each {@link Linker} provides a symbol lookup for libraries that are commonly * used on the OS and processor combination supported by that {@link Linker}. This * symbol lookup, which is known as a default lookup, helps clients to quickly * find addresses of well-known symbols. For example, a {@link Linker} for Linux/x64 * might choose to expose symbols in {@code libc} through the default lookup: * * {@snippet lang = java: * Linker nativeLinker = Linker.nativeLinker(); * SymbolLookup stdlib = nativeLinker.defaultLookup(); * MemorySegment malloc = stdlib.findOrThrow("malloc"); *} * * @since 22 */ @FunctionalInterface public interface SymbolLookup { /** * Returns the address of the symbol with the given name. * * @param name the symbol name * @return a zero-length memory segment whose address indicates the address of * the symbol, if found * @see #findOrThrow(String) */ Optional find(String name); /** * Returns the address of the symbol with the given name or throws an exception. *

* This is equivalent to the following code, but is more efficient: * to: * {@snippet lang= java : * String name = ... * MemorySegment address = lookup.find(name) * .orElseThrow(() -> new NoSuchElementException("Symbol not found: " + name)); * } * * @param name the symbol name * @return a zero-length memory segment whose address indicates the address of * the symbol * @throws NoSuchElementException if no symbol address can be found for the * given name * @see #find(String) * * @since 23 */ default MemorySegment findOrThrow(String name) { Objects.requireNonNull(name); Optional address = find(name); // Avoid lambda capturing if (address.isPresent()) { return address.get(); } throw new NoSuchElementException("Symbol not found: " + name); } /** * {@return a composed symbol lookup that returns the result of finding the symbol * with this lookup if found, otherwise returns the result of finding * the symbol with the other lookup} * * @apiNote This method could be used to chain multiple symbol lookups together, * e.g. so that symbols could be retrieved, in order, from multiple * libraries: * {@snippet lang = java: * var lookup = SymbolLookup.libraryLookup("foo", arena) * .or(SymbolLookup.libraryLookup("bar", arena)) * .or(SymbolLookup.loaderLookup()); *} * The above code creates a symbol lookup that first searches for symbols in * the "foo" library. If no symbol is found in "foo" then "bar" is searched. * Finally, if a symbol is neither found in "foo" nor in "bar", the * {@linkplain SymbolLookup#loaderLookup() loader lookup} is used. * * @param other the symbol lookup that should be used to look for symbols not found * in this lookup */ default SymbolLookup or(SymbolLookup other) { Objects.requireNonNull(other); return name -> find(name).or(() -> other.find(name)); } /** * Returns a symbol lookup for symbols in the libraries associated with the caller's * class loader. *

* A library is associated with a class loader {@code CL} when the library is loaded * via an invocation of {@link System#load(String)} or * {@link System#loadLibrary(String)} from code in a class defined by {@code CL}. * If that code makes further invocations of {@link System#load(String)} or * {@link System#loadLibrary(String)} then more libraries are loaded and associated * with {@code CL}. The symbol lookup returned by this method is always current: it * reflects all the libraries associated with the relevant class loader, even if they * were loaded after this method returned. *

* Libraries associated with a class loader are unloaded when the class loader becomes * {@linkplain java.lang.ref##reachability unreachable}. The * symbol lookup returned by this method is associated with an automatic * {@linkplain MemorySegment.Scope scope} which keeps the caller's class loader * reachable. Therefore, libraries associated with the caller's class loader are * kept loaded (and their symbols available) as long as a loader lookup for that * class loader, or any of the segments obtained by it, is reachable. *

* In cases where this method is called from a context where there is no caller * frame on the stack (e.g. when called directly from a JNI attached thread), the * caller's class loader defaults to the * {@linkplain ClassLoader#getSystemClassLoader system class loader}. * * @return a symbol lookup for symbols in the libraries associated with * the caller's class loader * @see System#load(String) * @see System#loadLibrary(String) */ @CallerSensitive @SuppressWarnings("restricted") static SymbolLookup loaderLookup() { Class caller = Reflection.getCallerClass(); // If there's no caller class, fallback to system loader ClassLoader loader = caller != null ? caller.getClassLoader() : ClassLoader.getSystemClassLoader(); Arena loaderArena;// builtin loaders never go away if ((loader == null || loader instanceof BuiltinClassLoader)) { loaderArena = Arena.global(); } else { MemorySessionImpl session = MemorySessionImpl.createHeap(loader); loaderArena = session.asArena(); } return name -> { Objects.requireNonNull(name); if (Utils.containsNullChars(name)) return Optional.empty(); JavaLangAccess javaLangAccess = SharedSecrets.getJavaLangAccess(); // note: ClassLoader::findNative supports a null loader NativeLibraries nativeLibraries = javaLangAccess.nativeLibrariesFor(loader); long addr = nativeLibraries.find(name); return addr == 0L ? Optional.empty() : Optional.of(MemorySegment.ofAddress(addr) .reinterpret(loaderArena, null)); // restricted }; } /** * Loads a library with the given name (if not already loaded) and creates a symbol * lookup for symbols in that library. The lifetime of the returned library lookup * is controlled by the provided arena. For instance, if the provided arena is a * confined arena, the library associated with the returned lookup will be unloaded * when the provided confined arena is {@linkplain Arena#close() closed}. * * @implNote The process of resolving a library name is OS-specific. For instance, * in a POSIX-compliant OS, the library name is resolved according to the * specification of the {@code dlopen} function for that OS. In Windows, * the library name is resolved according to the specification of the * {@code LoadLibrary} function. * * @param name the name of the library in which symbols should be looked up * @param arena the arena associated with symbols obtained from the returned lookup * @return a new symbol lookup suitable to find symbols in a library with the * given name * @throws IllegalStateException if {@code arena.scope().isAlive() == false} * @throws WrongThreadException if {@code arena} is a confined arena, and this method * is called from a thread {@code T}, other than the arena's owner thread * @throws IllegalArgumentException if {@code name} does not identify a valid library * @throws IllegalCallerException if the caller is in a module that does not have * native access enabled */ @CallerSensitive @Restricted static SymbolLookup libraryLookup(String name, Arena arena) { Reflection.ensureNativeAccess(Reflection.getCallerClass(), SymbolLookup.class, "libraryLookup", false); if (Utils.containsNullChars(name)) { throw new IllegalArgumentException("Cannot open library: " + name); } return libraryLookup(name, RawNativeLibraries::load, arena); } /** * Loads a library from the given path (if not already loaded) and creates a symbol * lookup for symbols in that library. The lifetime of the returned library lookup * is controlled by the provided arena. For instance, if the provided arena is a * confined arena, the library associated with the returned lookup will be unloaded * when the provided confined arena is {@linkplain Arena#close() closed}. * * @implNote On Linux, the functionalities provided by this factory method and the * returned symbol lookup are implemented using the {@code dlopen}, * {@code dlsym} and {@code dlclose} functions. * * @param path the path of the library in which symbols should be looked up * @param arena the arena associated with symbols obtained from the returned lookup * @return a new symbol lookup suitable to find symbols in a library with the given * path * @throws IllegalStateException if {@code arena.scope().isAlive() == false} * @throws WrongThreadException if {@code arena} is a confined arena, and this method * is called from a thread {@code T}, other than the arena's owner thread * @throws IllegalArgumentException if {@code path} does not point to a valid library * in the default file system * @throws IllegalCallerException if the caller is in a module that does not have * native access enabled */ @CallerSensitive @Restricted static SymbolLookup libraryLookup(Path path, Arena arena) { Reflection.ensureNativeAccess(Reflection.getCallerClass(), SymbolLookup.class, "libraryLookup", false); if (path.getFileSystem() != FileSystems.getDefault()) { throw new IllegalArgumentException("Path not in default file system: " + path); } return libraryLookup(path, RawNativeLibraries::load, arena); } @SuppressWarnings("restricted") private static SymbolLookup libraryLookup(Z libDesc, BiFunction loadLibraryFunc, Arena libArena) { Objects.requireNonNull(libDesc); Objects.requireNonNull(libArena); // attempt to load native library from path or name RawNativeLibraries nativeLibraries = RawNativeLibraries.newInstance(MethodHandles.lookup()); NativeLibrary library = loadLibraryFunc.apply(nativeLibraries, libDesc); if (library == null) { throw new IllegalArgumentException("Cannot open library: " + libDesc); } // register hook to unload library when 'libScope' becomes not alive MemorySessionImpl.toMemorySession(libArena).addOrCleanupIfFail(new MemorySessionImpl.ResourceList.ResourceCleanup() { @Override public void cleanup() { nativeLibraries.unload(library); } }); return name -> { Objects.requireNonNull(name); if (Utils.containsNullChars(name)) return Optional.empty(); long addr = library.find(name); return addr == 0L ? Optional.empty() : Optional.of(MemorySegment.ofAddress(addr) .reinterpret(libArena, null)); // restricted }; } }