1 /*
  2  * Copyright (c) 2022, 2023, 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 
 26 package java.lang.foreign;
 27 
 28 import jdk.internal.access.JavaLangAccess;
 29 import jdk.internal.access.SharedSecrets;
 30 import jdk.internal.foreign.MemorySessionImpl;
 31 import jdk.internal.foreign.Utils;
 32 import jdk.internal.javac.PreviewFeature;
 33 import jdk.internal.loader.BuiltinClassLoader;
 34 import jdk.internal.loader.NativeLibrary;
 35 import jdk.internal.loader.RawNativeLibraries;
 36 import jdk.internal.reflect.CallerSensitive;
 37 import jdk.internal.reflect.Reflection;
 38 
 39 import java.lang.invoke.MethodHandles;
 40 import java.nio.file.Path;
 41 import java.util.Objects;
 42 import java.util.Optional;
 43 import java.util.function.BiFunction;
 44 
 45 /**
 46  * A <em>symbol lookup</em> retrieves the address of a symbol in one or more libraries.
 47  * A symbol is a named entity, such as a function or a global variable.
 48  * <p>
 49  * A symbol lookup is created with respect to a particular library (or libraries). Subsequently, the {@link SymbolLookup#find(String)}
 50  * method takes the name of a symbol and returns the address of the symbol in that library.
 51  * <p>
 52  * The address of a symbol is modelled as a zero-length {@linkplain MemorySegment memory segment}. The segment can be used in different ways:
 53  * <ul>
 54  *     <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>
 55  *     <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>
 56  *     <li>It can be {@linkplain MemorySegment#set(AddressLayout, long, MemorySegment) stored} inside another memory segment.</li>
 57  *     <li>It can be used to access the region of memory backing a global variable (this requires
 58  *     {@linkplain MemorySegment#reinterpret(long) resizing} the segment first).</li>
 59  * </ul>
 60  *
 61  * <h2 id="obtaining">Obtaining a symbol lookup</h2>
 62  *
 63  * The factory methods {@link #libraryLookup(String, Arena)} and {@link #libraryLookup(Path, Arena)}
 64  * create a symbol lookup for a library known to the operating system. The library is specified by either its name or a path.
 65  * The library is loaded if not already loaded. The symbol lookup, which is known as a <em>library lookup</em>, and its
 66  * lifetime is controlled by an {@linkplain Arena arena}. For instance, if the provided arena is a
 67  * confined arena, the library associated with the symbol lookup is unloaded when the confined arena
 68  * is {@linkplain Arena#close() closed}:
 69  *
 70  * {@snippet lang = java:
 71  * try (Arena arena = Arena.ofConfined()) {
 72  *     SymbolLookup libGL = SymbolLookup.libraryLookup("libGL.so", arena); // libGL.so loaded here
 73  *     MemorySegment glGetString = libGL.find("glGetString").orElseThrow();
 74  *     ...
 75  * } //  libGL.so unloaded here
 76  *}
 77  * <p>
 78  * If a library was previously loaded through JNI, i.e., by {@link System#load(String)}
 79  * or {@link System#loadLibrary(String)}, then the library was also associated with a particular class loader. The factory
 80  * method {@link #loaderLookup()} creates a symbol lookup for all the libraries associated with the caller's class loader:
 81  *
 82  * {@snippet lang=java :
 83  * System.loadLibrary("GL"); // libGL.so loaded here
 84  * ...
 85  * SymbolLookup libGL = SymbolLookup.loaderLookup();
 86  * MemorySegment glGetString = libGL.find("glGetString").orElseThrow();
 87  * }
 88  *
 89  * This symbol lookup, which is known as a <em>loader lookup</em>, is dynamic with respect to the libraries associated
 90  * with the class loader. If other libraries are subsequently loaded through JNI and associated with the class loader,
 91  * then the loader lookup will expose their symbols automatically.
 92  * <p>
 93  * Note that a loader lookup only exposes symbols in libraries that were previously loaded through JNI, i.e.,
 94  * by {@link System#load(String)} or {@link System#loadLibrary(String)}. A loader lookup does not expose symbols in libraries
 95  * that were loaded in the course of creating a library lookup:
 96  *
 97  * {@snippet lang = java:
 98  * libraryLookup("libGL.so", arena).find("glGetString").isPresent(); // true
 99  * loaderLookup().find("glGetString").isPresent(); // false
100  *}
101  *
102  * Note also that a library lookup for library {@code L} exposes symbols in {@code L} even if {@code L} was previously loaded
103  * through JNI (the association with a class loader is immaterial to the library lookup):
104  *
105  * {@snippet lang = java:
106  * System.loadLibrary("GL"); // libGL.so loaded here
107  * libraryLookup("libGL.so", arena).find("glGetString").isPresent(); // true
108  *}
109  *
110  * <p>
111  * Finally, each {@link Linker} provides a symbol lookup for libraries that are commonly used on the OS and processor
112  * combination supported by that {@link Linker}. This symbol lookup, which is known as a <em>default lookup</em>,
113  * helps clients to quickly find addresses of well-known symbols. For example, a {@link Linker} for Linux/x64 might choose to
114  * expose symbols in {@code libc} through the default lookup:
115  *
116  * {@snippet lang = java:
117  * Linker nativeLinker = Linker.nativeLinker();
118  * SymbolLookup stdlib = nativeLinker.defaultLookup();
119  * MemorySegment malloc = stdlib.find("malloc").orElseThrow();
120  *}
121  *
122  * @since 19
123  */
124 @PreviewFeature(feature=PreviewFeature.Feature.FOREIGN)
125 @FunctionalInterface
126 public interface SymbolLookup {
127 
128     /**
129      * Returns the address of the symbol with the given name.
130      * @param name the symbol name.
131      * @return a zero-length memory segment whose address indicates the address of the symbol, if found.
132      */
133     Optional<MemorySegment> find(String name);
134 
135     /**
136      * {@return a composed symbol lookup that returns result of finding the symbol with this lookup if found,
137      * otherwise returns the result of finding the symbol with the other lookup}
138      *
139      * @apiNote This method could be used to chain multiple symbol lookups together, e.g. so that symbols could
140      * be retrieved, in order, from multiple libraries:
141      * {@snippet lang = java:
142      * var lookup = SymbolLookup.libraryLookup("foo", arena)
143      *         .or(SymbolLookup.libraryLookup("bar", arena))
144      *         .or(SymbolLookup.loaderLookup());
145      *}
146      * The above code creates a symbol lookup that first searches for symbols in the "foo" library. If no symbol is found
147      * in "foo" then "bar" is searched. Finally, if a symbol is not found in neither "foo" nor "bar", the {@linkplain
148      * SymbolLookup#loaderLookup() loader lookup} is used.
149      *
150      * @param other the symbol lookup that should be used to look for symbols not found in this lookup.
151      */
152     default SymbolLookup or(SymbolLookup other) {
153         Objects.requireNonNull(other);
154         return name -> find(name).or(() -> other.find(name));
155     }
156 
157     /**
158      * Returns a symbol lookup for symbols in the libraries associated with the caller's class loader.
159      * <p>
160      * A library is associated with a class loader {@code CL} when the library is loaded via an invocation of
161      * {@link System#load(String)} or {@link System#loadLibrary(String)} from code in a class defined by {@code CL}.
162      * If that code makes further invocations of {@link System#load(String)} or {@link System#loadLibrary(String)},
163      * then more libraries are loaded and associated with {@code CL}. The symbol lookup returned by this method is always
164      * current: it reflects all the libraries associated with the relevant class loader, even if they were loaded after
165      * this method returned.
166      * <p>
167      * Libraries associated with a class loader are unloaded when the class loader becomes
168      * <a href="../../../java/lang/ref/package.html#reachability">unreachable</a>. The symbol lookup
169      * returned by this method is associated with a fresh {@linkplain MemorySegment.Scope scope} which keeps the caller's
170      * class loader reachable. Therefore, libraries associated with the caller's class loader are kept loaded
171      * (and their symbols available) as long as a loader lookup for that class loader, or any of the segments
172      * obtained by it, is reachable.
173      * <p>
174      * In cases where this method is called from a context where there is no caller frame on the stack
175      * (e.g. when called directly from a JNI attached thread), the caller's class loader defaults to the
176      * {@linkplain ClassLoader#getSystemClassLoader system class loader}.
177      *
178      * @return a symbol lookup for symbols in the libraries associated with the caller's class loader.
179      * @see System#load(String)
180      * @see System#loadLibrary(String)
181      */
182     @CallerSensitive
183     static SymbolLookup loaderLookup() {
184         Class<?> caller = Reflection.getCallerClass();
185         // If there's no caller class, fallback to system loader
186         ClassLoader loader = caller != null ?
187                 caller.getClassLoader() :
188                 ClassLoader.getSystemClassLoader();
189         Arena loaderArena;// builtin loaders never go away
190         if ((loader == null || loader instanceof BuiltinClassLoader)) {
191             loaderArena = Arena.global();
192         } else {
193             MemorySessionImpl session = MemorySessionImpl.heapSession(loader);
194             loaderArena = session.asArena();
195         }
196         return name -> {
197             Objects.requireNonNull(name);
198             if (Utils.containsNullChars(name)) return Optional.empty();
199             JavaLangAccess javaLangAccess = SharedSecrets.getJavaLangAccess();
200             // note: ClassLoader::findNative supports a null loader
201             long addr = javaLangAccess.findNative(loader, name);
202             return addr == 0L ?
203                     Optional.empty() :
204                     Optional.of(MemorySegment.ofAddress(addr)
205                                     .reinterpret(loaderArena, null));
206         };
207     }
208 
209     /**
210      * Loads a library with the given name (if not already loaded) and creates a symbol lookup for symbols in that library.
211      * The lifetime of the returned library lookup is controlled by the provided arena.
212      * For instance, if the provided arena is a confined arena, the library
213      * associated with the returned lookup will be unloaded when the provided confined arena is
214      * {@linkplain Arena#close() closed}.
215      * <p>
216      * This method is <a href="package-summary.html#restricted"><em>restricted</em></a>.
217      * Restricted methods are unsafe, and, if used incorrectly, their use might crash
218      * the JVM or, worse, silently result in memory corruption. Thus, clients should refrain from depending on
219      * restricted methods, and use safe and supported functionalities, where possible.
220      *
221      * @implNote The process of resolving a library name is OS-specific. For instance, in a POSIX-compliant OS,
222      * the library name is resolved according to the specification of the {@code dlopen} function for that OS.
223      * In Windows, the library name is resolved according to the specification of the {@code LoadLibrary} function.
224      *
225      * @param name the name of the library in which symbols should be looked up.
226      * @param arena the arena associated with symbols obtained from the returned lookup.
227      * @return a new symbol lookup suitable to find symbols in a library with the given name.
228      * @throws IllegalStateException if {@code arena.scope().isAlive() == false}
229      * @throws WrongThreadException if {@code arena} is a confined arena, and this method is called from a
230      * thread {@code T}, other than the arena's owner thread.
231      * @throws IllegalArgumentException if {@code name} does not identify a valid library.
232      * @throws IllegalCallerException If the caller is in a module that does not have native access enabled.
233      */
234     @CallerSensitive
235     static SymbolLookup libraryLookup(String name, Arena arena) {
236         Reflection.ensureNativeAccess(Reflection.getCallerClass(), SymbolLookup.class, "libraryLookup");
237         if (Utils.containsNullChars(name)) {
238             throw new IllegalArgumentException("Cannot open library: " + name);
239         }
240         return libraryLookup(name, RawNativeLibraries::load, arena);
241     }
242 
243     /**
244      * Loads a library from the given path (if not already loaded) and creates a symbol lookup for symbols
245      * in that library. The lifetime of the returned library lookup is controlled by the provided arena.
246      * For instance, if the provided arena is a confined arena, the library
247      * associated with the returned lookup will be unloaded when the provided confined arena is
248      * {@linkplain Arena#close() closed}.
249      * <p>
250      * This method is <a href="package-summary.html#restricted"><em>restricted</em></a>.
251      * Restricted methods are unsafe, and, if used incorrectly, their use might crash
252      * the JVM or, worse, silently result in memory corruption. Thus, clients should refrain from depending on
253      * restricted methods, and use safe and supported functionalities, where possible.
254      *
255      * @implNote On Linux, the functionalities provided by this factory method and the returned symbol lookup are
256      * implemented using the {@code dlopen}, {@code dlsym} and {@code dlclose} functions.
257      * @param path the path of the library in which symbols should be looked up.
258      * @param arena the arena associated with symbols obtained from the returned lookup.
259      * @return a new symbol lookup suitable to find symbols in a library with the given path.
260      * @throws IllegalStateException if {@code arena.scope().isAlive() == false}
261      * @throws WrongThreadException if {@code arena} is a confined arena, and this method is called from a
262      * thread {@code T}, other than the arena's owner thread.
263      * @throws IllegalArgumentException if {@code path} does not point to a valid library.
264      * @throws IllegalCallerException If the caller is in a module that does not have native access enabled.
265      */
266     @CallerSensitive
267     static SymbolLookup libraryLookup(Path path, Arena arena) {
268         Reflection.ensureNativeAccess(Reflection.getCallerClass(), SymbolLookup.class, "libraryLookup");
269         return libraryLookup(path, RawNativeLibraries::load, arena);
270     }
271 
272     private static <Z> SymbolLookup libraryLookup(Z libDesc, BiFunction<RawNativeLibraries, Z, NativeLibrary> loadLibraryFunc, Arena libArena) {
273         Objects.requireNonNull(libDesc);
274         Objects.requireNonNull(libArena);
275         // attempt to load native library from path or name
276         RawNativeLibraries nativeLibraries = RawNativeLibraries.newInstance(MethodHandles.lookup());
277         NativeLibrary library = loadLibraryFunc.apply(nativeLibraries, libDesc);
278         if (library == null) {
279             throw new IllegalArgumentException("Cannot open library: " + libDesc);
280         }
281         // register hook to unload library when 'libScope' becomes not alive
282         MemorySessionImpl.toMemorySession(libArena).addOrCleanupIfFail(new MemorySessionImpl.ResourceList.ResourceCleanup() {
283             @Override
284             public void cleanup() {
285                 nativeLibraries.unload(library);
286             }
287         });
288         return name -> {
289             Objects.requireNonNull(name);
290             if (Utils.containsNullChars(name)) return Optional.empty();
291             long addr = library.find(name);
292             return addr == 0L ?
293                     Optional.empty() :
294                     Optional.of(MemorySegment.ofAddress(addr)
295                             .reinterpret(libArena, null));
296         };
297     }
298 }