| /* | |
| * 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 <em>symbol lookup</em> 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. | |
| * <p> | |
| * 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. | |
| * <p> | |
| * The address of a symbol is modeled as a zero-length | |
| * {@linkplain MemorySegment memory segment}. The segment can be used in different ways: | |
| * <ul> | |
| * <li>It can be passed to a {@link Linker} to create a downcall method handle, which | |
| * can then be used to call the foreign function at the segment's address.</li> | |
| * <li>It can be passed to an existing | |
| * {@linkplain Linker#downcallHandle(FunctionDescriptor, Linker.Option...) downcall method handle}, | |
| * as an argument to the underlying foreign function.</li> | |
| * <li>It can be {@linkplain MemorySegment#set(AddressLayout, long, MemorySegment) stored} | |
| * inside another memory segment.</li> | |
| * <li>It can be used to access the region of memory backing a global variable | |
| * (this requires {@linkplain MemorySegment#reinterpret(long) resizing} | |
| * the segment first).</li> | |
| * </ul> | |
| * | |
| * <h2 id="obtaining">Obtaining a symbol lookup</h2> | |
| * | |
| * 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 | |
| * <em>library lookup</em>, 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 | |
| *} | |
| * <p> | |
| * 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 <em>loader lookup</em>, 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. | |
| * <p> | |
| * 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 | |
| *} | |
| * | |
| * <p> | |
| * 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 <em>default lookup</em>, 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<MemorySegment> find(String name); | |
| /** | |
| * Returns the address of the symbol with the given name or throws an exception. | |
| *<p> | |
| * 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<MemorySegment> 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. | |
| * <p> | |
| * 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. | |
| * <p> | |
| * 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. | |
| * <p> | |
| * 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 <Z> | |
| SymbolLookup libraryLookup(Z libDesc, | |
| BiFunction<RawNativeLibraries, Z, NativeLibrary> 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 | |
| }; | |
| } | |
| } |