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